forked from Qortal/qortal
Merge branch 'master' into q-apps
This commit is contained in:
commit
03a54691a1
@ -17,10 +17,10 @@
|
||||
<ROW Property="Manufacturer" Value="Qortal"/>
|
||||
<ROW Property="MsiLogging" MultiBuildValue="DefaultBuild:vp"/>
|
||||
<ROW Property="NTP_GOOD" Value="false"/>
|
||||
<ROW Property="ProductCode" Value="1033:{ADE0C9E9-F7D9-4829-8626-8571C735C4D7} 1049:{F5230C0A-9D8C-4C70-AC72-17CECC8273B8} 2052:{D5A0760C-E5B3-4C4C-97B0-81CC445F07B9} 2057:{EF5EF0BE-0B00-4F5C-A2A0-DF2CB82FF20D} " Type="16"/>
|
||||
<ROW Property="ProductCode" Value="1033:{6C93A96C-E3AF-42FD-BE11-7EC3734905C6} 1049:{754F5347-82E5-4251-AED0-F4141CDD11F5} 2052:{413BD7B3-A3F8-47D0-BCA4-5C7694A40936} 2057:{71450AC8-1E6F-4469-852D-0591FA693680} " Type="16"/>
|
||||
<ROW Property="ProductLanguage" Value="2057"/>
|
||||
<ROW Property="ProductName" Value="Qortal"/>
|
||||
<ROW Property="ProductVersion" Value="3.6.3" Type="32"/>
|
||||
<ROW Property="ProductVersion" Value="3.8.3" Type="32"/>
|
||||
<ROW Property="RECONFIG_NTP" Value="true"/>
|
||||
<ROW Property="REMOVE_BLOCKCHAIN" Value="YES" Type="4"/>
|
||||
<ROW Property="REPAIR_BLOCKCHAIN" Value="YES" Type="4"/>
|
||||
@ -212,7 +212,7 @@
|
||||
<ROW Component="ADDITIONAL_LICENSE_INFO_71" ComponentId="{12A3ADBE-BB7A-496C-8869-410681E6232F}" Directory_="jdk.zipfs_Dir" Attributes="0" KeyPath="ADDITIONAL_LICENSE_INFO_71" Type="0"/>
|
||||
<ROW Component="ADDITIONAL_LICENSE_INFO_8" ComponentId="{D53AD95E-CF96-4999-80FC-5812277A7456}" Directory_="java.naming_Dir" Attributes="0" KeyPath="ADDITIONAL_LICENSE_INFO_8" Type="0"/>
|
||||
<ROW Component="ADDITIONAL_LICENSE_INFO_9" ComponentId="{6B7EA9B0-5D17-47A8-B78C-FACE86D15E01}" Directory_="java.net.http_Dir" Attributes="0" KeyPath="ADDITIONAL_LICENSE_INFO_9" Type="0"/>
|
||||
<ROW Component="AI_CustomARPName" ComponentId="{F4F774B9-18DC-4740-9552-EA16B98801C9}" Directory_="APPDIR" Attributes="260" KeyPath="DisplayName" Options="1"/>
|
||||
<ROW Component="AI_CustomARPName" ComponentId="{EC7B4AD9-F2D9-48C4-A586-C4697D9C380C}" Directory_="APPDIR" Attributes="260" KeyPath="DisplayName" Options="1"/>
|
||||
<ROW Component="AI_ExePath" ComponentId="{3644948D-AE0B-41BB-9FAF-A79E70490A08}" Directory_="APPDIR" Attributes="260" KeyPath="AI_ExePath"/>
|
||||
<ROW Component="APPDIR" ComponentId="{680DFDDE-3FB4-47A5-8FF5-934F576C6F91}" Directory_="APPDIR" Attributes="0"/>
|
||||
<ROW Component="AccessBridgeCallbacks.h" ComponentId="{288055D1-1062-47A3-AA44-5601B4E38AED}" Directory_="bridge_Dir" Attributes="0" KeyPath="AccessBridgeCallbacks.h" Type="0"/>
|
||||
|
2
pom.xml
2
pom.xml
@ -3,7 +3,7 @@
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>org.qortal</groupId>
|
||||
<artifactId>qortal</artifactId>
|
||||
<version>3.8.2</version>
|
||||
<version>3.8.4</version>
|
||||
<packaging>jar</packaging>
|
||||
<properties>
|
||||
<skipTests>true</skipTests>
|
||||
|
@ -1,6 +1,7 @@
|
||||
package org.qortal.api.model;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import org.qortal.controller.Controller;
|
||||
import org.qortal.data.block.BlockSummaryData;
|
||||
import org.qortal.data.network.PeerData;
|
||||
import org.qortal.network.Handshake;
|
||||
@ -36,6 +37,7 @@ public class ConnectedPeer {
|
||||
public Long lastBlockTimestamp;
|
||||
public UUID connectionId;
|
||||
public String age;
|
||||
public Boolean isTooDivergent;
|
||||
|
||||
protected ConnectedPeer() {
|
||||
}
|
||||
@ -69,6 +71,11 @@ public class ConnectedPeer {
|
||||
this.lastBlockSignature = peerChainTipData.getSignature();
|
||||
this.lastBlockTimestamp = peerChainTipData.getTimestamp();
|
||||
}
|
||||
|
||||
// Only include isTooDivergent decision if we've had the opportunity to request block summaries this peer
|
||||
if (peer.getLastTooDivergentTime() != null) {
|
||||
this.isTooDivergent = Controller.wasRecentlyTooDivergent.test(peer);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -634,13 +634,16 @@ public class BlocksResource {
|
||||
@ApiErrors({
|
||||
ApiError.REPOSITORY_ISSUE
|
||||
})
|
||||
public List<BlockData> getBlockRange(@PathParam("height") int height, @Parameter(
|
||||
ref = "count"
|
||||
) @QueryParam("count") int count) {
|
||||
public List<BlockData> getBlockRange(@PathParam("height") int height,
|
||||
@Parameter(ref = "count") @QueryParam("count") int count,
|
||||
@Parameter(ref = "reverse") @QueryParam("reverse") Boolean reverse,
|
||||
@QueryParam("includeOnlineSignatures") Boolean includeOnlineSignatures) {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
List<BlockData> blocks = new ArrayList<>();
|
||||
boolean shouldReverse = (reverse != null && reverse == true);
|
||||
|
||||
for (/* count already set */; count > 0; --count, ++height) {
|
||||
int i = 0;
|
||||
while (i < count) {
|
||||
BlockData blockData = repository.getBlockRepository().fromHeight(height);
|
||||
if (blockData == null) {
|
||||
// Not found - try the archive
|
||||
@ -650,8 +653,14 @@ public class BlocksResource {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (includeOnlineSignatures == null || includeOnlineSignatures == false) {
|
||||
blockData.setOnlineAccountsSignatures(null);
|
||||
}
|
||||
|
||||
blocks.add(blockData);
|
||||
|
||||
height = shouldReverse ? height - 1 : height + 1;
|
||||
i++;
|
||||
}
|
||||
|
||||
return blocks;
|
||||
|
@ -68,7 +68,7 @@ public class CrossChainBitcoinResource {
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_PRIVATE_KEY);
|
||||
|
||||
try {
|
||||
Long balance = bitcoin.getWalletBalanceFromTransactions(key58);
|
||||
Long balance = bitcoin.getWalletBalance(key58);
|
||||
if (balance == null)
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.FOREIGN_BLOCKCHAIN_NETWORK_ISSUE);
|
||||
|
||||
|
@ -68,7 +68,7 @@ public class CrossChainDigibyteResource {
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_PRIVATE_KEY);
|
||||
|
||||
try {
|
||||
Long balance = digibyte.getWalletBalanceFromTransactions(key58);
|
||||
Long balance = digibyte.getWalletBalance(key58);
|
||||
if (balance == null)
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.FOREIGN_BLOCKCHAIN_NETWORK_ISSUE);
|
||||
|
||||
|
@ -66,7 +66,7 @@ public class CrossChainDogecoinResource {
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_PRIVATE_KEY);
|
||||
|
||||
try {
|
||||
Long balance = dogecoin.getWalletBalanceFromTransactions(key58);
|
||||
Long balance = dogecoin.getWalletBalance(key58);
|
||||
if (balance == null)
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.FOREIGN_BLOCKCHAIN_NETWORK_ISSUE);
|
||||
|
||||
|
@ -68,7 +68,7 @@ public class CrossChainLitecoinResource {
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_PRIVATE_KEY);
|
||||
|
||||
try {
|
||||
Long balance = litecoin.getWalletBalanceFromTransactions(key58);
|
||||
Long balance = litecoin.getWalletBalance(key58);
|
||||
if (balance == null)
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.FOREIGN_BLOCKCHAIN_NETWORK_ISSUE);
|
||||
|
||||
|
@ -68,7 +68,7 @@ public class CrossChainRavencoinResource {
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_PRIVATE_KEY);
|
||||
|
||||
try {
|
||||
Long balance = ravencoin.getWalletBalanceFromTransactions(key58);
|
||||
Long balance = ravencoin.getWalletBalance(key58);
|
||||
if (balance == null)
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.FOREIGN_BLOCKCHAIN_NETWORK_ISSUE);
|
||||
|
||||
|
@ -2,6 +2,7 @@ package org.qortal.arbitrary;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.qortal.arbitrary.exception.DataNotPublishedException;
|
||||
import org.qortal.arbitrary.exception.MissingDataException;
|
||||
import org.qortal.arbitrary.metadata.ArbitraryDataMetadataCache;
|
||||
import org.qortal.arbitrary.misc.Service;
|
||||
@ -88,7 +89,7 @@ public class ArbitraryDataBuilder {
|
||||
if (latestPut == null) {
|
||||
String message = String.format("Couldn't find PUT transaction for name %s, service %s and identifier %s",
|
||||
this.name, this.service, this.identifierString());
|
||||
throw new DataException(message);
|
||||
throw new DataNotPublishedException(message);
|
||||
}
|
||||
this.latestPutTransaction = latestPut;
|
||||
|
||||
|
@ -4,6 +4,7 @@ import org.apache.commons.io.FileUtils;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import org.qortal.arbitrary.exception.DataNotPublishedException;
|
||||
import org.qortal.arbitrary.exception.MissingDataException;
|
||||
import org.qortal.arbitrary.misc.Service;
|
||||
import org.qortal.controller.arbitrary.ArbitraryDataBuildManager;
|
||||
@ -169,10 +170,18 @@ public class ArbitraryDataReader {
|
||||
this.uncompress();
|
||||
this.validate();
|
||||
|
||||
} catch (DataNotPublishedException e) {
|
||||
if (e.getMessage() != null) {
|
||||
// Log the message only, to avoid spamming the logs with a full stack trace
|
||||
LOGGER.debug("DataNotPublishedException when trying to load QDN resource: {}", e.getMessage());
|
||||
}
|
||||
this.deleteWorkingDirectory();
|
||||
throw e;
|
||||
|
||||
} catch (DataException e) {
|
||||
LOGGER.info("DataException when trying to load QDN resource", e);
|
||||
this.deleteWorkingDirectory();
|
||||
throw new DataException(e.getMessage());
|
||||
throw e;
|
||||
|
||||
} finally {
|
||||
this.postExecute();
|
||||
|
@ -3,6 +3,7 @@ package org.qortal.arbitrary;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.qortal.arbitrary.ArbitraryDataFile.ResourceIdType;
|
||||
import org.qortal.arbitrary.exception.DataNotPublishedException;
|
||||
import org.qortal.arbitrary.metadata.ArbitraryDataTransactionMetadata;
|
||||
import org.qortal.arbitrary.misc.Service;
|
||||
import org.qortal.controller.arbitrary.ArbitraryDataBuildManager;
|
||||
@ -325,7 +326,7 @@ public class ArbitraryDataResource {
|
||||
if (latestPut == null) {
|
||||
String message = String.format("Couldn't find PUT transaction for name %s, service %s and identifier %s",
|
||||
this.resourceId, this.service, this.identifierString());
|
||||
throw new DataException(message);
|
||||
throw new DataNotPublishedException(message);
|
||||
}
|
||||
this.latestPutTransaction = latestPut;
|
||||
|
||||
|
@ -0,0 +1,22 @@
|
||||
package org.qortal.arbitrary.exception;
|
||||
|
||||
import org.qortal.repository.DataException;
|
||||
|
||||
public class DataNotPublishedException extends DataException {
|
||||
|
||||
public DataNotPublishedException() {
|
||||
}
|
||||
|
||||
public DataNotPublishedException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public DataNotPublishedException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public DataNotPublishedException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
}
|
@ -10,9 +10,7 @@ import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.*;
|
||||
|
||||
import static java.util.Arrays.stream;
|
||||
import static java.util.stream.Collectors.toMap;
|
||||
@ -20,6 +18,31 @@ import static java.util.stream.Collectors.toMap;
|
||||
public enum Service {
|
||||
AUTO_UPDATE(1, false, null, null),
|
||||
ARBITRARY_DATA(100, false, null, null),
|
||||
QCHAT_ATTACHMENT(120, true, 1024*1024L, null) {
|
||||
@Override
|
||||
public ValidationResult validate(Path path) {
|
||||
// Custom validation function to require a single file, with a whitelisted extension
|
||||
int fileCount = 0;
|
||||
File[] files = path.toFile().listFiles();
|
||||
if (files != null) {
|
||||
for (File file : files) {
|
||||
if (file.isDirectory()) {
|
||||
return ValidationResult.DIRECTORIES_NOT_ALLOWED;
|
||||
}
|
||||
final String extension = FilenameUtils.getExtension(file.getName()).toLowerCase();
|
||||
final List<String> allowedExtensions = Arrays.asList("zip", "pdf", "txt", "odt", "ods", "doc", "docx", "xls", "xlsx", "ppt", "pptx");
|
||||
if (extension == null || !allowedExtensions.contains(extension)) {
|
||||
return ValidationResult.INVALID_FILE_EXTENSION;
|
||||
}
|
||||
fileCount++;
|
||||
}
|
||||
}
|
||||
if (fileCount != 1) {
|
||||
return ValidationResult.INVALID_FILE_COUNT;
|
||||
}
|
||||
return ValidationResult.OK;
|
||||
}
|
||||
},
|
||||
WEBSITE(200, true, null, null) {
|
||||
@Override
|
||||
public ValidationResult validate(Path path) {
|
||||
@ -143,7 +166,8 @@ public enum Service {
|
||||
MISSING_INDEX_FILE(4),
|
||||
DIRECTORIES_NOT_ALLOWED(5),
|
||||
INVALID_FILE_EXTENSION(6),
|
||||
MISSING_DATA(7);
|
||||
MISSING_DATA(7),
|
||||
INVALID_FILE_COUNT(8);
|
||||
|
||||
public final int value;
|
||||
|
||||
|
@ -100,6 +100,13 @@ public class BlockChain {
|
||||
/** Whether only one registered name is allowed per account. */
|
||||
private boolean oneNamePerAccount = false;
|
||||
|
||||
/** Checkpoints */
|
||||
public static class Checkpoint {
|
||||
public int height;
|
||||
public String signature;
|
||||
}
|
||||
private List<Checkpoint> checkpoints;
|
||||
|
||||
/** Block rewards by block height */
|
||||
public static class RewardByHeight {
|
||||
public int height;
|
||||
@ -381,6 +388,10 @@ public class BlockChain {
|
||||
return this.oneNamePerAccount;
|
||||
}
|
||||
|
||||
public List<Checkpoint> getCheckpoints() {
|
||||
return this.checkpoints;
|
||||
}
|
||||
|
||||
public List<RewardByHeight> getBlockRewardsByHeight() {
|
||||
return this.rewardsByHeight;
|
||||
}
|
||||
@ -679,6 +690,7 @@ public class BlockChain {
|
||||
|
||||
boolean isTopOnly = Settings.getInstance().isTopOnly();
|
||||
boolean archiveEnabled = Settings.getInstance().isArchiveEnabled();
|
||||
boolean isLite = Settings.getInstance().isLite();
|
||||
boolean canBootstrap = Settings.getInstance().getBootstrap();
|
||||
boolean needsArchiveRebuild = false;
|
||||
BlockData chainTip;
|
||||
@ -699,22 +711,44 @@ public class BlockChain {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Validate checkpoints
|
||||
// Limited to topOnly nodes for now, in order to reduce risk, and to solve a real-world problem with divergent topOnly nodes
|
||||
// TODO: remove the isTopOnly conditional below once this feature has had more testing time
|
||||
if (isTopOnly && !isLite) {
|
||||
List<Checkpoint> checkpoints = BlockChain.getInstance().getCheckpoints();
|
||||
for (Checkpoint checkpoint : checkpoints) {
|
||||
BlockData blockData = repository.getBlockRepository().fromHeight(checkpoint.height);
|
||||
if (blockData == null) {
|
||||
// Try the archive
|
||||
blockData = repository.getBlockArchiveRepository().fromHeight(checkpoint.height);
|
||||
}
|
||||
if (blockData == null) {
|
||||
LOGGER.trace("Couldn't find block for height {}", checkpoint.height);
|
||||
// This is likely due to the block being pruned, so is safe to ignore.
|
||||
// Continue, as there might be other blocks we can check more definitively.
|
||||
continue;
|
||||
}
|
||||
|
||||
byte[] signature = Base58.decode(checkpoint.signature);
|
||||
if (!Arrays.equals(signature, blockData.getSignature())) {
|
||||
LOGGER.info("Error: block at height {} with signature {} doesn't match checkpoint sig: {}. Bootstrapping...", checkpoint.height, Base58.encode(blockData.getSignature()), checkpoint.signature);
|
||||
needsArchiveRebuild = true;
|
||||
break;
|
||||
}
|
||||
LOGGER.info("Block at height {} matches checkpoint signature", blockData.getHeight());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
boolean hasBlocks = (chainTip != null && chainTip.getHeight() > 1);
|
||||
// Check first block is Genesis Block
|
||||
if (!isGenesisBlockValid() || needsArchiveRebuild) {
|
||||
try {
|
||||
rebuildBlockchain();
|
||||
|
||||
if (isTopOnly && hasBlocks) {
|
||||
// Top-only mode is enabled and we have blocks, so it's possible that the genesis block has been pruned
|
||||
// It's best not to validate it, and there's no real need to
|
||||
} else {
|
||||
// Check first block is Genesis Block
|
||||
if (!isGenesisBlockValid() || needsArchiveRebuild) {
|
||||
try {
|
||||
rebuildBlockchain();
|
||||
|
||||
} catch (InterruptedException e) {
|
||||
throw new DataException(String.format("Interrupted when trying to rebuild blockchain: %s", e.getMessage()));
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
throw new DataException(String.format("Interrupted when trying to rebuild blockchain: %s", e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
@ -723,9 +757,7 @@ public class BlockChain {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
repository.checkConsistency();
|
||||
|
||||
// Set the number of blocks to validate based on the pruned state of the chain
|
||||
// If pruned, subtract an extra 10 to allow room for error
|
||||
int blocksToValidate = (isTopOnly || archiveEnabled) ? Settings.getInstance().getPruneBlockLimit() - 10 : 1440;
|
||||
int blocksToValidate = Math.min(Settings.getInstance().getPruneBlockLimit() - 10, 1440);
|
||||
|
||||
int startHeight = Math.max(repository.getBlockRepository().getBlockchainHeight() - blocksToValidate, 1);
|
||||
BlockData detachedBlockData = repository.getBlockRepository().getDetachedBlockSignature(startHeight);
|
||||
|
@ -63,8 +63,8 @@ public class BlockMinter extends Thread {
|
||||
public void run() {
|
||||
Thread.currentThread().setName("BlockMinter");
|
||||
|
||||
if (Settings.getInstance().isLite()) {
|
||||
// Lite nodes do not mint
|
||||
if (Settings.getInstance().isTopOnly() || Settings.getInstance().isLite()) {
|
||||
// Top only and lite nodes do not sign blocks
|
||||
return;
|
||||
}
|
||||
if (Settings.getInstance().getWipeUnconfirmedOnStart()) {
|
||||
|
@ -769,6 +769,16 @@ public class Controller extends Thread {
|
||||
}
|
||||
};
|
||||
|
||||
public static final Predicate<Peer> wasRecentlyTooDivergent = peer -> {
|
||||
Long now = NTP.getTime();
|
||||
Long peerLastTooDivergentTime = peer.getLastTooDivergentTime();
|
||||
if (now == null || peerLastTooDivergentTime == null)
|
||||
return false;
|
||||
|
||||
// Exclude any peers that were TOO_DIVERGENT in the last 5 mins
|
||||
return (now - peerLastTooDivergentTime < 5 * 60 * 1000L);
|
||||
};
|
||||
|
||||
private long getRandomRepositoryMaintenanceInterval() {
|
||||
final long minInterval = Settings.getInstance().getRepositoryMaintenanceMinInterval();
|
||||
final long maxInterval = Settings.getInstance().getRepositoryMaintenanceMaxInterval();
|
||||
|
@ -1121,6 +1121,7 @@ public class Synchronizer extends Thread {
|
||||
// If common block is too far behind us then we're on massively different forks so give up.
|
||||
if (!force && testHeight < ourHeight - MAXIMUM_COMMON_DELTA) {
|
||||
LOGGER.info(String.format("Blockchain too divergent with peer %s", peer));
|
||||
peer.setLastTooDivergentTime(NTP.getTime());
|
||||
return SynchronizationResult.TOO_DIVERGENT;
|
||||
}
|
||||
|
||||
@ -1130,6 +1131,9 @@ public class Synchronizer extends Thread {
|
||||
testHeight = Math.max(testHeight - step, 1);
|
||||
}
|
||||
|
||||
// Peer not considered too divergent
|
||||
peer.setLastTooDivergentTime(0L);
|
||||
|
||||
// Prepend test block's summary as first block summary, as summaries returned are *after* test block
|
||||
BlockSummaryData testBlockSummary = new BlockSummaryData(testBlockData);
|
||||
blockSummariesFromCommon.add(0, testBlockSummary);
|
||||
|
@ -82,7 +82,7 @@ public class ArbitraryDataFileManager extends Thread {
|
||||
|
||||
try {
|
||||
// Use a fixed thread pool to execute the arbitrary data file requests
|
||||
int threadCount = 10;
|
||||
int threadCount = 5;
|
||||
ExecutorService arbitraryDataFileRequestExecutor = Executors.newFixedThreadPool(threadCount);
|
||||
for (int i = 0; i < threadCount; i++) {
|
||||
arbitraryDataFileRequestExecutor.execute(new ArbitraryDataFileRequestThread());
|
||||
@ -288,7 +288,7 @@ public class ArbitraryDataFileManager extends Thread {
|
||||
// The ID needs to match that of the original request
|
||||
message.setId(originalMessage.getId());
|
||||
|
||||
if (!requestingPeer.sendMessage(message)) {
|
||||
if (!requestingPeer.sendMessageWithTimeout(message, (int) ArbitraryDataManager.ARBITRARY_REQUEST_TIMEOUT)) {
|
||||
LOGGER.debug("Failed to forward arbitrary data file to peer {}", requestingPeer);
|
||||
requestingPeer.disconnect("failed to forward arbitrary data file");
|
||||
}
|
||||
@ -564,13 +564,16 @@ public class ArbitraryDataFileManager extends Thread {
|
||||
LOGGER.trace("Hash {} exists", hash58);
|
||||
|
||||
// We can serve the file directly as we already have it
|
||||
LOGGER.debug("Sending file {}...", arbitraryDataFile);
|
||||
ArbitraryDataFileMessage arbitraryDataFileMessage = new ArbitraryDataFileMessage(signature, arbitraryDataFile);
|
||||
arbitraryDataFileMessage.setId(message.getId());
|
||||
if (!peer.sendMessage(arbitraryDataFileMessage)) {
|
||||
LOGGER.debug("Couldn't sent file");
|
||||
if (!peer.sendMessageWithTimeout(arbitraryDataFileMessage, (int) ArbitraryDataManager.ARBITRARY_REQUEST_TIMEOUT)) {
|
||||
LOGGER.debug("Couldn't send file {}", arbitraryDataFile);
|
||||
peer.disconnect("failed to send file");
|
||||
}
|
||||
LOGGER.debug("Sent file {}", arbitraryDataFile);
|
||||
else {
|
||||
LOGGER.debug("Sent file {}", arbitraryDataFile);
|
||||
}
|
||||
}
|
||||
else if (relayInfo != null) {
|
||||
LOGGER.debug("We have relay info for hash {}", Base58.encode(hash));
|
||||
|
@ -42,6 +42,7 @@ public class AtStatesPruner implements Runnable {
|
||||
|
||||
repository.discardChanges();
|
||||
repository.getATRepository().rebuildLatestAtStates();
|
||||
repository.saveChanges();
|
||||
|
||||
while (!Controller.isStopping()) {
|
||||
repository.discardChanges();
|
||||
|
@ -29,6 +29,7 @@ public class AtStatesTrimmer implements Runnable {
|
||||
|
||||
repository.discardChanges();
|
||||
repository.getATRepository().rebuildLatestAtStates();
|
||||
repository.saveChanges();
|
||||
|
||||
while (!Controller.isStopping()) {
|
||||
repository.discardChanges();
|
||||
|
@ -357,19 +357,33 @@ public abstract class Bitcoiny implements ForeignBlockchain {
|
||||
* @return unspent BTC balance, or null if unable to determine balance
|
||||
*/
|
||||
public Long getWalletBalance(String key58) throws ForeignBlockchainException {
|
||||
// It's more accurate to calculate the balance from the transactions, rather than asking Bitcoinj
|
||||
return this.getWalletBalanceFromTransactions(key58);
|
||||
Long balance = 0L;
|
||||
|
||||
// Context.propagate(bitcoinjContext);
|
||||
//
|
||||
// Wallet wallet = walletFromDeterministicKey58(key58);
|
||||
// wallet.setUTXOProvider(new WalletAwareUTXOProvider(this, wallet));
|
||||
//
|
||||
// Coin balance = wallet.getBalance();
|
||||
// if (balance == null)
|
||||
// return null;
|
||||
//
|
||||
// return balance.value;
|
||||
List<TransactionOutput> allUnspentOutputs = new ArrayList<>();
|
||||
Set<String> walletAddresses = this.getWalletAddresses(key58);
|
||||
for (String address : walletAddresses) {
|
||||
allUnspentOutputs.addAll(this.getUnspentOutputs(address));
|
||||
}
|
||||
for (TransactionOutput output : allUnspentOutputs) {
|
||||
if (!output.isAvailableForSpending()) {
|
||||
continue;
|
||||
}
|
||||
balance += output.getValue().value;
|
||||
}
|
||||
return balance;
|
||||
}
|
||||
|
||||
public Long getWalletBalanceFromBitcoinj(String key58) {
|
||||
Context.propagate(bitcoinjContext);
|
||||
|
||||
Wallet wallet = walletFromDeterministicKey58(key58);
|
||||
wallet.setUTXOProvider(new WalletAwareUTXOProvider(this, wallet));
|
||||
|
||||
Coin balance = wallet.getBalance();
|
||||
if (balance == null)
|
||||
return null;
|
||||
|
||||
return balance.value;
|
||||
}
|
||||
|
||||
public Long getWalletBalanceFromTransactions(String key58) throws ForeignBlockchainException {
|
||||
@ -464,6 +478,64 @@ public abstract class Bitcoiny implements ForeignBlockchain {
|
||||
}
|
||||
}
|
||||
|
||||
public Set<String> getWalletAddresses(String key58) throws ForeignBlockchainException {
|
||||
synchronized (this) {
|
||||
Context.propagate(bitcoinjContext);
|
||||
|
||||
Wallet wallet = walletFromDeterministicKey58(key58);
|
||||
DeterministicKeyChain keyChain = wallet.getActiveKeyChain();
|
||||
|
||||
keyChain.setLookaheadSize(Bitcoiny.WALLET_KEY_LOOKAHEAD_INCREMENT);
|
||||
keyChain.maybeLookAhead();
|
||||
|
||||
List<DeterministicKey> keys = new ArrayList<>(keyChain.getLeafKeys());
|
||||
|
||||
Set<String> keySet = new HashSet<>();
|
||||
|
||||
int unusedCounter = 0;
|
||||
int ki = 0;
|
||||
do {
|
||||
boolean areAllKeysUnused = true;
|
||||
|
||||
for (; ki < keys.size(); ++ki) {
|
||||
DeterministicKey dKey = keys.get(ki);
|
||||
|
||||
// Check for transactions
|
||||
Address address = Address.fromKey(this.params, dKey, ScriptType.P2PKH);
|
||||
keySet.add(address.toString());
|
||||
byte[] script = ScriptBuilder.createOutputScript(address).getProgram();
|
||||
|
||||
// Ask for transaction history - if it's empty then key has never been used
|
||||
List<TransactionHash> historicTransactionHashes = this.getAddressTransactions(script, false);
|
||||
|
||||
if (!historicTransactionHashes.isEmpty()) {
|
||||
areAllKeysUnused = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (areAllKeysUnused) {
|
||||
// No transactions
|
||||
if (unusedCounter >= Settings.getInstance().getGapLimit()) {
|
||||
// ... and we've hit our search limit
|
||||
break;
|
||||
}
|
||||
// We haven't hit our search limit yet so increment the counter and keep looking
|
||||
unusedCounter += WALLET_KEY_LOOKAHEAD_INCREMENT;
|
||||
} else {
|
||||
// Some keys in this batch were used, so reset the counter
|
||||
unusedCounter = 0;
|
||||
}
|
||||
|
||||
// Generate some more keys
|
||||
keys.addAll(generateMoreKeys(keyChain));
|
||||
|
||||
// Process new keys
|
||||
} while (true);
|
||||
|
||||
return keySet;
|
||||
}
|
||||
}
|
||||
|
||||
protected SimpleTransaction convertToSimpleTransaction(BitcoinyTransaction t, Set<String> keySet) {
|
||||
long amount = 0;
|
||||
long total = 0L;
|
||||
|
@ -134,6 +134,8 @@ public class Digibyte extends Bitcoiny {
|
||||
Context bitcoinjContext = new Context(digibyteNet.getParams());
|
||||
|
||||
instance = new Digibyte(digibyteNet, electrumX, bitcoinjContext, CURRENCY_CODE);
|
||||
|
||||
electrumX.setBlockchain(instance);
|
||||
}
|
||||
|
||||
return instance;
|
||||
|
@ -138,6 +138,8 @@ public class Ravencoin extends Bitcoiny {
|
||||
Context bitcoinjContext = new Context(ravencoinNet.getParams());
|
||||
|
||||
instance = new Ravencoin(ravencoinNet, electrumX, bitcoinjContext, CURRENCY_CODE);
|
||||
|
||||
electrumX.setBlockchain(instance);
|
||||
}
|
||||
|
||||
return instance;
|
||||
|
@ -85,6 +85,10 @@ public class ChatTransactionData extends TransactionData {
|
||||
return this.chatReference;
|
||||
}
|
||||
|
||||
public void setChatReference(byte[] chatReference) {
|
||||
this.chatReference = chatReference;
|
||||
}
|
||||
|
||||
public byte[] getData() {
|
||||
return this.data;
|
||||
}
|
||||
|
@ -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.7.0";
|
||||
private static final String MIN_PEER_VERSION = "3.8.2";
|
||||
|
||||
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
|
||||
|
@ -155,6 +155,11 @@ public class Peer {
|
||||
*/
|
||||
private CommonBlockData commonBlockData;
|
||||
|
||||
/**
|
||||
* Last time we detected this peer as TOO_DIVERGENT
|
||||
*/
|
||||
private Long lastTooDivergentTime;
|
||||
|
||||
// Message stats
|
||||
|
||||
private static class MessageStats {
|
||||
@ -383,6 +388,14 @@ public class Peer {
|
||||
this.commonBlockData = commonBlockData;
|
||||
}
|
||||
|
||||
public Long getLastTooDivergentTime() {
|
||||
return this.lastTooDivergentTime;
|
||||
}
|
||||
|
||||
public void setLastTooDivergentTime(Long lastTooDivergentTime) {
|
||||
this.lastTooDivergentTime = lastTooDivergentTime;
|
||||
}
|
||||
|
||||
public boolean isSyncInProgress() {
|
||||
return this.syncInProgress;
|
||||
}
|
||||
|
@ -160,7 +160,7 @@ public class Settings {
|
||||
* This prevents the node from being able to serve older blocks */
|
||||
private boolean topOnly = false;
|
||||
/** The amount of recent blocks we should keep when pruning */
|
||||
private int pruneBlockLimit = 1450;
|
||||
private int pruneBlockLimit = 6000;
|
||||
|
||||
/** How often to attempt AT state pruning (ms). */
|
||||
private long atStatesPruneInterval = 3219L; // milliseconds
|
||||
@ -216,7 +216,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.0";
|
||||
private String minPeerVersion = "3.8.2";
|
||||
/** 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 */
|
||||
@ -274,7 +274,7 @@ public class Settings {
|
||||
private String[] bootstrapHosts = new String[] {
|
||||
"http://bootstrap.qortal.org",
|
||||
"http://bootstrap2.qortal.org",
|
||||
"http://62.171.190.193"
|
||||
"http://bootstrap.qortal.online"
|
||||
};
|
||||
|
||||
// Auto-update sources
|
||||
|
@ -30,7 +30,7 @@ public class ChatTransaction extends Transaction {
|
||||
private ChatTransactionData chatTransactionData;
|
||||
|
||||
// Other useful constants
|
||||
public static final int MAX_DATA_SIZE = 1024;
|
||||
public static final int MAX_DATA_SIZE = 4000;
|
||||
public static final int POW_BUFFER_SIZE = 8 * 1024 * 1024; // bytes
|
||||
public static final int POW_DIFFICULTY_ABOVE_QORT_THRESHOLD = 8; // leading zero bits
|
||||
public static final int POW_DIFFICULTY_BELOW_QORT_THRESHOLD = 18; // leading zero bits
|
||||
|
@ -85,8 +85,11 @@
|
||||
"onlineAccountMinterLevelValidationHeight": 1092000,
|
||||
"selfSponsorshipAlgoV1Height": 1092400,
|
||||
"feeValidationFixTimestamp": 1671918000000,
|
||||
"chatReferenceTimestamp": 9999999999999
|
||||
"chatReferenceTimestamp": 1674316800000
|
||||
},
|
||||
"checkpoints": [
|
||||
{ "height": 1136300, "signature": "3BbwawEF2uN8Ni5ofpJXkukoU8ctAPxYoFB7whq9pKfBnjfZcpfEJT4R95NvBDoTP8WDyWvsUvbfHbcr9qSZuYpSKZjUQTvdFf6eqznHGEwhZApWfvXu6zjGCxYCp65F4jsVYYJjkzbjmkCg5WAwN5voudngA23kMK6PpTNygapCzXt" }
|
||||
],
|
||||
"genesisInfo": {
|
||||
"version": 4,
|
||||
"timestamp": "1593450000000",
|
||||
|
@ -84,7 +84,7 @@ public class BlockApiTests extends ApiCommon {
|
||||
|
||||
@Test
|
||||
public void testGetBlockRange() {
|
||||
assertNotNull(this.blocksResource.getBlockRange(1, 1));
|
||||
assertNotNull(this.blocksResource.getBlockRange(1, 1, false, false));
|
||||
|
||||
List<Integer> testValues = Arrays.asList(null, Integer.valueOf(1));
|
||||
|
||||
|
@ -175,4 +175,93 @@ public class ArbitraryServiceTests extends Common {
|
||||
assertEquals(ValidationResult.INVALID_FILE_EXTENSION, service.validate(path));
|
||||
}
|
||||
|
||||
}
|
||||
@Test
|
||||
public void testValidateQChatAttachment() throws IOException {
|
||||
// Generate some random data
|
||||
byte[] data = new byte[1024];
|
||||
new Random().nextBytes(data);
|
||||
|
||||
// Write the data to several files in a temp path
|
||||
Path path = Files.createTempDirectory("testValidateQChatAttachment");
|
||||
path.toFile().deleteOnExit();
|
||||
Files.write(Paths.get(path.toString(), "document.pdf"), data, StandardOpenOption.CREATE);
|
||||
|
||||
Service service = Service.QCHAT_ATTACHMENT;
|
||||
assertTrue(service.isValidationRequired());
|
||||
|
||||
// There is an index file in the root
|
||||
assertEquals(ValidationResult.OK, service.validate(path));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidateInvalidQChatAttachmentFileExtension() throws IOException {
|
||||
// Generate some random data
|
||||
byte[] data = new byte[1024];
|
||||
new Random().nextBytes(data);
|
||||
|
||||
// Write the data to several files in a temp path
|
||||
Path path = Files.createTempDirectory("testValidateInvalidQChatAttachmentFileExtension");
|
||||
path.toFile().deleteOnExit();
|
||||
Files.write(Paths.get(path.toString(), "application.exe"), data, StandardOpenOption.CREATE);
|
||||
|
||||
Service service = Service.QCHAT_ATTACHMENT;
|
||||
assertTrue(service.isValidationRequired());
|
||||
|
||||
// There is an index file in the root
|
||||
assertEquals(ValidationResult.INVALID_FILE_EXTENSION, service.validate(path));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidateEmptyQChatAttachment() throws IOException {
|
||||
Path path = Files.createTempDirectory("testValidateEmptyQChatAttachment");
|
||||
|
||||
Service service = Service.QCHAT_ATTACHMENT;
|
||||
assertTrue(service.isValidationRequired());
|
||||
|
||||
// There is an index file in the root
|
||||
assertEquals(ValidationResult.INVALID_FILE_COUNT, service.validate(path));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidateMultiLayerQChatAttachment() throws IOException {
|
||||
// Generate some random data
|
||||
byte[] data = new byte[1024];
|
||||
new Random().nextBytes(data);
|
||||
|
||||
// Write the data to several files in a temp path
|
||||
Path path = Files.createTempDirectory("testValidateMultiLayerQChatAttachment");
|
||||
path.toFile().deleteOnExit();
|
||||
Files.write(Paths.get(path.toString(), "file1.txt"), data, StandardOpenOption.CREATE);
|
||||
|
||||
Path subdirectory = Paths.get(path.toString(), "subdirectory");
|
||||
Files.createDirectories(subdirectory);
|
||||
Files.write(Paths.get(subdirectory.toString(), "file2.txt"), data, StandardOpenOption.CREATE);
|
||||
Files.write(Paths.get(subdirectory.toString(), "file3.txt"), data, StandardOpenOption.CREATE);
|
||||
|
||||
Service service = Service.QCHAT_ATTACHMENT;
|
||||
assertTrue(service.isValidationRequired());
|
||||
|
||||
// There is an index file in the root
|
||||
assertEquals(ValidationResult.DIRECTORIES_NOT_ALLOWED, service.validate(path));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidateMultiFileQChatAttachment() throws IOException {
|
||||
// Generate some random data
|
||||
byte[] data = new byte[1024];
|
||||
new Random().nextBytes(data);
|
||||
|
||||
// Write the data to several files in a temp path
|
||||
Path path = Files.createTempDirectory("testValidateMultiFileQChatAttachment");
|
||||
path.toFile().deleteOnExit();
|
||||
Files.write(Paths.get(path.toString(), "file1.txt"), data, StandardOpenOption.CREATE);
|
||||
Files.write(Paths.get(path.toString(), "file2.txt"), data, StandardOpenOption.CREATE);
|
||||
|
||||
Service service = Service.QCHAT_ATTACHMENT;
|
||||
assertTrue(service.isValidationRequired());
|
||||
|
||||
// There is an index file in the root
|
||||
assertEquals(ValidationResult.INVALID_FILE_COUNT, service.validate(path));
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
package org.qortal.test.common.transaction;
|
||||
|
||||
import org.qortal.account.PrivateKeyAccount;
|
||||
import org.qortal.crypto.Crypto;
|
||||
import org.qortal.data.transaction.ChatTransactionData;
|
||||
import org.qortal.data.transaction.TransactionData;
|
||||
import org.qortal.repository.DataException;
|
||||
import org.qortal.repository.Repository;
|
||||
|
||||
import java.util.Random;
|
||||
|
||||
public class ChatTestTransaction extends TestTransaction {
|
||||
|
||||
public static TransactionData randomTransaction(Repository repository, PrivateKeyAccount account, boolean wantValid) throws DataException {
|
||||
Random random = new Random();
|
||||
byte[] orderId = new byte[64];
|
||||
random.nextBytes(orderId);
|
||||
|
||||
String sender = Crypto.toAddress(account.getPublicKey());
|
||||
int nonce = 1234567;
|
||||
|
||||
// Generate random recipient
|
||||
byte[] randomPrivateKey = new byte[32];
|
||||
random.nextBytes(randomPrivateKey);
|
||||
PrivateKeyAccount recipientAccount = new PrivateKeyAccount(repository, randomPrivateKey);
|
||||
String recipient = Crypto.toAddress(recipientAccount.getPublicKey());
|
||||
|
||||
byte[] chatReference = new byte[64];
|
||||
random.nextBytes(chatReference);
|
||||
|
||||
byte[] data = new byte[4000];
|
||||
random.nextBytes(data);
|
||||
|
||||
boolean isText = true;
|
||||
boolean isEncrypted = true;
|
||||
|
||||
return new ChatTransactionData(generateBase(account), sender, nonce, recipient, chatReference, data, isText, isEncrypted);
|
||||
}
|
||||
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package org.qortal.test.at;
|
||||
package org.qortal.test.serialization;
|
||||
|
||||
import com.google.common.hash.HashCode;
|
||||
import org.junit.After;
|
@ -0,0 +1,102 @@
|
||||
package org.qortal.test.serialization;
|
||||
|
||||
import com.google.common.hash.HashCode;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.qortal.account.PrivateKeyAccount;
|
||||
import org.qortal.data.transaction.ChatTransactionData;
|
||||
import org.qortal.data.transaction.TransactionData;
|
||||
import org.qortal.repository.DataException;
|
||||
import org.qortal.repository.Repository;
|
||||
import org.qortal.repository.RepositoryManager;
|
||||
import org.qortal.test.common.Common;
|
||||
import org.qortal.test.common.transaction.ChatTestTransaction;
|
||||
import org.qortal.transaction.Transaction;
|
||||
import org.qortal.transform.TransformationException;
|
||||
import org.qortal.transform.transaction.TransactionTransformer;
|
||||
import org.qortal.utils.Base58;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
public class ChatSerializationTests {
|
||||
|
||||
@Before
|
||||
public void beforeTest() throws DataException {
|
||||
Common.useDefaultSettings();
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testChatSerializationWithChatReference() throws DataException, TransformationException {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
|
||||
// Build MESSAGE-type AT transaction with chatReference
|
||||
PrivateKeyAccount signingAccount = Common.getTestAccount(repository, "alice");
|
||||
ChatTransactionData transactionData = (ChatTransactionData) ChatTestTransaction.randomTransaction(repository, signingAccount, true);
|
||||
Transaction transaction = Transaction.fromData(repository, transactionData);
|
||||
transaction.sign(signingAccount);
|
||||
|
||||
assertNotNull(transactionData.getChatReference());
|
||||
|
||||
final int claimedLength = TransactionTransformer.getDataLength(transactionData);
|
||||
byte[] serializedTransaction = TransactionTransformer.toBytes(transactionData);
|
||||
assertEquals("Serialized CHAT transaction length differs from declared length", claimedLength, serializedTransaction.length);
|
||||
|
||||
TransactionData deserializedTransactionData = TransactionTransformer.fromBytes(serializedTransaction);
|
||||
// Re-sign
|
||||
Transaction deserializedTransaction = Transaction.fromData(repository, deserializedTransactionData);
|
||||
deserializedTransaction.sign(signingAccount);
|
||||
assertEquals("Deserialized CHAT transaction signature differs", Base58.encode(transactionData.getSignature()), Base58.encode(deserializedTransactionData.getSignature()));
|
||||
|
||||
// Re-serialize to check new length and bytes
|
||||
final int reclaimedLength = TransactionTransformer.getDataLength(deserializedTransactionData);
|
||||
assertEquals("Reserialized CHAT transaction declared length differs", claimedLength, reclaimedLength);
|
||||
|
||||
byte[] reserializedTransaction = TransactionTransformer.toBytes(deserializedTransactionData);
|
||||
assertEquals("Reserialized CHAT transaction bytes differ", HashCode.fromBytes(serializedTransaction).toString(), HashCode.fromBytes(reserializedTransaction).toString());
|
||||
|
||||
// Deserialized chat reference must match initial chat reference
|
||||
ChatTransactionData deserializedChatTransactionData = (ChatTransactionData) deserializedTransactionData;
|
||||
assertNotNull(deserializedChatTransactionData.getChatReference());
|
||||
assertArrayEquals(deserializedChatTransactionData.getChatReference(), transactionData.getChatReference());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testChatSerializationWithoutChatReference() throws DataException, TransformationException {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
|
||||
// Build MESSAGE-type AT transaction without chatReference
|
||||
PrivateKeyAccount signingAccount = Common.getTestAccount(repository, "alice");
|
||||
ChatTransactionData transactionData = (ChatTransactionData) ChatTestTransaction.randomTransaction(repository, signingAccount, true);
|
||||
transactionData.setChatReference(null);
|
||||
Transaction transaction = Transaction.fromData(repository, transactionData);
|
||||
transaction.sign(signingAccount);
|
||||
|
||||
assertNull(transactionData.getChatReference());
|
||||
|
||||
final int claimedLength = TransactionTransformer.getDataLength(transactionData);
|
||||
byte[] serializedTransaction = TransactionTransformer.toBytes(transactionData);
|
||||
assertEquals("Serialized CHAT transaction length differs from declared length", claimedLength, serializedTransaction.length);
|
||||
|
||||
TransactionData deserializedTransactionData = TransactionTransformer.fromBytes(serializedTransaction);
|
||||
// Re-sign
|
||||
Transaction deserializedTransaction = Transaction.fromData(repository, deserializedTransactionData);
|
||||
deserializedTransaction.sign(signingAccount);
|
||||
assertEquals("Deserialized CHAT transaction signature differs", Base58.encode(transactionData.getSignature()), Base58.encode(deserializedTransactionData.getSignature()));
|
||||
|
||||
// Re-serialize to check new length and bytes
|
||||
final int reclaimedLength = TransactionTransformer.getDataLength(deserializedTransactionData);
|
||||
assertEquals("Reserialized CHAT transaction declared length differs", claimedLength, reclaimedLength);
|
||||
|
||||
byte[] reserializedTransaction = TransactionTransformer.toBytes(deserializedTransactionData);
|
||||
assertEquals("Reserialized CHAT transaction bytes differ", HashCode.fromBytes(serializedTransaction).toString(), HashCode.fromBytes(reserializedTransaction).toString());
|
||||
|
||||
// Deserialized chat reference must match initial chat reference
|
||||
ChatTransactionData deserializedChatTransactionData = (ChatTransactionData) deserializedTransactionData;
|
||||
assertNull(deserializedChatTransactionData.getChatReference());
|
||||
assertArrayEquals(deserializedChatTransactionData.getChatReference(), transactionData.getChatReference());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package org.qortal.test;
|
||||
package org.qortal.test.serialization;
|
||||
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
@ -47,7 +47,6 @@ public class SerializationTests extends Common {
|
||||
switch (txType) {
|
||||
case GENESIS:
|
||||
case ACCOUNT_FLAGS:
|
||||
case CHAT:
|
||||
case PUBLICIZE:
|
||||
case AIRDROP:
|
||||
case ENABLE_FORGING:
|
||||
@ -60,6 +59,7 @@ public class SerializationTests extends Common {
|
||||
TransactionData transactionData = TransactionUtils.randomTransaction(repository, signingAccount, txType, true);
|
||||
Transaction transaction = Transaction.fromData(repository, transactionData);
|
||||
transaction.sign(signingAccount);
|
||||
transaction.importAsUnconfirmed();
|
||||
|
||||
final int claimedLength = TransactionTransformer.getDataLength(transactionData);
|
||||
byte[] serializedTransaction = TransactionTransformer.toBytes(transactionData);
|
@ -74,6 +74,7 @@
|
||||
"increaseOnlineAccountsDifficultyTimestamp": 9999999999999,
|
||||
"onlineAccountMinterLevelValidationHeight": 0,
|
||||
"selfSponsorshipAlgoV1Height": 999999999,
|
||||
"feeValidationFixTimestamp": 0,
|
||||
"chatReferenceTimestamp": 0
|
||||
},
|
||||
"genesisInfo": {
|
||||
|
@ -77,6 +77,7 @@
|
||||
"increaseOnlineAccountsDifficultyTimestamp": 9999999999999,
|
||||
"onlineAccountMinterLevelValidationHeight": 0,
|
||||
"selfSponsorshipAlgoV1Height": 999999999,
|
||||
"feeValidationFixTimestamp": 0,
|
||||
"chatReferenceTimestamp": 0
|
||||
},
|
||||
"genesisInfo": {
|
||||
|
@ -78,6 +78,7 @@
|
||||
"increaseOnlineAccountsDifficultyTimestamp": 9999999999999,
|
||||
"onlineAccountMinterLevelValidationHeight": 0,
|
||||
"selfSponsorshipAlgoV1Height": 999999999,
|
||||
"feeValidationFixTimestamp": 0,
|
||||
"chatReferenceTimestamp": 0
|
||||
},
|
||||
"genesisInfo": {
|
||||
|
@ -78,6 +78,7 @@
|
||||
"increaseOnlineAccountsDifficultyTimestamp": 9999999999999,
|
||||
"onlineAccountMinterLevelValidationHeight": 0,
|
||||
"selfSponsorshipAlgoV1Height": 999999999,
|
||||
"feeValidationFixTimestamp": 0,
|
||||
"chatReferenceTimestamp": 0
|
||||
},
|
||||
"genesisInfo": {
|
||||
|
@ -78,6 +78,7 @@
|
||||
"increaseOnlineAccountsDifficultyTimestamp": 9999999999999,
|
||||
"onlineAccountMinterLevelValidationHeight": 0,
|
||||
"selfSponsorshipAlgoV1Height": 999999999,
|
||||
"feeValidationFixTimestamp": 0,
|
||||
"chatReferenceTimestamp": 0
|
||||
},
|
||||
"genesisInfo": {
|
||||
|
@ -78,6 +78,7 @@
|
||||
"increaseOnlineAccountsDifficultyTimestamp": 9999999999999,
|
||||
"onlineAccountMinterLevelValidationHeight": 0,
|
||||
"selfSponsorshipAlgoV1Height": 999999999,
|
||||
"feeValidationFixTimestamp": 0,
|
||||
"chatReferenceTimestamp": 0
|
||||
},
|
||||
"genesisInfo": {
|
||||
|
@ -79,6 +79,7 @@
|
||||
"increaseOnlineAccountsDifficultyTimestamp": 9999999999999,
|
||||
"onlineAccountMinterLevelValidationHeight": 0,
|
||||
"selfSponsorshipAlgoV1Height": 999999999,
|
||||
"feeValidationFixTimestamp": 0,
|
||||
"chatReferenceTimestamp": 0
|
||||
},
|
||||
"genesisInfo": {
|
||||
|
@ -78,6 +78,7 @@
|
||||
"increaseOnlineAccountsDifficultyTimestamp": 9999999999999,
|
||||
"onlineAccountMinterLevelValidationHeight": 0,
|
||||
"selfSponsorshipAlgoV1Height": 999999999,
|
||||
"feeValidationFixTimestamp": 0,
|
||||
"chatReferenceTimestamp": 0
|
||||
},
|
||||
"genesisInfo": {
|
||||
|
@ -78,6 +78,7 @@
|
||||
"increaseOnlineAccountsDifficultyTimestamp": 9999999999999,
|
||||
"onlineAccountMinterLevelValidationHeight": 0,
|
||||
"selfSponsorshipAlgoV1Height": 999999999,
|
||||
"feeValidationFixTimestamp": 0,
|
||||
"chatReferenceTimestamp": 0
|
||||
},
|
||||
"genesisInfo": {
|
||||
|
@ -78,6 +78,7 @@
|
||||
"increaseOnlineAccountsDifficultyTimestamp": 9999999999999,
|
||||
"onlineAccountMinterLevelValidationHeight": 0,
|
||||
"selfSponsorshipAlgoV1Height": 999999999,
|
||||
"feeValidationFixTimestamp": 0,
|
||||
"chatReferenceTimestamp": 0
|
||||
},
|
||||
"genesisInfo": {
|
||||
|
@ -78,6 +78,7 @@
|
||||
"increaseOnlineAccountsDifficultyTimestamp": 9999999999999,
|
||||
"onlineAccountMinterLevelValidationHeight": 0,
|
||||
"selfSponsorshipAlgoV1Height": 999999999,
|
||||
"feeValidationFixTimestamp": 0,
|
||||
"chatReferenceTimestamp": 0
|
||||
},
|
||||
"genesisInfo": {
|
||||
|
@ -77,7 +77,9 @@
|
||||
"disableReferenceTimestamp": 9999999999999,
|
||||
"increaseOnlineAccountsDifficultyTimestamp": 9999999999999,
|
||||
"onlineAccountMinterLevelValidationHeight": 0,
|
||||
"selfSponsorshipAlgoV1Height": 20
|
||||
"selfSponsorshipAlgoV1Height": 20,
|
||||
"feeValidationFixTimestamp": 0,
|
||||
"chatReferenceTimestamp": 0
|
||||
},
|
||||
"genesisInfo": {
|
||||
"version": 4,
|
||||
|
@ -78,6 +78,7 @@
|
||||
"increaseOnlineAccountsDifficultyTimestamp": 9999999999999,
|
||||
"onlineAccountMinterLevelValidationHeight": 0,
|
||||
"selfSponsorshipAlgoV1Height": 999999999,
|
||||
"feeValidationFixTimestamp": 0,
|
||||
"chatReferenceTimestamp": 0
|
||||
},
|
||||
"genesisInfo": {
|
||||
|
Loading…
Reference in New Issue
Block a user