mirror of
https://github.com/Qortal/qortal.git
synced 2025-04-23 19:37:51 +00:00
Merge branch 'master' into arbitrary-resources-cache
# Conflicts: # src/main/java/org/qortal/controller/Controller.java # src/main/java/org/qortal/repository/RepositoryManager.java # src/main/java/org/qortal/repository/hsqldb/HSQLDBDatabaseUpdates.java
This commit is contained in:
commit
74a914367f
2
pom.xml
2
pom.xml
@ -3,7 +3,7 @@
|
|||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
<groupId>org.qortal</groupId>
|
<groupId>org.qortal</groupId>
|
||||||
<artifactId>qortal</artifactId>
|
<artifactId>qortal</artifactId>
|
||||||
<version>4.0.3</version>
|
<version>4.1.2</version>
|
||||||
<packaging>jar</packaging>
|
<packaging>jar</packaging>
|
||||||
<properties>
|
<properties>
|
||||||
<skipTests>true</skipTests>
|
<skipTests>true</skipTests>
|
||||||
|
@ -223,13 +223,24 @@ public class BlocksResource {
|
|||||||
|
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
// Check if the block exists in either the database or archive
|
// Check if the block exists in either the database or archive
|
||||||
if (repository.getBlockRepository().getHeightFromSignature(signature) == 0 &&
|
int height = repository.getBlockRepository().getHeightFromSignature(signature);
|
||||||
repository.getBlockArchiveRepository().getHeightFromSignature(signature) == 0) {
|
if (height == 0) {
|
||||||
|
height = repository.getBlockArchiveRepository().getHeightFromSignature(signature);
|
||||||
|
if (height == 0) {
|
||||||
// Not found in either the database or archive
|
// Not found in either the database or archive
|
||||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.BLOCK_UNKNOWN);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.BLOCK_UNKNOWN);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return repository.getBlockRepository().getTransactionsFromSignature(signature, limit, offset, reverse);
|
List<byte[]> signatures = repository.getTransactionRepository().getSignaturesMatchingCriteria(null, null, height, height);
|
||||||
|
|
||||||
|
// Expand signatures to transactions
|
||||||
|
List<TransactionData> transactions = new ArrayList<>(signatures.size());
|
||||||
|
for (byte[] s : signatures) {
|
||||||
|
transactions.add(repository.getTransactionRepository().fromSignature(s));
|
||||||
|
}
|
||||||
|
|
||||||
|
return transactions;
|
||||||
} catch (DataException e) {
|
} catch (DataException e) {
|
||||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e);
|
||||||
}
|
}
|
||||||
|
70
src/main/java/org/qortal/api/resource/StatsResource.java
Normal file
70
src/main/java/org/qortal/api/resource/StatsResource.java
Normal file
@ -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<BlockChain.RewardByHeight> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -215,10 +215,25 @@ public class TransactionsResource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
if (repository.getBlockRepository().getHeightFromSignature(signature) == 0)
|
// 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);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.BLOCK_UNKNOWN);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return repository.getBlockRepository().getTransactionsFromSignature(signature, limit, offset, reverse);
|
List<byte[]> signatures = repository.getTransactionRepository().getSignaturesMatchingCriteria(null, null, height, height);
|
||||||
|
|
||||||
|
// Expand signatures to transactions
|
||||||
|
List<TransactionData> transactions = new ArrayList<>(signatures.size());
|
||||||
|
for (byte[] s : signatures) {
|
||||||
|
transactions.add(repository.getTransactionRepository().fromSignature(s));
|
||||||
|
}
|
||||||
|
|
||||||
|
return transactions;
|
||||||
} catch (ApiException e) {
|
} catch (ApiException e) {
|
||||||
throw e;
|
throw e;
|
||||||
} catch (DataException e) {
|
} catch (DataException e) {
|
||||||
|
@ -1686,12 +1686,14 @@ public class Block {
|
|||||||
transactionData.getSignature());
|
transactionData.getSignature());
|
||||||
this.repository.getBlockRepository().save(blockTransactionData);
|
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());
|
transactionRepository.updateBlockHeight(transactionData.getSignature(), this.blockData.getHeight());
|
||||||
|
|
||||||
// Update local transactionData's height too
|
|
||||||
transaction.getTransactionData().setBlockHeight(this.blockData.getHeight());
|
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
|
// No longer unconfirmed
|
||||||
transactionRepository.confirmTransaction(transactionData.getSignature());
|
transactionRepository.confirmTransaction(transactionData.getSignature());
|
||||||
|
|
||||||
@ -1778,6 +1780,9 @@ public class Block {
|
|||||||
|
|
||||||
// Unset height
|
// Unset height
|
||||||
transactionRepository.updateBlockHeight(transactionData.getSignature(), null);
|
transactionRepository.updateBlockHeight(transactionData.getSignature(), null);
|
||||||
|
|
||||||
|
// Unset sequence
|
||||||
|
transactionRepository.updateBlockSequence(transactionData.getSignature(), null);
|
||||||
}
|
}
|
||||||
|
|
||||||
transactionRepository.deleteParticipants(transactionData);
|
transactionRepository.deleteParticipants(transactionData);
|
||||||
|
@ -871,6 +871,9 @@ public class BlockChain {
|
|||||||
BlockData orphanBlockData = repository.getBlockRepository().fromHeight(height);
|
BlockData orphanBlockData = repository.getBlockRepository().fromHeight(height);
|
||||||
|
|
||||||
while (height > targetHeight) {
|
while (height > targetHeight) {
|
||||||
|
if (Controller.isStopping()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
LOGGER.info(String.format("Forcably orphaning block %d", height));
|
LOGGER.info(String.format("Forcably orphaning block %d", height));
|
||||||
|
|
||||||
Block block = new Block(repository, orphanBlockData);
|
Block block = new Block(repository, orphanBlockData);
|
||||||
|
@ -403,12 +403,12 @@ public class Controller extends Thread {
|
|||||||
RepositoryManager.setRequestedCheckpoint(Boolean.TRUE);
|
RepositoryManager.setRequestedCheckpoint(Boolean.TRUE);
|
||||||
|
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
|
RepositoryManager.rebuildTransactionSequences(repository);
|
||||||
ArbitraryDataCacheManager.getInstance().buildArbitraryResourcesCache(repository, false);
|
ArbitraryDataCacheManager.getInstance().buildArbitraryResourcesCache(repository, false);
|
||||||
}
|
}
|
||||||
}
|
} catch (DataException e) {
|
||||||
catch (DataException e) {
|
// If exception has no cause or message then repository is in use by some other process.
|
||||||
// If exception has no cause then repository is in use by some other process.
|
if (e.getCause() == null && e.getMessage() == null) {
|
||||||
if (e.getCause() == null) {
|
|
||||||
LOGGER.info("Repository in use by another process?");
|
LOGGER.info("Repository in use by another process?");
|
||||||
Gui.getInstance().fatalError("Repository issue", "Repository in use by another process?");
|
Gui.getInstance().fatalError("Repository issue", "Repository in use by another process?");
|
||||||
} else {
|
} else {
|
||||||
@ -442,6 +442,19 @@ 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
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
} catch (DataException e) {
|
||||||
|
LOGGER.error("Error checking transaction sequences in repository", e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Import current trade bot states and minting accounts if they exist
|
// Import current trade bot states and minting accounts if they exist
|
||||||
Controller.importRepositoryData();
|
Controller.importRepositoryData();
|
||||||
|
|
||||||
|
@ -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. */
|
* 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%
|
public static final double DELETION_THRESHOLD = 0.98f; // 98%
|
||||||
|
|
||||||
|
private static final long PER_NAME_STORAGE_MULTIPLIER = 4L;
|
||||||
|
|
||||||
public ArbitraryDataStorageManager() {
|
public ArbitraryDataStorageManager() {
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -535,7 +537,9 @@ public class ArbitraryDataStorageManager extends Thread {
|
|||||||
}
|
}
|
||||||
|
|
||||||
double maxStorageCapacity = (double)this.storageCapacity * threshold;
|
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;
|
return maxStoragePerName;
|
||||||
}
|
}
|
||||||
|
@ -79,6 +79,10 @@ public abstract class TransactionData {
|
|||||||
@Schema(accessMode = AccessMode.READ_ONLY, hidden = true, description = "height of block containing transaction")
|
@Schema(accessMode = AccessMode.READ_ONLY, hidden = true, description = "height of block containing transaction")
|
||||||
protected Integer blockHeight;
|
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
|
// Not always present
|
||||||
@Schema(accessMode = AccessMode.READ_ONLY, description = "group-approval status")
|
@Schema(accessMode = AccessMode.READ_ONLY, description = "group-approval status")
|
||||||
protected ApprovalStatus approvalStatus;
|
protected ApprovalStatus approvalStatus;
|
||||||
@ -109,6 +113,7 @@ public abstract class TransactionData {
|
|||||||
this.fee = baseTransactionData.fee;
|
this.fee = baseTransactionData.fee;
|
||||||
this.signature = baseTransactionData.signature;
|
this.signature = baseTransactionData.signature;
|
||||||
this.blockHeight = baseTransactionData.blockHeight;
|
this.blockHeight = baseTransactionData.blockHeight;
|
||||||
|
this.blockSequence = baseTransactionData.blockSequence;
|
||||||
this.approvalStatus = baseTransactionData.approvalStatus;
|
this.approvalStatus = baseTransactionData.approvalStatus;
|
||||||
this.approvalHeight = baseTransactionData.approvalHeight;
|
this.approvalHeight = baseTransactionData.approvalHeight;
|
||||||
}
|
}
|
||||||
@ -177,6 +182,15 @@ public abstract class TransactionData {
|
|||||||
this.blockHeight = blockHeight;
|
this.blockHeight = blockHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Integer getBlockSequence() {
|
||||||
|
return this.blockSequence;
|
||||||
|
}
|
||||||
|
|
||||||
|
@XmlTransient
|
||||||
|
public void setBlockSequence(Integer blockSequence) {
|
||||||
|
this.blockSequence = blockSequence;
|
||||||
|
}
|
||||||
|
|
||||||
public ApprovalStatus getApprovalStatus() {
|
public ApprovalStatus getApprovalStatus() {
|
||||||
return approvalStatus;
|
return approvalStatus;
|
||||||
}
|
}
|
||||||
|
@ -265,7 +265,7 @@ public enum Handshake {
|
|||||||
private static final long PEER_VERSION_131 = 0x0100030001L;
|
private static final long PEER_VERSION_131 = 0x0100030001L;
|
||||||
|
|
||||||
/** Minimum peer version that we are allowed to communicate with */
|
/** 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_BUFFER_SIZE_PRE_131 = 8 * 1024 * 1024; // bytes
|
||||||
private static final int POW_DIFFICULTY_PRE_131 = 8; // leading zero bits
|
private static final int POW_DIFFICULTY_PRE_131 = 8; // leading zero bits
|
||||||
|
@ -2,18 +2,23 @@ package org.qortal.repository;
|
|||||||
|
|
||||||
import org.apache.logging.log4j.LogManager;
|
import org.apache.logging.log4j.LogManager;
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
import org.qortal.api.resource.TransactionsResource;
|
import org.qortal.block.Block;
|
||||||
import org.qortal.controller.Controller;
|
import org.qortal.crypto.Crypto;
|
||||||
import org.qortal.data.arbitrary.ArbitraryResourceData;
|
import org.qortal.data.at.ATData;
|
||||||
import org.qortal.data.transaction.ArbitraryTransactionData;
|
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.gui.SplashFrame;
|
||||||
import org.qortal.settings.Settings;
|
import org.qortal.settings.Settings;
|
||||||
import org.qortal.transaction.ArbitraryTransaction;
|
|
||||||
import org.qortal.transaction.Transaction;
|
import org.qortal.transaction.Transaction;
|
||||||
|
import org.qortal.transform.block.BlockTransformation;
|
||||||
|
|
||||||
import java.sql.SQLException;
|
import java.sql.SQLException;
|
||||||
import java.util.List;
|
import java.util.*;
|
||||||
import java.util.concurrent.TimeoutException;
|
import java.util.concurrent.TimeoutException;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import static org.qortal.transaction.Transaction.TransactionType.AT;
|
||||||
|
|
||||||
public abstract class RepositoryManager {
|
public abstract class RepositoryManager {
|
||||||
private static final Logger LOGGER = LogManager.getLogger(RepositoryManager.class);
|
private static final Logger LOGGER = LogManager.getLogger(RepositoryManager.class);
|
||||||
@ -65,6 +70,164 @@ public abstract class RepositoryManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean needsTransactionSequenceRebuild(Repository repository) throws DataException {
|
||||||
|
// Check if we have any transactions without a block_sequence
|
||||||
|
List<byte[]> testSignatures = repository.getTransactionRepository().getSignaturesMatchingCustomCriteria(
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean rebuildTransactionSequences(Repository repository) throws DataException {
|
||||||
|
if (Settings.getInstance().isLite()) {
|
||||||
|
// 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
|
||||||
|
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.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
for (int height = 1; height <= blockchainHeight; ++height) {
|
||||||
|
List<TransactionData> inputTransactions = new ArrayList<>();
|
||||||
|
|
||||||
|
// Fetch block and transactions
|
||||||
|
BlockData blockData = repository.getBlockRepository().fromHeight(height);
|
||||||
|
boolean loadedFromArchive = false;
|
||||||
|
if (blockData == null) {
|
||||||
|
// Get (non-AT) transactions from the archive
|
||||||
|
BlockTransformation blockTransformation = BlockArchiveReader.getInstance().fetchBlockAtHeight(height);
|
||||||
|
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()) {
|
||||||
|
inputTransactions.add(transaction.getTransactionData());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (blockData == null) {
|
||||||
|
throw new DataException("Missing block data");
|
||||||
|
}
|
||||||
|
|
||||||
|
List<TransactionData> transactions = new ArrayList<>();
|
||||||
|
|
||||||
|
if (loadedFromArchive) {
|
||||||
|
List<TransactionData> transactionDataList = new ArrayList<>(blockData.getTransactionCount());
|
||||||
|
// Fetch any AT transactions in this block
|
||||||
|
List<byte[]> 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<ATTransactionData> atTransactions = new ArrayList<>();
|
||||||
|
for (TransactionData transactionData : transactionDataList) {
|
||||||
|
ATTransactionData atTransactionData = (ATTransactionData) transactionData;
|
||||||
|
atTransactions.add(atTransactionData);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create sorted list of ATs by creation time
|
||||||
|
List<ATData> ats = new ArrayList<>();
|
||||||
|
|
||||||
|
for (ATTransactionData atTransactionData : atTransactions) {
|
||||||
|
ATData atData = repository.getATRepository().fromATAddress(atTransactionData.getATAddress());
|
||||||
|
boolean hasExistingEntry = ats.stream().anyMatch(a -> Objects.equals(a.getATAddress(), atTransactionData.getATAddress()));
|
||||||
|
if (!hasExistingEntry) {
|
||||||
|
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<ATTransactionData> 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
|
||||||
|
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) {
|
public static void setRequestedCheckpoint(Boolean quick) {
|
||||||
quickCheckpointRequested = quick;
|
quickCheckpointRequested = quick;
|
||||||
}
|
}
|
||||||
|
@ -125,6 +125,23 @@ public interface TransactionRepository {
|
|||||||
public List<byte[]> getSignaturesMatchingCustomCriteria(TransactionType txType, List<String> whereClauses,
|
public List<byte[]> getSignaturesMatchingCustomCriteria(TransactionType txType, List<String> whereClauses,
|
||||||
List<Object> bindParams) throws DataException;
|
List<Object> bindParams) throws DataException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns signatures for transactions that match search criteria, with optional limit.
|
||||||
|
* <p>
|
||||||
|
* 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<byte[]> getSignaturesMatchingCustomCriteria(TransactionType txType, List<String> whereClauses,
|
||||||
|
List<Object> bindParams, Integer limit) throws DataException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns signature for latest auto-update transaction.
|
* Returns signature for latest auto-update transaction.
|
||||||
* <p>
|
* <p>
|
||||||
@ -309,6 +326,8 @@ public interface TransactionRepository {
|
|||||||
|
|
||||||
public void updateBlockHeight(byte[] signature, Integer height) throws DataException;
|
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;
|
public void updateApprovalHeight(byte[] signature, Integer approvalHeight) throws DataException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -296,10 +296,9 @@ public class HSQLDBATRepository implements ATRepository {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Integer getATCreationBlockHeight(String atAddress) throws DataException {
|
public Integer getATCreationBlockHeight(String atAddress) throws DataException {
|
||||||
String sql = "SELECT height "
|
String sql = "SELECT block_height "
|
||||||
+ "FROM DeployATTransactions "
|
+ "FROM DeployATTransactions "
|
||||||
+ "JOIN BlockTransactions ON transaction_signature = signature "
|
+ "JOIN Transactions USING (signature) "
|
||||||
+ "JOIN Blocks ON Blocks.signature = block_signature "
|
|
||||||
+ "WHERE AT_address = ? "
|
+ "WHERE AT_address = ? "
|
||||||
+ "LIMIT 1";
|
+ "LIMIT 1";
|
||||||
|
|
||||||
@ -877,18 +876,17 @@ public class HSQLDBATRepository implements ATRepository {
|
|||||||
public NextTransactionInfo findNextTransaction(String recipient, int height, int sequence) throws DataException {
|
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
|
// 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 ("
|
+ "FROM ("
|
||||||
+ "SELECT signature FROM PaymentTransactions WHERE recipient = ? "
|
+ "SELECT signature FROM PaymentTransactions WHERE recipient = ? "
|
||||||
+ "UNION "
|
+ "UNION "
|
||||||
+ "SELECT signature FROM MessageTransactions WHERE recipient = ? "
|
+ "SELECT signature FROM MessageTransactions WHERE recipient = ? "
|
||||||
+ "UNION "
|
+ "UNION "
|
||||||
+ "SELECT signature FROM ATTransactions WHERE recipient = ?"
|
+ "SELECT signature FROM ATTransactions WHERE recipient = ?"
|
||||||
+ ") AS Transactions "
|
+ ") AS SelectedTransactions "
|
||||||
+ "JOIN BlockTransactions ON BlockTransactions.transaction_signature = Transactions.signature "
|
+ "JOIN Transactions USING (signature)"
|
||||||
+ "JOIN Blocks ON Blocks.signature = BlockTransactions.block_signature "
|
+ "WHERE (block_height > ? OR (block_height = ? AND block_sequence > ?)) "
|
||||||
+ "WHERE (height > ? OR (height = ? AND sequence > ?)) "
|
+ "ORDER BY block_height ASC, block_sequence ASC "
|
||||||
+ "ORDER BY height ASC, sequence ASC "
|
|
||||||
+ "LIMIT 1";
|
+ "LIMIT 1";
|
||||||
|
|
||||||
Object[] bindParams = new Object[] { recipient, recipient, recipient, height, height, sequence };
|
Object[] bindParams = new Object[] { recipient, recipient, recipient, height, height, sequence };
|
||||||
|
@ -994,6 +994,17 @@ public class HSQLDBDatabaseUpdates {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case 47:
|
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;
|
||||||
|
|
||||||
|
case 48:
|
||||||
// We need to keep a local cache of arbitrary resources (items published to QDN), for easier searching.
|
// We need to keep a local cache of arbitrary resources (items published to QDN), for easier searching.
|
||||||
// IMPORTANT: this is a cache of the last known state of a resource (both confirmed
|
// IMPORTANT: this is a cache of the last known state of a resource (both confirmed
|
||||||
// and valid unconfirmed). It cannot be assumed that all nodes will contain the same state at a
|
// and valid unconfirmed). It cannot be assumed that all nodes will contain the same state at a
|
||||||
|
@ -194,8 +194,7 @@ public class HSQLDBTransactionRepository implements TransactionRepository {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public TransactionData fromHeightAndSequence(int height, int sequence) throws DataException {
|
public TransactionData fromHeightAndSequence(int height, int sequence) throws DataException {
|
||||||
String sql = "SELECT transaction_signature FROM BlockTransactions JOIN Blocks ON signature = block_signature "
|
String sql = "SELECT signature FROM Transactions WHERE block_height = ? AND block_sequence = ?";
|
||||||
+ "WHERE height = ? AND sequence = ?";
|
|
||||||
|
|
||||||
try (ResultSet resultSet = this.repository.checkedExecute(sql, height, sequence)) {
|
try (ResultSet resultSet = this.repository.checkedExecute(sql, height, sequence)) {
|
||||||
if (resultSet == null)
|
if (resultSet == null)
|
||||||
@ -657,8 +656,13 @@ public class HSQLDBTransactionRepository implements TransactionRepository {
|
|||||||
List<Object> bindParams) throws DataException {
|
List<Object> bindParams) throws DataException {
|
||||||
List<byte[]> signatures = new ArrayList<>();
|
List<byte[]> signatures = new ArrayList<>();
|
||||||
|
|
||||||
|
String txTypeClassName = "";
|
||||||
|
if (txType != null) {
|
||||||
|
txTypeClassName = txType.className;
|
||||||
|
}
|
||||||
|
|
||||||
StringBuilder sql = new StringBuilder(1024);
|
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()) {
|
if (!whereClauses.isEmpty()) {
|
||||||
sql.append(" WHERE ");
|
sql.append(" WHERE ");
|
||||||
@ -690,6 +694,53 @@ public class HSQLDBTransactionRepository implements TransactionRepository {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<byte[]> getSignaturesMatchingCustomCriteria(TransactionType txType, List<String> whereClauses,
|
||||||
|
List<Object> bindParams, Integer limit) throws DataException {
|
||||||
|
List<byte[]> 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
|
@Override
|
||||||
public byte[] getLatestAutoUpdateTransaction(TransactionType txType, int txGroupId, Integer service) throws DataException {
|
public byte[] getLatestAutoUpdateTransaction(TransactionType txType, int txGroupId, Integer service) throws DataException {
|
||||||
StringBuilder sql = new StringBuilder(1024);
|
StringBuilder sql = new StringBuilder(1024);
|
||||||
@ -1444,6 +1495,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
|
@Override
|
||||||
public void updateApprovalHeight(byte[] signature, Integer approvalHeight) throws DataException {
|
public void updateApprovalHeight(byte[] signature, Integer approvalHeight) throws DataException {
|
||||||
HSQLDBSaver saver = new HSQLDBSaver("Transactions");
|
HSQLDBSaver saver = new HSQLDBSaver("Transactions");
|
||||||
|
@ -181,7 +181,7 @@ public class Settings {
|
|||||||
/** How often to attempt archiving (ms). */
|
/** How often to attempt archiving (ms). */
|
||||||
private long archiveInterval = 7171L; // milliseconds
|
private long archiveInterval = 7171L; // milliseconds
|
||||||
/** Serialization version to use when building an archive */
|
/** 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 */
|
/** Whether to automatically bootstrap instead of syncing from genesis */
|
||||||
@ -201,25 +201,25 @@ public class Settings {
|
|||||||
/** Whether to attempt to open the listen port via UPnP */
|
/** Whether to attempt to open the listen port via UPnP */
|
||||||
private boolean uPnPEnabled = true;
|
private boolean uPnPEnabled = true;
|
||||||
/** Minimum number of peers to allow block minting / synchronization. */
|
/** 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. */
|
/** Target number of outbound connections to peers we should make. */
|
||||||
private int minOutboundPeers = 16;
|
private int minOutboundPeers = 16;
|
||||||
/** Maximum number of peer connections we allow. */
|
/** 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 */
|
/** Number of slots to reserve for short-lived QDN data transfers */
|
||||||
private int maxDataPeers = 4;
|
private int maxDataPeers = 4;
|
||||||
/** Maximum number of threads for network engine. */
|
/** 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. */
|
/** Maximum number of threads for network proof-of-work compute, used during handshaking. */
|
||||||
private int networkPoWComputePoolSize = 2;
|
private int networkPoWComputePoolSize = 2;
|
||||||
/** Maximum number of retry attempts if a peer fails to respond with the requested data */
|
/** Maximum number of retry attempts if a peer fails to respond with the requested data */
|
||||||
private int maxRetries = 2;
|
private int maxRetries = 2;
|
||||||
|
|
||||||
/** The number of seconds of no activity before recovery mode begins */
|
/** 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 */
|
/** Minimum peer version number required in order to sync with them */
|
||||||
private String minPeerVersion = "3.8.7";
|
private String minPeerVersion = "4.1.1";
|
||||||
/** Whether to allow connections with peers below minPeerVersion
|
/** 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 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 */
|
* If false, sync will be blocked both ways, and they will not appear in the peers list */
|
||||||
@ -267,7 +267,7 @@ public class Settings {
|
|||||||
/** Repository storage path. */
|
/** Repository storage path. */
|
||||||
private String repositoryPath = "db";
|
private String repositoryPath = "db";
|
||||||
/** Repository connection pool size. Needs to be a bit bigger than maxNetworkThreadPoolSize */
|
/** Repository connection pool size. Needs to be a bit bigger than maxNetworkThreadPoolSize */
|
||||||
private int repositoryConnectionPoolSize = 100;
|
private int repositoryConnectionPoolSize = 240;
|
||||||
private List<String> fixedNetwork;
|
private List<String> fixedNetwork;
|
||||||
|
|
||||||
// Export/import
|
// Export/import
|
||||||
@ -508,6 +508,9 @@ public class Settings {
|
|||||||
if (this.minBlockchainPeers < 1 && !singleNodeTestnet)
|
if (this.minBlockchainPeers < 1 && !singleNodeTestnet)
|
||||||
throwValidationError("minBlockchainPeers must be at least 1");
|
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)
|
if (this.apiKey != null && this.apiKey.trim().length() < 8)
|
||||||
throwValidationError("apiKey must be at least 8 characters");
|
throwValidationError("apiKey must be at least 8 characters");
|
||||||
|
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package org.qortal.utils;
|
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.at.ATStateData;
|
||||||
import org.qortal.data.block.BlockData;
|
import org.qortal.data.block.BlockData;
|
||||||
import org.qortal.data.transaction.TransactionData;
|
import org.qortal.data.transaction.TransactionData;
|
||||||
@ -12,6 +14,8 @@ import java.util.List;
|
|||||||
|
|
||||||
public class BlockArchiveUtils {
|
public class BlockArchiveUtils {
|
||||||
|
|
||||||
|
private static final Logger LOGGER = LogManager.getLogger(BlockArchiveUtils.class);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* importFromArchive
|
* importFromArchive
|
||||||
* <p>
|
* <p>
|
||||||
@ -87,7 +91,8 @@ public class BlockArchiveUtils {
|
|||||||
|
|
||||||
} catch (DataException e) {
|
} catch (DataException e) {
|
||||||
repository.discardChanges();
|
repository.discardChanges();
|
||||||
throw new IllegalStateException("Unable to import blocks from archive");
|
LOGGER.info("Unable to import blocks from archive", e);
|
||||||
|
throw(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
repository.saveChanges();
|
repository.saveChanges();
|
||||||
|
@ -212,7 +212,7 @@ public class BootstrapTests extends Common {
|
|||||||
@Test
|
@Test
|
||||||
public void testBootstrapHosts() throws IOException {
|
public void testBootstrapHosts() throws IOException {
|
||||||
String[] bootstrapHosts = Settings.getInstance().getBootstrapHosts();
|
String[] bootstrapHosts = Settings.getInstance().getBootstrapHosts();
|
||||||
String[] bootstrapTypes = { "archive", "toponly" };
|
String[] bootstrapTypes = { "archive" }; // , "toponly"
|
||||||
|
|
||||||
for (String host : bootstrapHosts) {
|
for (String host : bootstrapHosts) {
|
||||||
for (String type : bootstrapTypes) {
|
for (String type : bootstrapTypes) {
|
||||||
|
@ -113,13 +113,16 @@ public class ArbitraryDataStorageCapacityTests extends Common {
|
|||||||
assertTrue(resourceListManager.addToList("followedNames", "Test2", false));
|
assertTrue(resourceListManager.addToList("followedNames", "Test2", false));
|
||||||
assertTrue(resourceListManager.addToList("followedNames", "Test3", false));
|
assertTrue(resourceListManager.addToList("followedNames", "Test3", false));
|
||||||
assertTrue(resourceListManager.addToList("followedNames", "Test4", 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
|
// Ensure the followed name count is correct
|
||||||
assertEquals(4, resourceListManager.getItemCountForList("followedNames"));
|
assertEquals(6, resourceListManager.getItemCountForList("followedNames"));
|
||||||
assertEquals(4, ListUtils.followedNamesCount());
|
assertEquals(6, ListUtils.followedNamesCount());
|
||||||
|
|
||||||
// Storage space per name should be the total storage capacity divided by the number of names
|
// 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));
|
assertEquals(expectedStorageCapacityPerName, storageManager.storageCapacityPerName(storageFullThreshold));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,6 +8,7 @@ import org.bitcoinj.core.Transaction;
|
|||||||
import org.bitcoinj.store.BlockStoreException;
|
import org.bitcoinj.store.BlockStoreException;
|
||||||
import org.junit.After;
|
import org.junit.After;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
|
import org.junit.Ignore;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.qortal.crosschain.Bitcoin;
|
import org.qortal.crosschain.Bitcoin;
|
||||||
import org.qortal.crosschain.ForeignBlockchainException;
|
import org.qortal.crosschain.ForeignBlockchainException;
|
||||||
@ -32,6 +33,7 @@ public class BitcoinTests extends Common {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@Ignore("Often fails due to unreliable BTC testnet ElectrumX servers")
|
||||||
public void testGetMedianBlockTime() throws BlockStoreException, ForeignBlockchainException {
|
public void testGetMedianBlockTime() throws BlockStoreException, ForeignBlockchainException {
|
||||||
System.out.println(String.format("Starting BTC instance..."));
|
System.out.println(String.format("Starting BTC instance..."));
|
||||||
System.out.println(String.format("BTC instance started"));
|
System.out.println(String.format("BTC instance started"));
|
||||||
@ -53,6 +55,7 @@ public class BitcoinTests extends Common {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@Ignore("Often fails due to unreliable BTC testnet ElectrumX servers")
|
||||||
public void testFindHtlcSecret() throws ForeignBlockchainException {
|
public void testFindHtlcSecret() throws ForeignBlockchainException {
|
||||||
// This actually exists on TEST3 but can take a while to fetch
|
// This actually exists on TEST3 but can take a while to fetch
|
||||||
String p2shAddress = "2N8WCg52ULCtDSMjkgVTm5mtPdCsUptkHWE";
|
String p2shAddress = "2N8WCg52ULCtDSMjkgVTm5mtPdCsUptkHWE";
|
||||||
@ -65,6 +68,7 @@ public class BitcoinTests extends Common {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@Ignore("Often fails due to unreliable BTC testnet ElectrumX servers")
|
||||||
public void testBuildSpend() {
|
public void testBuildSpend() {
|
||||||
String xprv58 = "tprv8ZgxMBicQKsPdahhFSrCdvC1bsWyzHHZfTneTVqUXN6s1wEtZLwAkZXzFP6TYLg2aQMecZLXLre5bTVGajEB55L1HYJcawpdFG66STVAWPJ";
|
String xprv58 = "tprv8ZgxMBicQKsPdahhFSrCdvC1bsWyzHHZfTneTVqUXN6s1wEtZLwAkZXzFP6TYLg2aQMecZLXLre5bTVGajEB55L1HYJcawpdFG66STVAWPJ";
|
||||||
|
|
||||||
@ -81,6 +85,7 @@ public class BitcoinTests extends Common {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@Ignore("Often fails due to unreliable BTC testnet ElectrumX servers")
|
||||||
public void testGetWalletBalance() throws ForeignBlockchainException {
|
public void testGetWalletBalance() throws ForeignBlockchainException {
|
||||||
String xprv58 = "tprv8ZgxMBicQKsPdahhFSrCdvC1bsWyzHHZfTneTVqUXN6s1wEtZLwAkZXzFP6TYLg2aQMecZLXLre5bTVGajEB55L1HYJcawpdFG66STVAWPJ";
|
String xprv58 = "tprv8ZgxMBicQKsPdahhFSrCdvC1bsWyzHHZfTneTVqUXN6s1wEtZLwAkZXzFP6TYLg2aQMecZLXLre5bTVGajEB55L1HYJcawpdFG66STVAWPJ";
|
||||||
|
|
||||||
@ -102,6 +107,7 @@ public class BitcoinTests extends Common {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@Ignore("Often fails due to unreliable BTC testnet ElectrumX servers")
|
||||||
public void testGetUnusedReceiveAddress() throws ForeignBlockchainException {
|
public void testGetUnusedReceiveAddress() throws ForeignBlockchainException {
|
||||||
String xprv58 = "tprv8ZgxMBicQKsPdahhFSrCdvC1bsWyzHHZfTneTVqUXN6s1wEtZLwAkZXzFP6TYLg2aQMecZLXLre5bTVGajEB55L1HYJcawpdFG66STVAWPJ";
|
String xprv58 = "tprv8ZgxMBicQKsPdahhFSrCdvC1bsWyzHHZfTneTVqUXN6s1wEtZLwAkZXzFP6TYLg2aQMecZLXLre5bTVGajEB55L1HYJcawpdFG66STVAWPJ";
|
||||||
|
|
||||||
|
@ -8,6 +8,7 @@ import org.junit.Ignore;
|
|||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.qortal.crosschain.Bitcoin;
|
import org.qortal.crosschain.Bitcoin;
|
||||||
import org.qortal.crosschain.ForeignBlockchainException;
|
import org.qortal.crosschain.ForeignBlockchainException;
|
||||||
|
import org.qortal.crosschain.Litecoin;
|
||||||
import org.qortal.crypto.Crypto;
|
import org.qortal.crypto.Crypto;
|
||||||
import org.qortal.crosschain.BitcoinyHTLC;
|
import org.qortal.crosschain.BitcoinyHTLC;
|
||||||
import org.qortal.repository.DataException;
|
import org.qortal.repository.DataException;
|
||||||
@ -18,17 +19,19 @@ import com.google.common.primitives.Longs;
|
|||||||
public class HtlcTests extends Common {
|
public class HtlcTests extends Common {
|
||||||
|
|
||||||
private Bitcoin bitcoin;
|
private Bitcoin bitcoin;
|
||||||
|
private Litecoin litecoin;
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void beforeTest() throws DataException {
|
public void beforeTest() throws DataException {
|
||||||
Common.useDefaultSettings(); // TestNet3
|
Common.useDefaultSettings(); // TestNet3
|
||||||
bitcoin = Bitcoin.getInstance();
|
bitcoin = Bitcoin.getInstance();
|
||||||
|
litecoin = Litecoin.getInstance();
|
||||||
}
|
}
|
||||||
|
|
||||||
@After
|
@After
|
||||||
public void afterTest() {
|
public void afterTest() {
|
||||||
Bitcoin.resetForTesting();
|
Bitcoin.resetForTesting();
|
||||||
bitcoin = null;
|
litecoin = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -52,12 +55,12 @@ public class HtlcTests extends Common {
|
|||||||
do {
|
do {
|
||||||
// We need to perform fresh setup for 1st test
|
// We need to perform fresh setup for 1st test
|
||||||
Bitcoin.resetForTesting();
|
Bitcoin.resetForTesting();
|
||||||
bitcoin = Bitcoin.getInstance();
|
litecoin = Litecoin.getInstance();
|
||||||
|
|
||||||
long now = System.currentTimeMillis();
|
long now = System.currentTimeMillis();
|
||||||
long timestampBoundary = now / 30_000L;
|
long timestampBoundary = now / 30_000L;
|
||||||
|
|
||||||
byte[] secret1 = BitcoinyHTLC.findHtlcSecret(bitcoin, p2shAddress);
|
byte[] secret1 = BitcoinyHTLC.findHtlcSecret(litecoin, p2shAddress);
|
||||||
long executionPeriod1 = System.currentTimeMillis() - now;
|
long executionPeriod1 = System.currentTimeMillis() - now;
|
||||||
|
|
||||||
assertNotNull(secret1);
|
assertNotNull(secret1);
|
||||||
@ -65,7 +68,7 @@ public class HtlcTests extends Common {
|
|||||||
|
|
||||||
assertTrue("1st execution period should not be instant!", executionPeriod1 > 10);
|
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;
|
long executionPeriod2 = System.currentTimeMillis() - now - executionPeriod1;
|
||||||
|
|
||||||
assertNotNull(secret2);
|
assertNotNull(secret2);
|
||||||
@ -86,7 +89,7 @@ public class HtlcTests extends Common {
|
|||||||
// This actually exists on TEST3 but can take a while to fetch
|
// This actually exists on TEST3 but can take a while to fetch
|
||||||
String p2shAddress = "2N8WCg52ULCtDSMjkgVTm5mtPdCsUptkHWE";
|
String p2shAddress = "2N8WCg52ULCtDSMjkgVTm5mtPdCsUptkHWE";
|
||||||
|
|
||||||
BitcoinyHTLC.Status htlcStatus = BitcoinyHTLC.determineHtlcStatus(bitcoin.getBlockchainProvider(), p2shAddress, 1L);
|
BitcoinyHTLC.Status htlcStatus = BitcoinyHTLC.determineHtlcStatus(litecoin.getBlockchainProvider(), p2shAddress, 1L);
|
||||||
assertNotNull(htlcStatus);
|
assertNotNull(htlcStatus);
|
||||||
|
|
||||||
System.out.println(String.format("HTLC %s status: %s", p2shAddress, htlcStatus.name()));
|
System.out.println(String.format("HTLC %s status: %s", p2shAddress, htlcStatus.name()));
|
||||||
@ -97,21 +100,21 @@ public class HtlcTests extends Common {
|
|||||||
do {
|
do {
|
||||||
// We need to perform fresh setup for 1st test
|
// We need to perform fresh setup for 1st test
|
||||||
Bitcoin.resetForTesting();
|
Bitcoin.resetForTesting();
|
||||||
bitcoin = Bitcoin.getInstance();
|
litecoin = Litecoin.getInstance();
|
||||||
|
|
||||||
long now = System.currentTimeMillis();
|
long now = System.currentTimeMillis();
|
||||||
long timestampBoundary = now / 30_000L;
|
long timestampBoundary = now / 30_000L;
|
||||||
|
|
||||||
// Won't ever exist
|
// 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;
|
long executionPeriod1 = System.currentTimeMillis() - now;
|
||||||
|
|
||||||
assertNotNull(htlcStatus1);
|
assertNotNull(htlcStatus1);
|
||||||
assertTrue("1st execution period should not be instant!", executionPeriod1 > 10);
|
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;
|
long executionPeriod2 = System.currentTimeMillis() - now - executionPeriod1;
|
||||||
|
|
||||||
assertNotNull(htlcStatus2);
|
assertNotNull(htlcStatus2);
|
||||||
|
@ -5,7 +5,6 @@ import static org.junit.Assert.*;
|
|||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
|
||||||
import org.bitcoinj.core.Transaction;
|
import org.bitcoinj.core.Transaction;
|
||||||
import org.bitcoinj.store.BlockStoreException;
|
|
||||||
import org.junit.After;
|
import org.junit.After;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Ignore;
|
import org.junit.Ignore;
|
||||||
@ -33,12 +32,12 @@ public class LitecoinTests extends Common {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testGetMedianBlockTime() throws BlockStoreException, ForeignBlockchainException {
|
public void testGetMedianBlockTime() throws ForeignBlockchainException {
|
||||||
long before = System.currentTimeMillis();
|
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();
|
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 afterSecond = System.currentTimeMillis();
|
||||||
|
|
||||||
long firstPeriod = afterFirst - before;
|
long firstPeriod = afterFirst - before;
|
||||||
|
3
start.sh
3
start.sh
@ -33,7 +33,8 @@ fi
|
|||||||
# Limits Java JVM stack size and maximum heap usage.
|
# Limits Java JVM stack size and maximum heap usage.
|
||||||
# Comment out for bigger systems, e.g. non-routers
|
# Comment out for bigger systems, e.g. non-routers
|
||||||
# or when API documentation is enabled
|
# 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
|
# Although java.net.preferIPv4Stack is supposed to be false
|
||||||
# by default in Java 11, on some platforms (e.g. FreeBSD 12),
|
# by default in Java 11, on some platforms (e.g. FreeBSD 12),
|
||||||
|
Loading…
x
Reference in New Issue
Block a user