From 896d8143854a43bb22d52f9b72c19e88b90154ec Mon Sep 17 00:00:00 2001 From: CalDescent Date: Sun, 12 Feb 2023 13:20:23 +0000 Subject: [PATCH 01/35] Add block_sequence to Transactions table, and populate all past transactions. This data was being lost when pruning the BlockTransactions table. Note: on first run this will reshape the db, which can take several minutes. --- src/main/java/org/qortal/block/Block.java | 11 ++- .../org/qortal/controller/Controller.java | 1 + .../data/transaction/TransactionData.java | 14 ++++ .../qortal/repository/RepositoryManager.java | 68 +++++++++++++++++++ .../repository/TransactionRepository.java | 2 + .../hsqldb/HSQLDBDatabaseUpdates.java | 11 +++ .../HSQLDBTransactionRepository.java | 20 +++++- 7 files changed, 123 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/qortal/block/Block.java b/src/main/java/org/qortal/block/Block.java index 3f306b93..59de8870 100644 --- a/src/main/java/org/qortal/block/Block.java +++ b/src/main/java/org/qortal/block/Block.java @@ -1682,12 +1682,14 @@ public class Block { transactionData.getSignature()); this.repository.getBlockRepository().save(blockTransactionData); - // Update transaction's height in repository + // Update transaction's height in repository and local transactionData transactionRepository.updateBlockHeight(transactionData.getSignature(), this.blockData.getHeight()); - - // Update local transactionData's height too transaction.getTransactionData().setBlockHeight(this.blockData.getHeight()); + // Update transaction's sequence in repository and local transactionData + transactionRepository.updateBlockSequence(transactionData.getSignature(), sequence); + transaction.getTransactionData().setBlockSequence(sequence); + // No longer unconfirmed transactionRepository.confirmTransaction(transactionData.getSignature()); @@ -1774,6 +1776,9 @@ public class Block { // Unset height transactionRepository.updateBlockHeight(transactionData.getSignature(), null); + + // Unset sequence + transactionRepository.updateBlockSequence(transactionData.getSignature(), null); } transactionRepository.deleteParticipants(transactionData); diff --git a/src/main/java/org/qortal/controller/Controller.java b/src/main/java/org/qortal/controller/Controller.java index e9e1fcc2..054e5530 100644 --- a/src/main/java/org/qortal/controller/Controller.java +++ b/src/main/java/org/qortal/controller/Controller.java @@ -404,6 +404,7 @@ public class Controller extends Thread { try (final Repository repository = RepositoryManager.getRepository()) { RepositoryManager.archive(repository); RepositoryManager.prune(repository); + RepositoryManager.rebuildTransactionSequences(repository); } } catch (DataException e) { // If exception has no cause then repository is in use by some other process. diff --git a/src/main/java/org/qortal/data/transaction/TransactionData.java b/src/main/java/org/qortal/data/transaction/TransactionData.java index ec1139f4..713f98b5 100644 --- a/src/main/java/org/qortal/data/transaction/TransactionData.java +++ b/src/main/java/org/qortal/data/transaction/TransactionData.java @@ -76,6 +76,10 @@ public abstract class TransactionData { @Schema(accessMode = AccessMode.READ_ONLY, hidden = true, description = "height of block containing transaction") protected Integer blockHeight; + // Not always present + @Schema(accessMode = AccessMode.READ_ONLY, hidden = true, description = "sequence in block containing transaction") + protected Integer blockSequence; + // Not always present @Schema(accessMode = AccessMode.READ_ONLY, description = "group-approval status") protected ApprovalStatus approvalStatus; @@ -106,6 +110,7 @@ public abstract class TransactionData { this.fee = baseTransactionData.fee; this.signature = baseTransactionData.signature; this.blockHeight = baseTransactionData.blockHeight; + this.blockSequence = baseTransactionData.blockSequence; this.approvalStatus = baseTransactionData.approvalStatus; this.approvalHeight = baseTransactionData.approvalHeight; } @@ -174,6 +179,15 @@ public abstract class TransactionData { this.blockHeight = blockHeight; } + public Integer getBlockSequence() { + return this.blockSequence; + } + + @XmlTransient + public void setBlockSequence(Integer blockSequence) { + this.blockSequence = blockSequence; + } + public ApprovalStatus getApprovalStatus() { return approvalStatus; } diff --git a/src/main/java/org/qortal/repository/RepositoryManager.java b/src/main/java/org/qortal/repository/RepositoryManager.java index 0d9325b9..983404c1 100644 --- a/src/main/java/org/qortal/repository/RepositoryManager.java +++ b/src/main/java/org/qortal/repository/RepositoryManager.java @@ -2,13 +2,18 @@ package org.qortal.repository; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.qortal.data.transaction.TransactionData; import org.qortal.gui.SplashFrame; import org.qortal.repository.hsqldb.HSQLDBDatabaseArchiving; import org.qortal.repository.hsqldb.HSQLDBDatabasePruning; import org.qortal.repository.hsqldb.HSQLDBRepository; import org.qortal.settings.Settings; +import org.qortal.transaction.Transaction; import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; import java.util.concurrent.TimeoutException; public abstract class RepositoryManager { @@ -117,6 +122,69 @@ public abstract class RepositoryManager { return false; } + public static boolean rebuildTransactionSequences(Repository repository) throws DataException { + if (Settings.getInstance().isLite()) { + // Lite nodes have no blockchain + return false; + } + + try { + // Check if we have any unpopulated block_sequence values for the first 1000 blocks + List testSignatures = repository.getTransactionRepository().getSignaturesMatchingCustomCriteria( + null, Arrays.asList("block_height < 1000 AND block_sequence IS NULL"), new ArrayList<>()); + if (testSignatures.isEmpty()) { + // block_sequence already populated for the first 1000 blocks, so assume complete. + // We avoid checkpointing and prevent the node from starting up in the case of a rebuild failure, so + // we shouldn't ever be left in a partially rebuilt state. + return false; + } + + int blockchainHeight = repository.getBlockRepository().getBlockchainHeight(); + int totalTransactionCount = 0; + + for (int height = 1; height < blockchainHeight; height++) { + List transactions = new ArrayList<>(); + + // Fetch transactions for height + List signatures = repository.getTransactionRepository().getSignaturesMatchingCriteria(null, null, height, height); + for (byte[] signature : signatures) { + TransactionData transactionData = repository.getTransactionRepository().fromSignature(signature); + if (transactionData != null) { + transactions.add(transactionData); + } + } + totalTransactionCount += transactions.size(); + + // Sort the transactions for this height + transactions.sort(Transaction.getDataComparator()); + + // Loop through and update sequences + for (int sequence = 0; sequence < transactions.size(); ++sequence) { + TransactionData transactionData = transactions.get(sequence); + + // Update transaction's sequence in repository + repository.getTransactionRepository().updateBlockSequence(transactionData.getSignature(), sequence); + } + + if (height % 10000 == 0) { + LOGGER.info("Rebuilt sequences for {} blocks (total transactions: {})", height, totalTransactionCount); + } + + repository.saveChanges(); + } + + LOGGER.info("Completed rebuild of transaction sequences."); + return true; + } + catch (DataException e) { + LOGGER.info("Unable to rebuild transaction sequences: {}. The database may have been left in an inconsistent state.", e.getMessage()); + + // Throw an exception so that the node startup is halted, allowing for a retry next time. + repository.discardChanges(); + throw new DataException("Rebuild of transaction sequences failed."); + } + } + public static void setRequestedCheckpoint(Boolean quick) { quickCheckpointRequested = quick; } diff --git a/src/main/java/org/qortal/repository/TransactionRepository.java b/src/main/java/org/qortal/repository/TransactionRepository.java index 105a317d..e528166b 100644 --- a/src/main/java/org/qortal/repository/TransactionRepository.java +++ b/src/main/java/org/qortal/repository/TransactionRepository.java @@ -309,6 +309,8 @@ public interface TransactionRepository { public void updateBlockHeight(byte[] signature, Integer height) throws DataException; + public void updateBlockSequence(byte[] signature, Integer sequence) throws DataException; + public void updateApprovalHeight(byte[] signature, Integer approvalHeight) throws DataException; /** diff --git a/src/main/java/org/qortal/repository/hsqldb/HSQLDBDatabaseUpdates.java b/src/main/java/org/qortal/repository/hsqldb/HSQLDBDatabaseUpdates.java index aecac034..cd2b30fa 100644 --- a/src/main/java/org/qortal/repository/hsqldb/HSQLDBDatabaseUpdates.java +++ b/src/main/java/org/qortal/repository/hsqldb/HSQLDBDatabaseUpdates.java @@ -993,6 +993,17 @@ public class HSQLDBDatabaseUpdates { stmt.execute("ALTER TABLE CancelSellNameTransactions ADD sale_price QortalAmount"); break; + case 47: + // Add `block_sequence` to the Transaction table, as the BlockTransactions table is pruned for + // older blocks and therefore the sequence becomes unavailable + LOGGER.info("Reshaping Transactions table - this can take a while..."); + stmt.execute("ALTER TABLE Transactions ADD block_sequence INTEGER"); + + // For finding transactions by height and sequence + LOGGER.info("Adding index to Transactions table - this can take a while..."); + stmt.execute("CREATE INDEX TransactionHeightSequenceIndex on Transactions (block_height, block_sequence)"); + break; + default: // nothing to do return false; diff --git a/src/main/java/org/qortal/repository/hsqldb/transaction/HSQLDBTransactionRepository.java b/src/main/java/org/qortal/repository/hsqldb/transaction/HSQLDBTransactionRepository.java index a8df1ab5..5bf149b2 100644 --- a/src/main/java/org/qortal/repository/hsqldb/transaction/HSQLDBTransactionRepository.java +++ b/src/main/java/org/qortal/repository/hsqldb/transaction/HSQLDBTransactionRepository.java @@ -657,8 +657,13 @@ public class HSQLDBTransactionRepository implements TransactionRepository { List bindParams) throws DataException { List signatures = new ArrayList<>(); + String txTypeClassName = ""; + if (txType != null) { + txTypeClassName = txType.className; + } + StringBuilder sql = new StringBuilder(1024); - sql.append(String.format("SELECT signature FROM %sTransactions", txType.className)); + sql.append(String.format("SELECT signature FROM %sTransactions", txTypeClassName)); if (!whereClauses.isEmpty()) { sql.append(" WHERE "); @@ -1444,6 +1449,19 @@ public class HSQLDBTransactionRepository implements TransactionRepository { } } + @Override + public void updateBlockSequence(byte[] signature, Integer blockSequence) throws DataException { + HSQLDBSaver saver = new HSQLDBSaver("Transactions"); + + saver.bind("signature", signature).bind("block_sequence", blockSequence); + + try { + saver.execute(repository); + } catch (SQLException e) { + throw new DataException("Unable to update transaction's block sequence in repository", e); + } + } + @Override public void updateApprovalHeight(byte[] signature, Integer approvalHeight) throws DataException { HSQLDBSaver saver = new HSQLDBSaver("Transactions"); From af6be759e7ab6c25bd54f10ca509735251358871 Mon Sep 17 00:00:00 2001 From: CalDescent Date: Sun, 12 Feb 2023 13:20:31 +0000 Subject: [PATCH 02/35] Fixed long term issue where logs would report "Repository in use by another process?" when the database actually failed to start for some other reason. It will now log the correct reason. --- src/main/java/org/qortal/controller/Controller.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/qortal/controller/Controller.java b/src/main/java/org/qortal/controller/Controller.java index 054e5530..69995180 100644 --- a/src/main/java/org/qortal/controller/Controller.java +++ b/src/main/java/org/qortal/controller/Controller.java @@ -407,8 +407,8 @@ public class Controller extends Thread { RepositoryManager.rebuildTransactionSequences(repository); } } catch (DataException e) { - // If exception has no cause then repository is in use by some other process. - if (e.getCause() == null) { + // If exception has no cause or message then repository is in use by some other process. + if (e.getCause() == null && e.getMessage() == null) { LOGGER.info("Repository in use by another process?"); Gui.getInstance().fatalError("Repository issue", "Repository in use by another process?"); } else { From a8c27be18ac814e264b06f06d966f29fa4b6648d Mon Sep 17 00:00:00 2001 From: CalDescent Date: Sun, 12 Feb 2023 13:21:41 +0000 Subject: [PATCH 03/35] Modified AT and transaction repository queries to use Transactions.block_sequence instead of BlockTransactions.sequence. The former is available for all blocks, whereas the latter is only available for unpruned blocks. Also removed joins with the Blocks table - as the Blocks table is also pruned - and instead retrieved the height from the Transactions table. --- .../repository/hsqldb/HSQLDBATRepository.java | 16 +++++++--------- .../transaction/HSQLDBTransactionRepository.java | 3 +-- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/src/main/java/org/qortal/repository/hsqldb/HSQLDBATRepository.java b/src/main/java/org/qortal/repository/hsqldb/HSQLDBATRepository.java index dd0404a8..33817309 100644 --- a/src/main/java/org/qortal/repository/hsqldb/HSQLDBATRepository.java +++ b/src/main/java/org/qortal/repository/hsqldb/HSQLDBATRepository.java @@ -296,10 +296,9 @@ public class HSQLDBATRepository implements ATRepository { @Override public Integer getATCreationBlockHeight(String atAddress) throws DataException { - String sql = "SELECT height " + String sql = "SELECT block_height " + "FROM DeployATTransactions " - + "JOIN BlockTransactions ON transaction_signature = signature " - + "JOIN Blocks ON Blocks.signature = block_signature " + + "JOIN Transactions USING (signature) " + "WHERE AT_address = ? " + "LIMIT 1"; @@ -877,18 +876,17 @@ public class HSQLDBATRepository implements ATRepository { public NextTransactionInfo findNextTransaction(String recipient, int height, int sequence) throws DataException { // We only need to search for a subset of transaction types: MESSAGE, PAYMENT or AT - String sql = "SELECT height, sequence, Transactions.signature " + String sql = "SELECT block_height, block_sequence, Transactions.signature " + "FROM (" + "SELECT signature FROM PaymentTransactions WHERE recipient = ? " + "UNION " + "SELECT signature FROM MessageTransactions WHERE recipient = ? " + "UNION " + "SELECT signature FROM ATTransactions WHERE recipient = ?" - + ") AS Transactions " - + "JOIN BlockTransactions ON BlockTransactions.transaction_signature = Transactions.signature " - + "JOIN Blocks ON Blocks.signature = BlockTransactions.block_signature " - + "WHERE (height > ? OR (height = ? AND sequence > ?)) " - + "ORDER BY height ASC, sequence ASC " + + ") AS SelectedTransactions " + + "JOIN Transactions USING (signature)" + + "WHERE (block_height > ? OR (block_height = ? AND block_sequence > ?)) " + + "ORDER BY block_height ASC, block_sequence ASC " + "LIMIT 1"; Object[] bindParams = new Object[] { recipient, recipient, recipient, height, height, sequence }; diff --git a/src/main/java/org/qortal/repository/hsqldb/transaction/HSQLDBTransactionRepository.java b/src/main/java/org/qortal/repository/hsqldb/transaction/HSQLDBTransactionRepository.java index 5bf149b2..e7bab926 100644 --- a/src/main/java/org/qortal/repository/hsqldb/transaction/HSQLDBTransactionRepository.java +++ b/src/main/java/org/qortal/repository/hsqldb/transaction/HSQLDBTransactionRepository.java @@ -194,8 +194,7 @@ public class HSQLDBTransactionRepository implements TransactionRepository { @Override public TransactionData fromHeightAndSequence(int height, int sequence) throws DataException { - String sql = "SELECT transaction_signature FROM BlockTransactions JOIN Blocks ON signature = block_signature " - + "WHERE height = ? AND sequence = ?"; + String sql = "SELECT signature FROM Transactions WHERE block_height = ? AND block_sequence = ?"; try (ResultSet resultSet = this.repository.checkedExecute(sql, height, sequence)) { if (resultSet == null) From 20d4e88fabf770e3294362f39fd899a98bc30625 Mon Sep 17 00:00:00 2001 From: CalDescent Date: Sun, 12 Feb 2023 13:21:54 +0000 Subject: [PATCH 04/35] Fixed API endpoints relying on getTransactionsFromSignature(), which therefore won't have worked properly since core V2. --- .../qortal/api/resource/BlocksResource.java | 25 +++++++++++++------ .../api/resource/TransactionsResource.java | 21 +++++++++++++--- 2 files changed, 36 insertions(+), 10 deletions(-) diff --git a/src/main/java/org/qortal/api/resource/BlocksResource.java b/src/main/java/org/qortal/api/resource/BlocksResource.java index 15541802..98c5f00d 100644 --- a/src/main/java/org/qortal/api/resource/BlocksResource.java +++ b/src/main/java/org/qortal/api/resource/BlocksResource.java @@ -218,14 +218,25 @@ public class BlocksResource { } try (final Repository repository = RepositoryManager.getRepository()) { - // Check if the block exists in either the database or archive - if (repository.getBlockRepository().getHeightFromSignature(signature) == 0 && - repository.getBlockArchiveRepository().getHeightFromSignature(signature) == 0) { - // Not found in either the database or archive - throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.BLOCK_UNKNOWN); - } + // Check if the block exists in either the database or archive + int height = repository.getBlockRepository().getHeightFromSignature(signature); + if (height == 0) { + height = repository.getBlockArchiveRepository().getHeightFromSignature(signature); + if (height == 0) { + // Not found in either the database or archive + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.BLOCK_UNKNOWN); + } + } - return repository.getBlockRepository().getTransactionsFromSignature(signature, limit, offset, reverse); + List signatures = repository.getTransactionRepository().getSignaturesMatchingCriteria(null, null, height, height); + + // Expand signatures to transactions + List transactions = new ArrayList<>(signatures.size()); + for (byte[] s : signatures) { + transactions.add(repository.getTransactionRepository().fromSignature(s)); + } + + return transactions; } catch (DataException e) { throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e); } diff --git a/src/main/java/org/qortal/api/resource/TransactionsResource.java b/src/main/java/org/qortal/api/resource/TransactionsResource.java index 2b9b28a1..a8962497 100644 --- a/src/main/java/org/qortal/api/resource/TransactionsResource.java +++ b/src/main/java/org/qortal/api/resource/TransactionsResource.java @@ -220,10 +220,25 @@ public class TransactionsResource { } try (final Repository repository = RepositoryManager.getRepository()) { - if (repository.getBlockRepository().getHeightFromSignature(signature) == 0) - throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.BLOCK_UNKNOWN); + // Check if the block exists in either the database or archive + int height = repository.getBlockRepository().getHeightFromSignature(signature); + if (height == 0) { + height = repository.getBlockArchiveRepository().getHeightFromSignature(signature); + if (height == 0) { + // Not found in either the database or archive + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.BLOCK_UNKNOWN); + } + } - return repository.getBlockRepository().getTransactionsFromSignature(signature, limit, offset, reverse); + List signatures = repository.getTransactionRepository().getSignaturesMatchingCriteria(null, null, height, height); + + // Expand signatures to transactions + List transactions = new ArrayList<>(signatures.size()); + for (byte[] s : signatures) { + transactions.add(repository.getTransactionRepository().fromSignature(s)); + } + + return transactions; } catch (ApiException e) { throw e; } catch (DataException e) { From bc44b998dc95a6361284c83a3d363bb0cf73b1f5 Mon Sep 17 00:00:00 2001 From: CalDescent Date: Fri, 10 Mar 2023 21:29:35 +0000 Subject: [PATCH 05/35] The transaction sequences reshape now fetches transactions from the archive. This is required as it's the only place that holds the original order of each block's transactions. We cannot sort them, because the comparator function for transactions has some dependencies on the existing order for AT transactions. As a result, topOnly nodes cannot perform this reshape, and will be unable to run this version. --- .../qortal/repository/RepositoryManager.java | 31 +++++++++++++------ 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/src/main/java/org/qortal/repository/RepositoryManager.java b/src/main/java/org/qortal/repository/RepositoryManager.java index 6d1f361e..9008f98e 100644 --- a/src/main/java/org/qortal/repository/RepositoryManager.java +++ b/src/main/java/org/qortal/repository/RepositoryManager.java @@ -2,9 +2,12 @@ package org.qortal.repository; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.qortal.block.Block; +import org.qortal.data.block.BlockData; import org.qortal.data.transaction.TransactionData; import org.qortal.settings.Settings; import org.qortal.transaction.Transaction; +import org.qortal.transform.block.BlockTransformation; import java.sql.SQLException; import java.util.ArrayList; @@ -66,6 +69,10 @@ public abstract class RepositoryManager { // Lite nodes have no blockchain return false; } + if (Settings.getInstance().isTopOnly()) { + // topOnly nodes are unable to perform this reindex, and so are temporarily unsupported + throw new DataException("topOnly nodes are now unsupported, as they are missing data required for a db reshape"); + } try { // Check if we have any unpopulated block_sequence values for the first 1000 blocks @@ -78,24 +85,30 @@ public abstract class RepositoryManager { return false; } + LOGGER.info("Rebuilding transaction sequences - this will take a while..."); + int blockchainHeight = repository.getBlockRepository().getBlockchainHeight(); int totalTransactionCount = 0; for (int height = 1; height < blockchainHeight; height++) { List transactions = new ArrayList<>(); - // Fetch transactions for height - List signatures = repository.getTransactionRepository().getSignaturesMatchingCriteria(null, null, height, height); - for (byte[] signature : signatures) { - TransactionData transactionData = repository.getTransactionRepository().fromSignature(signature); - if (transactionData != null) { - transactions.add(transactionData); + // Fetch block and transactions + BlockData blockData = repository.getBlockRepository().fromHeight(height); + if (blockData == null) { + // Try the archive + BlockTransformation blockTransformation = BlockArchiveReader.getInstance().fetchBlockAtHeight(height); + transactions = blockTransformation.getTransactions(); + } + else { + // Get transactions from db + Block block = new Block(repository, blockData); + for (Transaction transaction : block.getTransactions()) { + transactions.add(transaction.getTransactionData()); } } - totalTransactionCount += transactions.size(); - // Sort the transactions for this height - transactions.sort(Transaction.getDataComparator()); + totalTransactionCount += transactions.size(); // Loop through and update sequences for (int sequence = 0; sequence < transactions.size(); ++sequence) { From e4f45c1a7027842c8c1470c0bab29ec8c1438177 Mon Sep 17 00:00:00 2001 From: CalDescent Date: Sun, 12 Mar 2023 19:08:07 +0000 Subject: [PATCH 06/35] Break out of orphan loop when stopping. --- src/main/java/org/qortal/block/BlockChain.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/java/org/qortal/block/BlockChain.java b/src/main/java/org/qortal/block/BlockChain.java index 88880887..218fb14d 100644 --- a/src/main/java/org/qortal/block/BlockChain.java +++ b/src/main/java/org/qortal/block/BlockChain.java @@ -871,6 +871,9 @@ public class BlockChain { BlockData orphanBlockData = repository.getBlockRepository().fromHeight(height); while (height > targetHeight) { + if (Controller.isStopping()) { + return false; + } LOGGER.info(String.format("Forcably orphaning block %d", height)); Block block = new Block(repository, orphanBlockData); From a4551245cbd04b1c07a5c2ee6264d4916866e802 Mon Sep 17 00:00:00 2001 From: CalDescent Date: Sun, 12 Mar 2023 19:08:57 +0000 Subject: [PATCH 07/35] Improved error logging in BlockArchiveUtils.importFromArchive() --- src/main/java/org/qortal/utils/BlockArchiveUtils.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/qortal/utils/BlockArchiveUtils.java b/src/main/java/org/qortal/utils/BlockArchiveUtils.java index 807faef9..f9ca0d0d 100644 --- a/src/main/java/org/qortal/utils/BlockArchiveUtils.java +++ b/src/main/java/org/qortal/utils/BlockArchiveUtils.java @@ -1,5 +1,7 @@ package org.qortal.utils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.qortal.data.at.ATStateData; import org.qortal.data.block.BlockData; import org.qortal.data.transaction.TransactionData; @@ -12,6 +14,8 @@ import java.util.List; public class BlockArchiveUtils { + private static final Logger LOGGER = LogManager.getLogger(BlockArchiveUtils.class); + /** * importFromArchive *

@@ -87,7 +91,8 @@ public class BlockArchiveUtils { } catch (DataException e) { repository.discardChanges(); - throw new IllegalStateException("Unable to import blocks from archive"); + LOGGER.info("Unable to import blocks from archive", e); + throw(e); } } repository.saveChanges(); From 92119b5558cefffbe8d77214b15ea3ca093fcafb Mon Sep 17 00:00:00 2001 From: CalDescent Date: Fri, 12 May 2023 20:14:14 +0100 Subject: [PATCH 08/35] Increased per-name limit for followed names by 4x. --- .../arbitrary/ArbitraryDataStorageManager.java | 6 +++++- .../arbitrary/ArbitraryDataStorageCapacityTests.java | 9 ++++++--- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/qortal/controller/arbitrary/ArbitraryDataStorageManager.java b/src/main/java/org/qortal/controller/arbitrary/ArbitraryDataStorageManager.java index d3aadc43..f6b2dc0a 100644 --- a/src/main/java/org/qortal/controller/arbitrary/ArbitraryDataStorageManager.java +++ b/src/main/java/org/qortal/controller/arbitrary/ArbitraryDataStorageManager.java @@ -57,6 +57,8 @@ public class ArbitraryDataStorageManager extends Thread { * This must be higher than STORAGE_FULL_THRESHOLD in order to avoid a fetch/delete loop. */ public static final double DELETION_THRESHOLD = 0.98f; // 98% + private static final long PER_NAME_STORAGE_MULTIPLIER = 4L; + public ArbitraryDataStorageManager() { } @@ -535,7 +537,9 @@ public class ArbitraryDataStorageManager extends Thread { } double maxStorageCapacity = (double)this.storageCapacity * threshold; - long maxStoragePerName = (long)(maxStorageCapacity / (double)followedNamesCount); + + // Some names won't need/use much space, so give all names a 4x multiplier to compensate + long maxStoragePerName = (long)(maxStorageCapacity / (double)followedNamesCount) * PER_NAME_STORAGE_MULTIPLIER; return maxStoragePerName; } diff --git a/src/test/java/org/qortal/test/arbitrary/ArbitraryDataStorageCapacityTests.java b/src/test/java/org/qortal/test/arbitrary/ArbitraryDataStorageCapacityTests.java index 028c054d..c05ceabf 100644 --- a/src/test/java/org/qortal/test/arbitrary/ArbitraryDataStorageCapacityTests.java +++ b/src/test/java/org/qortal/test/arbitrary/ArbitraryDataStorageCapacityTests.java @@ -113,13 +113,16 @@ public class ArbitraryDataStorageCapacityTests extends Common { assertTrue(resourceListManager.addToList("followedNames", "Test2", false)); assertTrue(resourceListManager.addToList("followedNames", "Test3", false)); assertTrue(resourceListManager.addToList("followedNames", "Test4", false)); + assertTrue(resourceListManager.addToList("followedNames", "Test5", false)); + assertTrue(resourceListManager.addToList("followedNames", "Test6", false)); // Ensure the followed name count is correct - assertEquals(4, resourceListManager.getItemCountForList("followedNames")); - assertEquals(4, ListUtils.followedNamesCount()); + assertEquals(6, resourceListManager.getItemCountForList("followedNames")); + assertEquals(6, ListUtils.followedNamesCount()); // Storage space per name should be the total storage capacity divided by the number of names - long expectedStorageCapacityPerName = (long)(totalStorageCapacity / 4.0f); + // then multiplied by 4, to allow for names that don't use much space + long expectedStorageCapacityPerName = (long)(totalStorageCapacity / 6.0f) * 4L; assertEquals(expectedStorageCapacityPerName, storageManager.storageCapacityPerName(storageFullThreshold)); } From 4cb755a2f1052e6eb1117375cdf7780f87b0e229 Mon Sep 17 00:00:00 2001 From: CalDescent Date: Sat, 13 May 2023 13:48:27 +0100 Subject: [PATCH 09/35] Added `GET /stats/supply/circulating` API endpoint, to fetch total QORT minted so far. --- .../qortal/api/resource/StatsResource.java | 70 +++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 src/main/java/org/qortal/api/resource/StatsResource.java diff --git a/src/main/java/org/qortal/api/resource/StatsResource.java b/src/main/java/org/qortal/api/resource/StatsResource.java new file mode 100644 index 00000000..c1588490 --- /dev/null +++ b/src/main/java/org/qortal/api/resource/StatsResource.java @@ -0,0 +1,70 @@ +package org.qortal.api.resource; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.qortal.api.*; +import org.qortal.block.BlockChain; +import org.qortal.repository.DataException; +import org.qortal.repository.Repository; +import org.qortal.repository.RepositoryManager; +import org.qortal.utils.Amounts; + +import javax.servlet.http.HttpServletRequest; +import javax.ws.rs.*; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.MediaType; +import java.math.BigDecimal; +import java.util.List; + +@Path("/stats") +@Tag(name = "Stats") +public class StatsResource { + + private static final Logger LOGGER = LogManager.getLogger(StatsResource.class); + + + @Context + HttpServletRequest request; + + @GET + @Path("/supply/circulating") + @Operation( + summary = "Fetch circulating QORT supply", + responses = { + @ApiResponse( + description = "circulating supply of QORT", + content = @Content(mediaType = MediaType.TEXT_PLAIN, schema = @Schema(type = "string", format = "number")) + ) + } + ) + public BigDecimal circulatingSupply() { + long total = 0L; + + try (final Repository repository = RepositoryManager.getRepository()) { + int currentHeight = repository.getBlockRepository().getBlockchainHeight(); + + List rewardsByHeight = BlockChain.getInstance().getBlockRewardsByHeight(); + int rewardIndex = rewardsByHeight.size() - 1; + BlockChain.RewardByHeight rewardInfo = rewardsByHeight.get(rewardIndex); + + for (int height = currentHeight; height > 1; --height) { + if (height < rewardInfo.height) { + --rewardIndex; + rewardInfo = rewardsByHeight.get(rewardIndex); + } + + total += rewardInfo.reward; + } + + return Amounts.toBigDecimal(total); + } catch (DataException e) { + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e); + } + } + +} From a8d92805f9170364a3dfcbf8bef6b9c2db982de6 Mon Sep 17 00:00:00 2001 From: CalDescent Date: Sat, 20 May 2023 11:33:43 +0100 Subject: [PATCH 10/35] Added extra check for topOnly mode. --- src/main/java/org/qortal/settings/Settings.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/java/org/qortal/settings/Settings.java b/src/main/java/org/qortal/settings/Settings.java index a87a72f4..901e1956 100644 --- a/src/main/java/org/qortal/settings/Settings.java +++ b/src/main/java/org/qortal/settings/Settings.java @@ -508,6 +508,9 @@ public class Settings { if (this.minBlockchainPeers < 1 && !singleNodeTestnet) throwValidationError("minBlockchainPeers must be at least 1"); + if (this.topOnly) + throwValidationError("topOnly mode is no longer supported"); + if (this.apiKey != null && this.apiKey.trim().length() < 8) throwValidationError("apiKey must be at least 8 characters"); From 8b51590844fa8b12163b66293babd5838092d2f4 Mon Sep 17 00:00:00 2001 From: CalDescent Date: Sat, 20 May 2023 20:54:22 +0100 Subject: [PATCH 11/35] Include AT transactions when rebuilding transaction sequences, as these aren't directly included in the block archive. --- .../qortal/repository/RepositoryManager.java | 90 +++++++++++++++++-- 1 file changed, 83 insertions(+), 7 deletions(-) diff --git a/src/main/java/org/qortal/repository/RepositoryManager.java b/src/main/java/org/qortal/repository/RepositoryManager.java index 9008f98e..1562b38c 100644 --- a/src/main/java/org/qortal/repository/RepositoryManager.java +++ b/src/main/java/org/qortal/repository/RepositoryManager.java @@ -3,17 +3,21 @@ package org.qortal.repository; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.qortal.block.Block; +import org.qortal.crypto.Crypto; +import org.qortal.data.at.ATData; import org.qortal.data.block.BlockData; +import org.qortal.data.transaction.ATTransactionData; import org.qortal.data.transaction.TransactionData; import org.qortal.settings.Settings; import org.qortal.transaction.Transaction; import org.qortal.transform.block.BlockTransformation; import java.sql.SQLException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; +import java.util.*; import java.util.concurrent.TimeoutException; +import java.util.stream.Collectors; + +import static org.qortal.transaction.Transaction.TransactionType.AT; public abstract class RepositoryManager { private static final Logger LOGGER = LogManager.getLogger(RepositoryManager.class); @@ -91,23 +95,95 @@ public abstract class RepositoryManager { int totalTransactionCount = 0; for (int height = 1; height < blockchainHeight; height++) { - List transactions = new ArrayList<>(); + List inputTransactions = new ArrayList<>(); // Fetch block and transactions BlockData blockData = repository.getBlockRepository().fromHeight(height); + boolean loadedFromArchive = false; if (blockData == null) { - // Try the archive + // Get (non-AT) transactions from the archive BlockTransformation blockTransformation = BlockArchiveReader.getInstance().fetchBlockAtHeight(height); - transactions = blockTransformation.getTransactions(); + blockData = blockTransformation.getBlockData(); + inputTransactions = blockTransformation.getTransactions(); // This doesn't include AT transactions + loadedFromArchive = true; } else { // Get transactions from db Block block = new Block(repository, blockData); for (Transaction transaction : block.getTransactions()) { - transactions.add(transaction.getTransactionData()); + inputTransactions.add(transaction.getTransactionData()); } } + if (blockData == null) { + throw new DataException("Missing block data"); + } + + List transactions = new ArrayList<>(); + + if (loadedFromArchive) { + List transactionDataList = new ArrayList<>(blockData.getTransactionCount()); + // Fetch any AT transactions in this block + List atSignatures = repository.getTransactionRepository().getSignaturesMatchingCriteria(null, null, height, height); + for (byte[] s : atSignatures) { + TransactionData transactionData = repository.getTransactionRepository().fromSignature(s); + if (transactionData.getType() == AT) { + transactionDataList.add(transactionData); + } + } + + List atTransactions = new ArrayList<>(); + for (TransactionData transactionData : transactionDataList) { + ATTransactionData atTransactionData = (ATTransactionData) transactionData; + atTransactions.add(atTransactionData); + } + + // Create sorted list of ATs by creation time + List ats = new ArrayList<>(); + + for (ATTransactionData atTransactionData : atTransactions) { + ATData atData = repository.getATRepository().fromATAddress(atTransactionData.getATAddress()); + if (!ats.contains(atData)) { + ats.add(atData); + } + } + + // Sort list of ATs by creation date + ats.sort(Comparator.comparingLong(ATData::getCreation)); + + // Loop through unique ATs + for (ATData atData : ats) { + List thisAtTransactions = atTransactions.stream() + .filter(t -> Objects.equals(t.getATAddress(), atData.getATAddress())) + .collect(Collectors.toList()); + + int count = thisAtTransactions.size(); + + if (count == 1) { + ATTransactionData atTransactionData = thisAtTransactions.get(0); + transactions.add(atTransactionData); + } + else if (count == 2) { + String atCreatorAddress = Crypto.toAddress(atData.getCreatorPublicKey()); + + ATTransactionData atTransactionData1 = thisAtTransactions.stream() + .filter(t -> !Objects.equals(t.getRecipient(), atCreatorAddress)) + .findFirst().orElse(null); + transactions.add(atTransactionData1); + + ATTransactionData atTransactionData2 = thisAtTransactions.stream() + .filter(t -> Objects.equals(t.getRecipient(), atCreatorAddress)) + .findFirst().orElse(null); + transactions.add(atTransactionData2); + } + else if (count > 2) { + LOGGER.info("Error: AT has more than 2 output transactions"); + } + } + } + + // Add all the regular transactions now that AT transactions have been handled + transactions.addAll(inputTransactions); totalTransactionCount += transactions.size(); // Loop through and update sequences From e1043ceacb490e1f0b34ef6032bd457a2aa15c00 Mon Sep 17 00:00:00 2001 From: CalDescent Date: Sun, 21 May 2023 08:41:56 +0100 Subject: [PATCH 12/35] Fixed bug causing duplicate AT entries in local array. --- src/main/java/org/qortal/repository/RepositoryManager.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/qortal/repository/RepositoryManager.java b/src/main/java/org/qortal/repository/RepositoryManager.java index 1562b38c..fefaeea9 100644 --- a/src/main/java/org/qortal/repository/RepositoryManager.java +++ b/src/main/java/org/qortal/repository/RepositoryManager.java @@ -143,7 +143,8 @@ public abstract class RepositoryManager { for (ATTransactionData atTransactionData : atTransactions) { ATData atData = repository.getATRepository().fromATAddress(atTransactionData.getATAddress()); - if (!ats.contains(atData)) { + boolean hasExistingEntry = ats.stream().anyMatch(a -> Objects.equals(a.getATAddress(), atTransactionData.getATAddress())); + if (!hasExistingEntry) { ats.add(atData); } } From b9015217de26fcd4dc8224d739c130f34ff1ce09 Mon Sep 17 00:00:00 2001 From: CalDescent Date: Sun, 21 May 2023 11:05:34 +0100 Subject: [PATCH 13/35] Fixed bug causing final block to be missed in the reshape. --- src/main/java/org/qortal/repository/RepositoryManager.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/qortal/repository/RepositoryManager.java b/src/main/java/org/qortal/repository/RepositoryManager.java index fefaeea9..390c72c2 100644 --- a/src/main/java/org/qortal/repository/RepositoryManager.java +++ b/src/main/java/org/qortal/repository/RepositoryManager.java @@ -94,7 +94,7 @@ public abstract class RepositoryManager { int blockchainHeight = repository.getBlockRepository().getBlockchainHeight(); int totalTransactionCount = 0; - for (int height = 1; height < blockchainHeight; height++) { + for (int height = 1; height < blockchainHeight; ++height) { List inputTransactions = new ArrayList<>(); // Fetch block and transactions From 68b99c8643d264b5315fa0d9bf28bffe6fc0bf66 Mon Sep 17 00:00:00 2001 From: CalDescent Date: Sun, 21 May 2023 12:28:51 +0100 Subject: [PATCH 14/35] Update status when rebuilding transaction sequences. --- src/main/java/org/qortal/repository/RepositoryManager.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/org/qortal/repository/RepositoryManager.java b/src/main/java/org/qortal/repository/RepositoryManager.java index 390c72c2..d874effd 100644 --- a/src/main/java/org/qortal/repository/RepositoryManager.java +++ b/src/main/java/org/qortal/repository/RepositoryManager.java @@ -91,6 +91,8 @@ public abstract class RepositoryManager { LOGGER.info("Rebuilding transaction sequences - this will take a while..."); + SplashFrame.getInstance().updateStatus("Rebuilding transactions - please wait..."); + int blockchainHeight = repository.getBlockRepository().getBlockchainHeight(); int totalTransactionCount = 0; From a74fa15d60b0a8a2e122d23d5aa5c7fb67318c2c Mon Sep 17 00:00:00 2001 From: CalDescent Date: Sun, 21 May 2023 12:31:49 +0100 Subject: [PATCH 15/35] Missing import --- src/main/java/org/qortal/repository/RepositoryManager.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/org/qortal/repository/RepositoryManager.java b/src/main/java/org/qortal/repository/RepositoryManager.java index d874effd..92936278 100644 --- a/src/main/java/org/qortal/repository/RepositoryManager.java +++ b/src/main/java/org/qortal/repository/RepositoryManager.java @@ -8,6 +8,7 @@ import org.qortal.data.at.ATData; import org.qortal.data.block.BlockData; import org.qortal.data.transaction.ATTransactionData; import org.qortal.data.transaction.TransactionData; +import org.qortal.gui.SplashFrame; import org.qortal.settings.Settings; import org.qortal.transaction.Transaction; import org.qortal.transform.block.BlockTransformation; From c6456669e2a0daf65542295362e418e469c0eb49 Mon Sep 17 00:00:00 2001 From: CalDescent Date: Sun, 21 May 2023 12:33:37 +0100 Subject: [PATCH 16/35] Don't allow core to start if transaction sequences haven't been rebuilt yet. --- .../java/org/qortal/controller/Controller.java | 12 ++++++++++++ .../qortal/repository/RepositoryManager.java | 17 ++++++++++++++--- 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/qortal/controller/Controller.java b/src/main/java/org/qortal/controller/Controller.java index 7e8fea5e..94ad885f 100644 --- a/src/main/java/org/qortal/controller/Controller.java +++ b/src/main/java/org/qortal/controller/Controller.java @@ -440,6 +440,18 @@ public class Controller extends Thread { } } + try (Repository repository = RepositoryManager.getRepository()) { + if (RepositoryManager.needsTransactionSequenceRebuild(repository)) { + // Don't allow the node to start if transaction sequences haven't been built yet + // This is needed to handle a case when bootstrapping + Gui.getInstance().fatalError("Database upgrade needed", "Please start the core again to complete the process."); + return; + } + } catch (DataException e) { + LOGGER.error("Error checking transaction sequences in repository", e); + return; + } + // Import current trade bot states and minting accounts if they exist Controller.importRepositoryData(); diff --git a/src/main/java/org/qortal/repository/RepositoryManager.java b/src/main/java/org/qortal/repository/RepositoryManager.java index 92936278..03147662 100644 --- a/src/main/java/org/qortal/repository/RepositoryManager.java +++ b/src/main/java/org/qortal/repository/RepositoryManager.java @@ -69,6 +69,19 @@ public abstract class RepositoryManager { // Backup is best-effort so don't complain } } + + public static boolean needsTransactionSequenceRebuild(Repository repository) throws DataException { + // Check if we have any unpopulated block_sequence values for the first 1000 blocks + List testSignatures = repository.getTransactionRepository().getSignaturesMatchingCustomCriteria( + null, Arrays.asList("block_height < 1000 AND block_sequence IS NULL"), new ArrayList<>()); + if (testSignatures.isEmpty()) { + // block_sequence already populated for the first 1000 blocks, so assume complete. + return false; + } + + return true; + } + public static boolean rebuildTransactionSequences(Repository repository) throws DataException { if (Settings.getInstance().isLite()) { // Lite nodes have no blockchain @@ -81,9 +94,7 @@ public abstract class RepositoryManager { try { // Check if we have any unpopulated block_sequence values for the first 1000 blocks - List testSignatures = repository.getTransactionRepository().getSignaturesMatchingCustomCriteria( - null, Arrays.asList("block_height < 1000 AND block_sequence IS NULL"), new ArrayList<>()); - if (testSignatures.isEmpty()) { + if (!needsTransactionSequenceRebuild(repository)) { // block_sequence already populated for the first 1000 blocks, so assume complete. // We avoid checkpointing and prevent the node from starting up in the case of a rebuild failure, so // we shouldn't ever be left in a partially rebuilt state. From 2b2d6f4e521330630048a3a9838a49742db57da6 Mon Sep 17 00:00:00 2001 From: CalDescent Date: Sun, 21 May 2023 14:02:45 +0100 Subject: [PATCH 17/35] Updated message. --- 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 94ad885f..72ee9b99 100644 --- a/src/main/java/org/qortal/controller/Controller.java +++ b/src/main/java/org/qortal/controller/Controller.java @@ -444,7 +444,7 @@ public class Controller extends Thread { if (RepositoryManager.needsTransactionSequenceRebuild(repository)) { // Don't allow the node to start if transaction sequences haven't been built yet // This is needed to handle a case when bootstrapping - Gui.getInstance().fatalError("Database upgrade needed", "Please start the core again to complete the process."); + Gui.getInstance().fatalError("Database upgrade needed", "Please restart the core to complete the upgrade process."); return; } } catch (DataException e) { From 072aa469e3cf02d7984cfa918117f37ffb1e333c Mon Sep 17 00:00:00 2001 From: CalDescent Date: Sun, 21 May 2023 15:21:04 +0100 Subject: [PATCH 18/35] Reduce default minBlockchainPeers to 3, ahead of the upcoming reshape. --- src/main/java/org/qortal/settings/Settings.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/qortal/settings/Settings.java b/src/main/java/org/qortal/settings/Settings.java index a87a72f4..564dfaf9 100644 --- a/src/main/java/org/qortal/settings/Settings.java +++ b/src/main/java/org/qortal/settings/Settings.java @@ -201,7 +201,7 @@ public class Settings { /** Whether to attempt to open the listen port via UPnP */ private boolean uPnPEnabled = true; /** Minimum number of peers to allow block minting / synchronization. */ - private int minBlockchainPeers = 5; + private int minBlockchainPeers = 3; /** Target number of outbound connections to peers we should make. */ private int minOutboundPeers = 16; /** Maximum number of peer connections we allow. */ From 648fa66f6a4ce0b8cc11ea22d95d11e76d966b3a Mon Sep 17 00:00:00 2001 From: CalDescent Date: Sun, 21 May 2023 15:22:00 +0100 Subject: [PATCH 19/35] Increased default maxPeers to 40. --- src/main/java/org/qortal/settings/Settings.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/qortal/settings/Settings.java b/src/main/java/org/qortal/settings/Settings.java index 564dfaf9..eec2aa80 100644 --- a/src/main/java/org/qortal/settings/Settings.java +++ b/src/main/java/org/qortal/settings/Settings.java @@ -205,7 +205,7 @@ public class Settings { /** Target number of outbound connections to peers we should make. */ private int minOutboundPeers = 16; /** Maximum number of peer connections we allow. */ - private int maxPeers = 36; + private int maxPeers = 40; /** Number of slots to reserve for short-lived QDN data transfers */ private int maxDataPeers = 4; /** Maximum number of threads for network engine. */ From 3c4c5a1457bd5e2a65d0c2554299869d12590e85 Mon Sep 17 00:00:00 2001 From: CalDescent Date: Sun, 21 May 2023 15:22:24 +0100 Subject: [PATCH 20/35] Default minPeerVersion set to 4.0.0 --- src/main/java/org/qortal/settings/Settings.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/qortal/settings/Settings.java b/src/main/java/org/qortal/settings/Settings.java index eec2aa80..018c1e29 100644 --- a/src/main/java/org/qortal/settings/Settings.java +++ b/src/main/java/org/qortal/settings/Settings.java @@ -219,7 +219,7 @@ public class Settings { public long recoveryModeTimeout = 10 * 60 * 1000L; /** Minimum peer version number required in order to sync with them */ - private String minPeerVersion = "3.8.7"; + private String minPeerVersion = "4.0.0"; /** 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 */ From b1a904a3c7e359bdd64b30bd282002c7866f65d2 Mon Sep 17 00:00:00 2001 From: CalDescent Date: Sun, 21 May 2023 15:26:49 +0100 Subject: [PATCH 21/35] MIN_PEER_VERSION set to 4.0.0 --- src/main/java/org/qortal/network/Handshake.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/qortal/network/Handshake.java b/src/main/java/org/qortal/network/Handshake.java index 47752767..4500cd59 100644 --- a/src/main/java/org/qortal/network/Handshake.java +++ b/src/main/java/org/qortal/network/Handshake.java @@ -265,7 +265,7 @@ public enum Handshake { private static final long PEER_VERSION_131 = 0x0100030001L; /** Minimum peer version that we are allowed to communicate with */ - private static final String MIN_PEER_VERSION = "3.8.2"; + private static final String MIN_PEER_VERSION = "4.0.0"; private static final int POW_BUFFER_SIZE_PRE_131 = 8 * 1024 * 1024; // bytes private static final int POW_DIFFICULTY_PRE_131 = 8; // leading zero bits From 3763035d4a0864b8b82c8cedb68bdb0ceaf178f9 Mon Sep 17 00:00:00 2001 From: CalDescent Date: Sun, 21 May 2023 15:34:27 +0100 Subject: [PATCH 22/35] Default recoveryModeTimeout increased to 24 hours for now. It doesn't quite work as intended, so it's best that it doesn't interfere right away. 24 hours should be long enough for any issues to be manually resolved. --- src/main/java/org/qortal/settings/Settings.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/qortal/settings/Settings.java b/src/main/java/org/qortal/settings/Settings.java index 018c1e29..2449e34a 100644 --- a/src/main/java/org/qortal/settings/Settings.java +++ b/src/main/java/org/qortal/settings/Settings.java @@ -216,7 +216,7 @@ public class Settings { private int maxRetries = 2; /** The number of seconds of no activity before recovery mode begins */ - public long recoveryModeTimeout = 10 * 60 * 1000L; + public long recoveryModeTimeout = 24 * 60 * 60 * 1000L; /** Minimum peer version number required in order to sync with them */ private String minPeerVersion = "4.0.0"; From 7a6b83aa221c27260863c605eaf98c476f483746 Mon Sep 17 00:00:00 2001 From: CalDescent Date: Sun, 21 May 2023 16:49:59 +0100 Subject: [PATCH 23/35] Bump version to 4.1.0 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 0dfa0cf4..d2232d31 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 org.qortal qortal - 4.0.3 + 4.1.0 jar true From c763445e6e70db881c72ec9a4f731ed1c80db12d Mon Sep 17 00:00:00 2001 From: CalDescent Date: Sun, 21 May 2023 19:51:22 +0100 Subject: [PATCH 24/35] Log to console if an extra core restart is needed to complete the update process (this needed ins some cases after bootstrapping). --- src/main/java/org/qortal/controller/Controller.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/org/qortal/controller/Controller.java b/src/main/java/org/qortal/controller/Controller.java index 72ee9b99..e1e90486 100644 --- a/src/main/java/org/qortal/controller/Controller.java +++ b/src/main/java/org/qortal/controller/Controller.java @@ -444,6 +444,7 @@ public class Controller extends Thread { if (RepositoryManager.needsTransactionSequenceRebuild(repository)) { // Don't allow the node to start if transaction sequences haven't been built yet // This is needed to handle a case when bootstrapping + LOGGER.error("Database upgrade needed. Please restart the core to complete the upgrade process."); Gui.getInstance().fatalError("Database upgrade needed", "Please restart the core to complete the upgrade process."); return; } From aea1cc62c812f2bc1998906a963d60191dbc14d0 Mon Sep 17 00:00:00 2001 From: CalDescent Date: Sun, 21 May 2023 20:02:58 +0100 Subject: [PATCH 25/35] Fixed off-by-one bug (correctly this time) --- src/main/java/org/qortal/repository/RepositoryManager.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/qortal/repository/RepositoryManager.java b/src/main/java/org/qortal/repository/RepositoryManager.java index 03147662..e0447ab0 100644 --- a/src/main/java/org/qortal/repository/RepositoryManager.java +++ b/src/main/java/org/qortal/repository/RepositoryManager.java @@ -108,7 +108,7 @@ public abstract class RepositoryManager { int blockchainHeight = repository.getBlockRepository().getBlockchainHeight(); int totalTransactionCount = 0; - for (int height = 1; height < blockchainHeight; ++height) { + for (int height = 1; height <= blockchainHeight; ++height) { List inputTransactions = new ArrayList<>(); // Fetch block and transactions From 95d72866e959eae45436f0ac67f5580dee052740 Mon Sep 17 00:00:00 2001 From: CalDescent Date: Sun, 21 May 2023 20:06:09 +0100 Subject: [PATCH 26/35] Use a better method to detect if a transactions table in need of a rebuild. Should handle cases where a previous rebuild didn't fully complete, or missed a block. --- src/main/java/org/qortal/repository/RepositoryManager.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/qortal/repository/RepositoryManager.java b/src/main/java/org/qortal/repository/RepositoryManager.java index e0447ab0..66156620 100644 --- a/src/main/java/org/qortal/repository/RepositoryManager.java +++ b/src/main/java/org/qortal/repository/RepositoryManager.java @@ -71,11 +71,11 @@ public abstract class RepositoryManager { } public static boolean needsTransactionSequenceRebuild(Repository repository) throws DataException { - // Check if we have any unpopulated block_sequence values for the first 1000 blocks + // Check if we have any transactions without a block_sequence List testSignatures = repository.getTransactionRepository().getSignaturesMatchingCustomCriteria( - null, Arrays.asList("block_height < 1000 AND block_sequence IS NULL"), new ArrayList<>()); + null, Arrays.asList("block_height IS NOT NULL AND block_sequence IS NULL"), new ArrayList<>()); if (testSignatures.isEmpty()) { - // block_sequence already populated for the first 1000 blocks, so assume complete. + // block_sequence intact, so assume complete return false; } From 947b523e6185eca19abae47bb3754fbbc0f11907 Mon Sep 17 00:00:00 2001 From: CalDescent Date: Sun, 21 May 2023 20:33:33 +0100 Subject: [PATCH 27/35] Limit query to 100 so that it doesn't return endless amounts of transaction signatures. Using a separate database method for now to reduce risk of interfering with other parts of the code which use it. It can be combined later when there is more testing time. --- .../qortal/repository/RepositoryManager.java | 2 +- .../repository/TransactionRepository.java | 17 +++++++ .../HSQLDBTransactionRepository.java | 47 +++++++++++++++++++ 3 files changed, 65 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/qortal/repository/RepositoryManager.java b/src/main/java/org/qortal/repository/RepositoryManager.java index 66156620..66b1d23e 100644 --- a/src/main/java/org/qortal/repository/RepositoryManager.java +++ b/src/main/java/org/qortal/repository/RepositoryManager.java @@ -73,7 +73,7 @@ public abstract class RepositoryManager { public static boolean needsTransactionSequenceRebuild(Repository repository) throws DataException { // Check if we have any transactions without a block_sequence List testSignatures = repository.getTransactionRepository().getSignaturesMatchingCustomCriteria( - null, Arrays.asList("block_height IS NOT NULL AND block_sequence IS NULL"), new ArrayList<>()); + null, Arrays.asList("block_height IS NOT NULL AND block_sequence IS NULL"), new ArrayList<>(), 100); if (testSignatures.isEmpty()) { // block_sequence intact, so assume complete return false; diff --git a/src/main/java/org/qortal/repository/TransactionRepository.java b/src/main/java/org/qortal/repository/TransactionRepository.java index e528166b..6cc88290 100644 --- a/src/main/java/org/qortal/repository/TransactionRepository.java +++ b/src/main/java/org/qortal/repository/TransactionRepository.java @@ -125,6 +125,23 @@ public interface TransactionRepository { public List getSignaturesMatchingCustomCriteria(TransactionType txType, List whereClauses, List bindParams) throws DataException; + /** + * Returns signatures for transactions that match search criteria, with optional limit. + *

+ * Alternate version that allows for custom where clauses and bind params. + * Only use for very specific use cases, such as the names integrity check. + * Not advised to be used otherwise, given that it could be possible for + * unsanitized inputs to be passed in if not careful. + * + * @param txType + * @param whereClauses + * @param bindParams + * @return + * @throws DataException + */ + public List getSignaturesMatchingCustomCriteria(TransactionType txType, List whereClauses, + List bindParams, Integer limit) throws DataException; + /** * Returns signature for latest auto-update transaction. *

diff --git a/src/main/java/org/qortal/repository/hsqldb/transaction/HSQLDBTransactionRepository.java b/src/main/java/org/qortal/repository/hsqldb/transaction/HSQLDBTransactionRepository.java index e7bab926..740b3e65 100644 --- a/src/main/java/org/qortal/repository/hsqldb/transaction/HSQLDBTransactionRepository.java +++ b/src/main/java/org/qortal/repository/hsqldb/transaction/HSQLDBTransactionRepository.java @@ -694,6 +694,53 @@ public class HSQLDBTransactionRepository implements TransactionRepository { } } + public List getSignaturesMatchingCustomCriteria(TransactionType txType, List whereClauses, + List bindParams, Integer limit) throws DataException { + List signatures = new ArrayList<>(); + + String txTypeClassName = ""; + if (txType != null) { + txTypeClassName = txType.className; + } + + StringBuilder sql = new StringBuilder(1024); + sql.append(String.format("SELECT signature FROM %sTransactions", txTypeClassName)); + + if (!whereClauses.isEmpty()) { + sql.append(" WHERE "); + + final int whereClausesSize = whereClauses.size(); + for (int wci = 0; wci < whereClausesSize; ++wci) { + if (wci != 0) + sql.append(" AND "); + + sql.append(whereClauses.get(wci)); + } + } + + if (limit != null) { + sql.append(" LIMIT ?"); + bindParams.add(limit); + } + + LOGGER.trace(() -> String.format("Transaction search SQL: %s", sql)); + + try (ResultSet resultSet = this.repository.checkedExecute(sql.toString(), bindParams.toArray())) { + if (resultSet == null) + return signatures; + + do { + byte[] signature = resultSet.getBytes(1); + + signatures.add(signature); + } while (resultSet.next()); + + return signatures; + } catch (SQLException e) { + throw new DataException("Unable to fetch matching transaction signatures from repository", e); + } + } + @Override public byte[] getLatestAutoUpdateTransaction(TransactionType txType, int txGroupId, Integer service) throws DataException { StringBuilder sql = new StringBuilder(1024); From 90f7cee058630b0850c698a015d8db4f396d1ecd Mon Sep 17 00:00:00 2001 From: CalDescent Date: Sun, 21 May 2023 20:34:04 +0100 Subject: [PATCH 28/35] Bump version to 4.1.1 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index d2232d31..288205ba 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 org.qortal qortal - 4.1.0 + 4.1.1 jar true From 0b50f965ccda066e3a00aa292943d9ce68f3aca9 Mon Sep 17 00:00:00 2001 From: CalDescent Date: Wed, 24 May 2023 19:47:10 +0100 Subject: [PATCH 29/35] Default maxNetworkThreadPoolSize set to 120 --- src/main/java/org/qortal/settings/Settings.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/qortal/settings/Settings.java b/src/main/java/org/qortal/settings/Settings.java index a3a4e2a1..926628e0 100644 --- a/src/main/java/org/qortal/settings/Settings.java +++ b/src/main/java/org/qortal/settings/Settings.java @@ -209,7 +209,7 @@ public class Settings { /** Number of slots to reserve for short-lived QDN data transfers */ private int maxDataPeers = 4; /** Maximum number of threads for network engine. */ - private int maxNetworkThreadPoolSize = 32; + private int maxNetworkThreadPoolSize = 120; /** Maximum number of threads for network proof-of-work compute, used during handshaking. */ private int networkPoWComputePoolSize = 2; /** Maximum number of retry attempts if a peer fails to respond with the requested data */ From b967800a3ee8a0eebdc0a72c6db50ae6e27594fb Mon Sep 17 00:00:00 2001 From: CalDescent Date: Wed, 24 May 2023 19:47:25 +0100 Subject: [PATCH 30/35] Default repositoryConnectionPoolSize set to 240 --- src/main/java/org/qortal/settings/Settings.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/qortal/settings/Settings.java b/src/main/java/org/qortal/settings/Settings.java index 926628e0..5fb5dd5d 100644 --- a/src/main/java/org/qortal/settings/Settings.java +++ b/src/main/java/org/qortal/settings/Settings.java @@ -267,7 +267,7 @@ public class Settings { /** Repository storage path. */ private String repositoryPath = "db"; /** Repository connection pool size. Needs to be a bit bigger than maxNetworkThreadPoolSize */ - private int repositoryConnectionPoolSize = 100; + private int repositoryConnectionPoolSize = 240; private List fixedNetwork; // Export/import From 6f0479c4fcbe853bb255c651ca4405a46886b4b3 Mon Sep 17 00:00:00 2001 From: CalDescent Date: Wed, 24 May 2023 19:47:37 +0100 Subject: [PATCH 31/35] Default minPeerVersion set to 4.1.1 --- src/main/java/org/qortal/settings/Settings.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/qortal/settings/Settings.java b/src/main/java/org/qortal/settings/Settings.java index 5fb5dd5d..3e490e09 100644 --- a/src/main/java/org/qortal/settings/Settings.java +++ b/src/main/java/org/qortal/settings/Settings.java @@ -219,7 +219,7 @@ public class Settings { public long recoveryModeTimeout = 24 * 60 * 60 * 1000L; /** Minimum peer version number required in order to sync with them */ - private String minPeerVersion = "4.0.0"; + private String minPeerVersion = "4.1.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 */ From 1f30bef4f8d15e4f85b3cf84f016f71b2d51dc2f Mon Sep 17 00:00:00 2001 From: CalDescent Date: Wed, 24 May 2023 19:54:36 +0100 Subject: [PATCH 32/35] defaultArchiveVersion set to 2 --- src/main/java/org/qortal/settings/Settings.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/qortal/settings/Settings.java b/src/main/java/org/qortal/settings/Settings.java index 3e490e09..362227a5 100644 --- a/src/main/java/org/qortal/settings/Settings.java +++ b/src/main/java/org/qortal/settings/Settings.java @@ -181,7 +181,7 @@ public class Settings { /** How often to attempt archiving (ms). */ private long archiveInterval = 7171L; // milliseconds /** Serialization version to use when building an archive */ - private int defaultArchiveVersion = 1; + private int defaultArchiveVersion = 2; /** Whether to automatically bootstrap instead of syncing from genesis */ From 1565a461ac4a0848f9c5afd923f8062eefdcf83d Mon Sep 17 00:00:00 2001 From: CalDescent Date: Wed, 24 May 2023 20:01:18 +0100 Subject: [PATCH 33/35] Bump version to 4.1.2 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 288205ba..c9986fd4 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 org.qortal qortal - 4.1.1 + 4.1.2 jar true From c8f3b6918f40040f0d0bbdc73f3a167bca7a17d2 Mon Sep 17 00:00:00 2001 From: crowetic <5431064+crowetic@users.noreply.github.com> Date: Wed, 24 May 2023 16:20:05 -0700 Subject: [PATCH 34/35] Update start.sh Added better defaults for JVM_MEMORY_ARGS and description as to how and when to uncomment the line. --- start.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/start.sh b/start.sh index b3db54fe..a7a1419f 100755 --- a/start.sh +++ b/start.sh @@ -33,7 +33,8 @@ fi # Limits Java JVM stack size and maximum heap usage. # Comment out for bigger systems, e.g. non-routers # or when API documentation is enabled -# JVM_MEMORY_ARGS="-Xss256k -Xmx128m" +# Uncomment (remove '#' sign) line below if your system has less than 12GB of RAM for optimal RAM defaults +# JVM_MEMORY_ARGS="-Xss1256k -Xmx3128m" # Although java.net.preferIPv4Stack is supposed to be false # by default in Java 11, on some platforms (e.g. FreeBSD 12), From eda6ab57014acd5d3eb3cd83a46ef976ad59e71e Mon Sep 17 00:00:00 2001 From: CalDescent Date: Fri, 26 May 2023 18:01:09 +0200 Subject: [PATCH 35/35] Fixed some failing unit tests, and ignored some failing BTC ones that have been superseded by LTC. --- .../java/org/qortal/test/BootstrapTests.java | 2 +- .../qortal/test/crosschain/BitcoinTests.java | 6 ++++++ .../org/qortal/test/crosschain/HtlcTests.java | 21 +++++++++++-------- .../qortal/test/crosschain/LitecoinTests.java | 7 +++---- 4 files changed, 22 insertions(+), 14 deletions(-) diff --git a/src/test/java/org/qortal/test/BootstrapTests.java b/src/test/java/org/qortal/test/BootstrapTests.java index b60b412c..58e1cfa2 100644 --- a/src/test/java/org/qortal/test/BootstrapTests.java +++ b/src/test/java/org/qortal/test/BootstrapTests.java @@ -212,7 +212,7 @@ public class BootstrapTests extends Common { @Test public void testBootstrapHosts() throws IOException { String[] bootstrapHosts = Settings.getInstance().getBootstrapHosts(); - String[] bootstrapTypes = { "archive", "toponly" }; + String[] bootstrapTypes = { "archive" }; // , "toponly" for (String host : bootstrapHosts) { for (String type : bootstrapTypes) { diff --git a/src/test/java/org/qortal/test/crosschain/BitcoinTests.java b/src/test/java/org/qortal/test/crosschain/BitcoinTests.java index 07a01ce2..1096d7ad 100644 --- a/src/test/java/org/qortal/test/crosschain/BitcoinTests.java +++ b/src/test/java/org/qortal/test/crosschain/BitcoinTests.java @@ -8,6 +8,7 @@ import org.bitcoinj.core.Transaction; import org.bitcoinj.store.BlockStoreException; import org.junit.After; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import org.qortal.crosschain.Bitcoin; import org.qortal.crosschain.ForeignBlockchainException; @@ -32,6 +33,7 @@ public class BitcoinTests extends Common { } @Test + @Ignore("Often fails due to unreliable BTC testnet ElectrumX servers") public void testGetMedianBlockTime() throws BlockStoreException, ForeignBlockchainException { System.out.println(String.format("Starting BTC instance...")); System.out.println(String.format("BTC instance started")); @@ -53,6 +55,7 @@ public class BitcoinTests extends Common { } @Test + @Ignore("Often fails due to unreliable BTC testnet ElectrumX servers") public void testFindHtlcSecret() throws ForeignBlockchainException { // This actually exists on TEST3 but can take a while to fetch String p2shAddress = "2N8WCg52ULCtDSMjkgVTm5mtPdCsUptkHWE"; @@ -65,6 +68,7 @@ public class BitcoinTests extends Common { } @Test + @Ignore("Often fails due to unreliable BTC testnet ElectrumX servers") public void testBuildSpend() { String xprv58 = "tprv8ZgxMBicQKsPdahhFSrCdvC1bsWyzHHZfTneTVqUXN6s1wEtZLwAkZXzFP6TYLg2aQMecZLXLre5bTVGajEB55L1HYJcawpdFG66STVAWPJ"; @@ -81,6 +85,7 @@ public class BitcoinTests extends Common { } @Test + @Ignore("Often fails due to unreliable BTC testnet ElectrumX servers") public void testGetWalletBalance() throws ForeignBlockchainException { String xprv58 = "tprv8ZgxMBicQKsPdahhFSrCdvC1bsWyzHHZfTneTVqUXN6s1wEtZLwAkZXzFP6TYLg2aQMecZLXLre5bTVGajEB55L1HYJcawpdFG66STVAWPJ"; @@ -102,6 +107,7 @@ public class BitcoinTests extends Common { } @Test + @Ignore("Often fails due to unreliable BTC testnet ElectrumX servers") public void testGetUnusedReceiveAddress() throws ForeignBlockchainException { String xprv58 = "tprv8ZgxMBicQKsPdahhFSrCdvC1bsWyzHHZfTneTVqUXN6s1wEtZLwAkZXzFP6TYLg2aQMecZLXLre5bTVGajEB55L1HYJcawpdFG66STVAWPJ"; diff --git a/src/test/java/org/qortal/test/crosschain/HtlcTests.java b/src/test/java/org/qortal/test/crosschain/HtlcTests.java index 75b290bf..3f3678f7 100644 --- a/src/test/java/org/qortal/test/crosschain/HtlcTests.java +++ b/src/test/java/org/qortal/test/crosschain/HtlcTests.java @@ -8,6 +8,7 @@ import org.junit.Ignore; import org.junit.Test; import org.qortal.crosschain.Bitcoin; import org.qortal.crosschain.ForeignBlockchainException; +import org.qortal.crosschain.Litecoin; import org.qortal.crypto.Crypto; import org.qortal.crosschain.BitcoinyHTLC; import org.qortal.repository.DataException; @@ -18,17 +19,19 @@ import com.google.common.primitives.Longs; public class HtlcTests extends Common { private Bitcoin bitcoin; + private Litecoin litecoin; @Before public void beforeTest() throws DataException { Common.useDefaultSettings(); // TestNet3 bitcoin = Bitcoin.getInstance(); + litecoin = Litecoin.getInstance(); } @After public void afterTest() { Bitcoin.resetForTesting(); - bitcoin = null; + litecoin = null; } @Test @@ -52,12 +55,12 @@ public class HtlcTests extends Common { do { // We need to perform fresh setup for 1st test Bitcoin.resetForTesting(); - bitcoin = Bitcoin.getInstance(); + litecoin = Litecoin.getInstance(); long now = System.currentTimeMillis(); long timestampBoundary = now / 30_000L; - byte[] secret1 = BitcoinyHTLC.findHtlcSecret(bitcoin, p2shAddress); + byte[] secret1 = BitcoinyHTLC.findHtlcSecret(litecoin, p2shAddress); long executionPeriod1 = System.currentTimeMillis() - now; assertNotNull(secret1); @@ -65,7 +68,7 @@ public class HtlcTests extends Common { assertTrue("1st execution period should not be instant!", executionPeriod1 > 10); - byte[] secret2 = BitcoinyHTLC.findHtlcSecret(bitcoin, p2shAddress); + byte[] secret2 = BitcoinyHTLC.findHtlcSecret(litecoin, p2shAddress); long executionPeriod2 = System.currentTimeMillis() - now - executionPeriod1; assertNotNull(secret2); @@ -86,7 +89,7 @@ public class HtlcTests extends Common { // This actually exists on TEST3 but can take a while to fetch String p2shAddress = "2N8WCg52ULCtDSMjkgVTm5mtPdCsUptkHWE"; - BitcoinyHTLC.Status htlcStatus = BitcoinyHTLC.determineHtlcStatus(bitcoin.getBlockchainProvider(), p2shAddress, 1L); + BitcoinyHTLC.Status htlcStatus = BitcoinyHTLC.determineHtlcStatus(litecoin.getBlockchainProvider(), p2shAddress, 1L); assertNotNull(htlcStatus); System.out.println(String.format("HTLC %s status: %s", p2shAddress, htlcStatus.name())); @@ -97,21 +100,21 @@ public class HtlcTests extends Common { do { // We need to perform fresh setup for 1st test Bitcoin.resetForTesting(); - bitcoin = Bitcoin.getInstance(); + litecoin = Litecoin.getInstance(); long now = System.currentTimeMillis(); long timestampBoundary = now / 30_000L; // Won't ever exist - String p2shAddress = bitcoin.deriveP2shAddress(Crypto.hash160(Longs.toByteArray(now))); + String p2shAddress = litecoin.deriveP2shAddress(Crypto.hash160(Longs.toByteArray(now))); - BitcoinyHTLC.Status htlcStatus1 = BitcoinyHTLC.determineHtlcStatus(bitcoin.getBlockchainProvider(), p2shAddress, 1L); + BitcoinyHTLC.Status htlcStatus1 = BitcoinyHTLC.determineHtlcStatus(litecoin.getBlockchainProvider(), p2shAddress, 1L); long executionPeriod1 = System.currentTimeMillis() - now; assertNotNull(htlcStatus1); assertTrue("1st execution period should not be instant!", executionPeriod1 > 10); - BitcoinyHTLC.Status htlcStatus2 = BitcoinyHTLC.determineHtlcStatus(bitcoin.getBlockchainProvider(), p2shAddress, 1L); + BitcoinyHTLC.Status htlcStatus2 = BitcoinyHTLC.determineHtlcStatus(litecoin.getBlockchainProvider(), p2shAddress, 1L); long executionPeriod2 = System.currentTimeMillis() - now - executionPeriod1; assertNotNull(htlcStatus2); diff --git a/src/test/java/org/qortal/test/crosschain/LitecoinTests.java b/src/test/java/org/qortal/test/crosschain/LitecoinTests.java index 6236483a..5ea7bc95 100644 --- a/src/test/java/org/qortal/test/crosschain/LitecoinTests.java +++ b/src/test/java/org/qortal/test/crosschain/LitecoinTests.java @@ -5,7 +5,6 @@ import static org.junit.Assert.*; import java.util.Arrays; import org.bitcoinj.core.Transaction; -import org.bitcoinj.store.BlockStoreException; import org.junit.After; import org.junit.Before; import org.junit.Ignore; @@ -33,12 +32,12 @@ public class LitecoinTests extends Common { } @Test - public void testGetMedianBlockTime() throws BlockStoreException, ForeignBlockchainException { + public void testGetMedianBlockTime() throws ForeignBlockchainException { long before = System.currentTimeMillis(); - System.out.println(String.format("Bitcoin median blocktime: %d", litecoin.getMedianBlockTime())); + System.out.println(String.format("Litecoin median blocktime: %d", litecoin.getMedianBlockTime())); long afterFirst = System.currentTimeMillis(); - System.out.println(String.format("Bitcoin median blocktime: %d", litecoin.getMedianBlockTime())); + System.out.println(String.format("Litecoin median blocktime: %d", litecoin.getMedianBlockTime())); long afterSecond = System.currentTimeMillis(); long firstPeriod = afterFirst - before;