mirror of
https://github.com/Qortal/qortal.git
synced 2025-08-01 14:41:23 +00:00
Compare commits
60 Commits
v1.5.3
...
sync-multi
Author | SHA1 | Date | |
---|---|---|---|
|
a48a9592d0 | ||
|
227cdc1ec8 | ||
|
2c585a9328 | ||
|
45b0d9e19b | ||
|
026a4b896c | ||
|
78237fcd11 | ||
|
73cc3dcb92 | ||
|
4cff03e7fe | ||
|
280f7814aa | ||
|
3174681bd8 | ||
|
853f80b928 | ||
|
8bdad377d7 | ||
|
9e1c2a5bd1 | ||
|
b1777b6011 | ||
|
904be3005f | ||
|
95eaf4c887 | ||
|
e3923b7b22 | ||
|
a43993e3ec | ||
|
bc6b3fb5f4 | ||
|
df47f5d47b | ||
|
319e64bacc | ||
|
ecf044bed1 | ||
|
76e1de38e8 | ||
|
1648a74ed7 | ||
|
c63a7884cb | ||
|
cffbd41f26 | ||
|
c443187d0b | ||
|
8c305d8390 | ||
|
0345c5c03b | ||
|
2ceba45782 | ||
|
ed423ed041 | ||
|
f58a52eaa4 | ||
|
688404011b | ||
|
8881e0fb75 | ||
|
61de7e144e | ||
|
c3ff9e49e8 | ||
|
d52875aa8f | ||
|
9027cd290c | ||
|
58a7203ede | ||
|
7f5486dade | ||
|
27aeb4f05f | ||
|
255233fe38 | ||
|
4ac3984b7c | ||
|
428af3c0e8 | ||
|
68544715bf | ||
|
d2ea5633fb | ||
|
3aa9b5f0b6 | ||
|
6c5dbf7bd0 | ||
|
3b3dc5032b | ||
|
08f3d653cc | ||
|
f2bbafe6c2 | ||
|
cb80280eaf | ||
|
f22f954ae3 | ||
|
2556855bd7 | ||
|
365662a2af | ||
|
3e0ff7f43f | ||
|
8c3753326f | ||
|
dbcf6de2d5 | ||
|
a5308995b7 | ||
|
270ac88b51 |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -1,6 +1,7 @@
|
||||
/db*
|
||||
/bin/
|
||||
/target/
|
||||
/qortal-backup/
|
||||
/log.txt.*
|
||||
/arbitrary*
|
||||
/Qortal-BTC*
|
||||
@@ -20,7 +21,7 @@
|
||||
/qortal.iml
|
||||
.DS_Store
|
||||
/src/main/resources/resources
|
||||
/src/main/resources/log*.properties
|
||||
/*.jar
|
||||
/run.pid
|
||||
/run.log
|
||||
/WindowsInstaller/Install Files/qortal.jar
|
||||
|
Binary file not shown.
@@ -1,4 +1,4 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<DOCUMENT Type="Advanced Installer" CreateVersion="14.9" version="18.2" Modules="enterprise" RootPath="." Language="en_GB" Id="{713E21E0-28FC-422F-8A95-823D01A5F80B}">
|
||||
<COMPONENT cid="caphyon.advinst.msicomp.MsiPropsComponent">
|
||||
<ROW Property="AI_BITMAP_DISPLAY_MODE" Value="0"/>
|
||||
@@ -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:{75ABAC87-9F88-4229-A7D5-DE07F082566D} 1049:{42997A88-BD5D-4829-9FFD-96D0F14A2572} 2052:{0F6AAF5B-3089-4837-AC63-DEEC282862D9} 2057:{F3CAA239-F0FC-456F-B3F8-D5D846030731} " Type="16"/>
|
||||
<ROW Property="ProductCode" Value="1033:{EB5562C3-664E-4A8B-8907-6D2033B98836} 1049:{36D0E774-B970-4A13-BCC4-1BA6AB3B2633} 2052:{AF6B6B44-9404-403A-B00F-B7110C28E453} 2057:{68BB9EB8-5991-42E5-841C-E76ACE51166D} " Type="16"/>
|
||||
<ROW Property="ProductLanguage" Value="2057"/>
|
||||
<ROW Property="ProductName" Value="Qortal"/>
|
||||
<ROW Property="ProductVersion" Value="1.5.2" Type="32"/>
|
||||
<ROW Property="ProductVersion" Value="1.5.4" 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="{922F3242-D791-484E-B034-2EC3EBE7E57F}" Directory_="APPDIR" Attributes="260" KeyPath="DisplayName" Options="1"/>
|
||||
<ROW Component="AI_CustomARPName" ComponentId="{83DFE721-3F68-4ABE-8697-8EC3A91EEB8A}" 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"/>
|
||||
|
@@ -12,7 +12,7 @@ configured paths, or create a dummy `D:` drive with the expected layout.
|
||||
|
||||
Typical build procedure:
|
||||
|
||||
* Overwrite the `qortal.jar` file in `Install-Files\`
|
||||
* Place the `qortal.jar` file in `Install-Files\`
|
||||
* Open AdvancedInstaller with qortal.aip file
|
||||
* If releasing a new version, change version number in:
|
||||
+ "Product Information" side menu
|
||||
|
2
pom.xml
2
pom.xml
@@ -3,7 +3,7 @@
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>org.qortal</groupId>
|
||||
<artifactId>qortal</artifactId>
|
||||
<version>1.5.3</version>
|
||||
<version>1.5.4</version>
|
||||
<packaging>jar</packaging>
|
||||
<properties>
|
||||
<skipTests>true</skipTests>
|
||||
|
@@ -115,6 +115,9 @@ public class ChatMessagesWebSocket extends ApiWebSocket {
|
||||
}
|
||||
|
||||
private void onNotify(Session session, ChatTransactionData chatTransactionData, List<String> involvingAddresses) {
|
||||
if (chatTransactionData == null)
|
||||
return;
|
||||
|
||||
// We only want direct/non-group messages where sender/recipient match our addresses
|
||||
String recipient = chatTransactionData.getRecipient();
|
||||
if (recipient == null)
|
||||
|
@@ -476,6 +476,16 @@ public class Block {
|
||||
return this.minter;
|
||||
}
|
||||
|
||||
|
||||
public void setRepository(Repository repository) throws DataException {
|
||||
this.repository = repository;
|
||||
|
||||
for (Transaction transaction : this.getTransactions()) {
|
||||
transaction.setRepository(repository);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// More information
|
||||
|
||||
/**
|
||||
@@ -524,8 +534,10 @@ public class Block {
|
||||
long nonAtTransactionCount = transactionsData.stream().filter(transactionData -> transactionData.getType() != TransactionType.AT).count();
|
||||
|
||||
// The number of non-AT transactions fetched from repository should correspond with Block's transactionCount
|
||||
if (nonAtTransactionCount != this.blockData.getTransactionCount())
|
||||
if (nonAtTransactionCount != this.blockData.getTransactionCount()) {
|
||||
LOGGER.error(() -> String.format("Block's transactions from repository (%d) do not match block's transaction count (%d)", nonAtTransactionCount, this.blockData.getTransactionCount()));
|
||||
throw new IllegalStateException("Block's transactions from repository do not match block's transaction count");
|
||||
}
|
||||
|
||||
this.transactions = new ArrayList<>();
|
||||
|
||||
|
@@ -68,9 +68,11 @@ import org.qortal.network.Network;
|
||||
import org.qortal.network.Peer;
|
||||
import org.qortal.network.message.ArbitraryDataMessage;
|
||||
import org.qortal.network.message.BlockSummariesMessage;
|
||||
import org.qortal.network.message.BlocksMessage;
|
||||
import org.qortal.network.message.CachedBlockMessage;
|
||||
import org.qortal.network.message.GetArbitraryDataMessage;
|
||||
import org.qortal.network.message.GetBlockMessage;
|
||||
import org.qortal.network.message.GetBlocksMessage;
|
||||
import org.qortal.network.message.GetBlockSummariesMessage;
|
||||
import org.qortal.network.message.GetOnlineAccountsMessage;
|
||||
import org.qortal.network.message.GetPeersMessage;
|
||||
@@ -101,6 +103,8 @@ import org.qortal.utils.Triple;
|
||||
|
||||
import com.google.common.primitives.Longs;
|
||||
|
||||
import static org.qortal.network.Peer.FETCH_BLOCKS_TIMEOUT;
|
||||
|
||||
public class Controller extends Thread {
|
||||
|
||||
static {
|
||||
@@ -222,6 +226,18 @@ public class Controller extends Thread {
|
||||
}
|
||||
public GetBlockMessageStats getBlockMessageStats = new GetBlockMessageStats();
|
||||
|
||||
public static class GetBlocksMessageStats {
|
||||
public AtomicLong requests = new AtomicLong();
|
||||
public AtomicLong cacheHits = new AtomicLong();
|
||||
public AtomicLong unknownBlocks = new AtomicLong();
|
||||
public AtomicLong cacheFills = new AtomicLong();
|
||||
public AtomicLong fullyFromCache = new AtomicLong();
|
||||
|
||||
public GetBlocksMessageStats() {
|
||||
}
|
||||
}
|
||||
public GetBlocksMessageStats getBlocksMessageStats = new GetBlocksMessageStats();
|
||||
|
||||
public static class GetBlockSummariesStats {
|
||||
public AtomicLong requests = new AtomicLong();
|
||||
public AtomicLong cacheHits = new AtomicLong();
|
||||
@@ -259,17 +275,29 @@ public class Controller extends Thread {
|
||||
throw new RuntimeException("Can't read build.properties resource", e);
|
||||
}
|
||||
|
||||
// Determine build timestamp
|
||||
String buildTimestampProperty = properties.getProperty("build.timestamp");
|
||||
if (buildTimestampProperty == null)
|
||||
if (buildTimestampProperty == null) {
|
||||
throw new RuntimeException("Can't read build.timestamp from build.properties resource");
|
||||
|
||||
this.buildTimestamp = LocalDateTime.parse(buildTimestampProperty, DateTimeFormatter.ofPattern("yyyyMMddHHmmss")).toEpochSecond(ZoneOffset.UTC);
|
||||
}
|
||||
if (buildTimestampProperty.startsWith("$")) {
|
||||
// Maven vars haven't been replaced - this was most likely built using an IDE, not via mvn package
|
||||
this.buildTimestamp = System.currentTimeMillis();
|
||||
buildTimestampProperty = "unknown";
|
||||
} else {
|
||||
this.buildTimestamp = LocalDateTime.parse(buildTimestampProperty, DateTimeFormatter.ofPattern("yyyyMMddHHmmss")).toEpochSecond(ZoneOffset.UTC);
|
||||
}
|
||||
LOGGER.info(String.format("Build timestamp: %s", buildTimestampProperty));
|
||||
|
||||
// Determine build version
|
||||
String buildVersionProperty = properties.getProperty("build.version");
|
||||
if (buildVersionProperty == null)
|
||||
if (buildVersionProperty == null) {
|
||||
throw new RuntimeException("Can't read build.version from build.properties resource");
|
||||
|
||||
}
|
||||
if (buildVersionProperty.contains("${git.commit.id.abbrev}")) {
|
||||
// Maven vars haven't been replaced - this was most likely built using an IDE, not via mvn package
|
||||
buildVersionProperty = buildVersionProperty.replace("${git.commit.id.abbrev}", "debug");
|
||||
}
|
||||
this.buildVersion = VERSION_PREFIX + buildVersionProperty;
|
||||
LOGGER.info(String.format("Build version: %s", this.buildVersion));
|
||||
|
||||
@@ -678,7 +706,7 @@ public class Controller extends Thread {
|
||||
|
||||
final int peersRemoved = peersBeforeComparison - peers.size();
|
||||
if (peersRemoved > 0 && peers.size() > 0)
|
||||
LOGGER.info(String.format("Ignoring %d peers on inferior chains. Peers remaining: %d", peersRemoved, peers.size()));
|
||||
LOGGER.debug(String.format("Ignoring %d peers on inferior chains. Peers remaining: %d", peersRemoved, peers.size()));
|
||||
|
||||
if (peers.isEmpty())
|
||||
return;
|
||||
@@ -687,7 +715,7 @@ public class Controller extends Thread {
|
||||
StringBuilder finalPeersString = new StringBuilder();
|
||||
for (Peer peer : peers)
|
||||
finalPeersString = finalPeersString.length() > 0 ? finalPeersString.append(", ").append(peer) : finalPeersString.append(peer);
|
||||
LOGGER.info(String.format("Choosing random peer from: [%s]", finalPeersString.toString()));
|
||||
LOGGER.debug(String.format("Choosing random peer from: [%s]", finalPeersString.toString()));
|
||||
}
|
||||
|
||||
// Pick random peer to sync with
|
||||
@@ -710,6 +738,7 @@ public class Controller extends Thread {
|
||||
hasStatusChanged = true;
|
||||
}
|
||||
}
|
||||
peer.setSyncInProgress(true);
|
||||
|
||||
if (hasStatusChanged)
|
||||
updateSysTray();
|
||||
@@ -789,6 +818,7 @@ public class Controller extends Thread {
|
||||
return syncResult;
|
||||
} finally {
|
||||
isSynchronizing = false;
|
||||
peer.setSyncInProgress(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -883,14 +913,19 @@ public class Controller extends Thread {
|
||||
|
||||
List<TransactionData> transactions = repository.getTransactionRepository().getUnconfirmedTransactions();
|
||||
|
||||
int deletedCount = 0;
|
||||
for (TransactionData transactionData : transactions) {
|
||||
Transaction transaction = Transaction.fromData(repository, transactionData);
|
||||
|
||||
if (now >= transaction.getDeadline()) {
|
||||
LOGGER.info(() -> String.format("Deleting expired, unconfirmed transaction %s", Base58.encode(transactionData.getSignature())));
|
||||
LOGGER.debug(() -> String.format("Deleting expired, unconfirmed transaction %s", Base58.encode(transactionData.getSignature())));
|
||||
repository.getTransactionRepository().delete(transactionData);
|
||||
deletedCount++;
|
||||
}
|
||||
}
|
||||
if (deletedCount > 0) {
|
||||
LOGGER.info(String.format("Deleted %d expired, unconfirmed transaction%s", deletedCount, (deletedCount == 1 ? "" : "s")));
|
||||
}
|
||||
|
||||
repository.saveChanges();
|
||||
} catch (DataException e) {
|
||||
@@ -1182,6 +1217,10 @@ public class Controller extends Thread {
|
||||
onNetworkGetBlockMessage(peer, message);
|
||||
break;
|
||||
|
||||
case GET_BLOCKS:
|
||||
onNetworkGetBlocksMessage(peer, message);
|
||||
break;
|
||||
|
||||
case TRANSACTION:
|
||||
onNetworkTransactionMessage(peer, message);
|
||||
break;
|
||||
@@ -1296,6 +1335,54 @@ public class Controller extends Thread {
|
||||
}
|
||||
}
|
||||
|
||||
private void onNetworkGetBlocksMessage(Peer peer, Message message) {
|
||||
GetBlocksMessage getBlocksMessage = (GetBlocksMessage) message;
|
||||
byte[] parentSignature = getBlocksMessage.getParentSignature();
|
||||
this.stats.getBlocksMessageStats.requests.incrementAndGet();
|
||||
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
|
||||
// If peer's parent signature matches our latest block signature
|
||||
// then we can short-circuit with an empty response
|
||||
BlockData chainTip = getChainTip();
|
||||
if (chainTip != null && Arrays.equals(parentSignature, chainTip.getSignature())) {
|
||||
Message blocksMessage = new BlocksMessage(Collections.emptyList());
|
||||
blocksMessage.setId(message.getId());
|
||||
if (!peer.sendMessage(blocksMessage))
|
||||
peer.disconnect("failed to send blocks");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Ensure that we don't serve more blocks than the amount specified in the settings
|
||||
// Serializing multiple blocks is very slow, so by default we are using a low limit
|
||||
int blockLimitPerRequest = Settings.getInstance().getMaxBlocksPerResponse();
|
||||
int untrimmedBlockLimitPerRequest = Settings.getInstance().getMaxBlocksPerResponse();
|
||||
int numberRequested = Math.min(blockLimitPerRequest, getBlocksMessage.getNumberRequested());
|
||||
|
||||
List<Block> blocks = new ArrayList<>();
|
||||
BlockData blockData = repository.getBlockRepository().fromReference(parentSignature);
|
||||
|
||||
while (blockData != null && blocks.size() < numberRequested) {
|
||||
// If we're dealing with untrimmed blocks, ensure we don't go above the untrimmedBlockLimitPerRequest
|
||||
if (blockData.isTrimmed() == false && blocks.size() >= untrimmedBlockLimitPerRequest) {
|
||||
break;
|
||||
}
|
||||
Block block = new Block(repository, blockData);
|
||||
blocks.add(block);
|
||||
blockData = repository.getBlockRepository().fromReference(blockData.getSignature());
|
||||
}
|
||||
|
||||
Message blocksMessage = new BlocksMessage(blocks);
|
||||
blocksMessage.setId(message.getId());
|
||||
if (!peer.sendMessageWithTimeout(blocksMessage, FETCH_BLOCKS_TIMEOUT))
|
||||
peer.disconnect("failed to send blocks");
|
||||
|
||||
} catch (DataException e) {
|
||||
LOGGER.error(String.format("Repository issue while sending blocks after %s to peer %s", Base58.encode(parentSignature), peer), e);
|
||||
}
|
||||
}
|
||||
|
||||
private void onNetworkTransactionMessage(Peer peer, Message message) {
|
||||
TransactionMessage transactionMessage = (TransactionMessage) message;
|
||||
TransactionData transactionData = transactionMessage.getTransactionData();
|
||||
|
@@ -25,8 +25,10 @@ import org.qortal.data.transaction.RewardShareTransactionData;
|
||||
import org.qortal.data.transaction.TransactionData;
|
||||
import org.qortal.network.Peer;
|
||||
import org.qortal.network.message.BlockMessage;
|
||||
import org.qortal.network.message.BlocksMessage;
|
||||
import org.qortal.network.message.BlockSummariesMessage;
|
||||
import org.qortal.network.message.GetBlockMessage;
|
||||
import org.qortal.network.message.GetBlocksMessage;
|
||||
import org.qortal.network.message.GetBlockSummariesMessage;
|
||||
import org.qortal.network.message.GetSignaturesV2Message;
|
||||
import org.qortal.network.message.Message;
|
||||
@@ -40,12 +42,14 @@ import org.qortal.transaction.Transaction;
|
||||
import org.qortal.utils.Base58;
|
||||
import org.qortal.utils.NTP;
|
||||
|
||||
import static org.qortal.network.Peer.FETCH_BLOCKS_TIMEOUT;
|
||||
|
||||
public class Synchronizer {
|
||||
|
||||
private static final Logger LOGGER = LogManager.getLogger(Synchronizer.class);
|
||||
|
||||
/** Max number of new blocks we aim to add to chain tip in each sync round */
|
||||
private static final int SYNC_BATCH_SIZE = 200; // XXX move to Settings?
|
||||
private static final int SYNC_BATCH_SIZE = 1000; // XXX move to Settings?
|
||||
|
||||
/** Initial jump back of block height when searching for common block with peer */
|
||||
private static final int INITIAL_BLOCK_STEP = 8;
|
||||
@@ -58,6 +62,14 @@ public class Synchronizer {
|
||||
/** Maximum number of block signatures we ask from peer in one go */
|
||||
private static final int MAXIMUM_REQUEST_SIZE = 200; // XXX move to Settings?
|
||||
|
||||
/* Minimum peer version that supports syncing multiple blocks at once via GetBlocksMessage */
|
||||
private static final long PEER_VERSION_160 = 0x0100060000L;
|
||||
|
||||
|
||||
|
||||
|
||||
// Keep track of the size of the last re-org, so it can be logged
|
||||
private int lastReorgSize;
|
||||
|
||||
private static Synchronizer instance;
|
||||
|
||||
@@ -139,7 +151,7 @@ public class Synchronizer {
|
||||
|
||||
if (wereNewRequestsMade) {
|
||||
final long totalTimeTaken = System.currentTimeMillis() - startTime;
|
||||
LOGGER.info(String.format("Finished searching for common blocks with %d peer%s. Found: %d. Total time taken: %d ms", peers.size(), (peers.size() != 1 ? "s" : ""), commonBlocksFound, totalTimeTaken));
|
||||
LOGGER.debug(String.format("Finished searching for common blocks with %d peer%s. Found: %d. Total time taken: %d ms", peers.size(), (peers.size() != 1 ? "s" : ""), commonBlocksFound, totalTimeTaken));
|
||||
}
|
||||
|
||||
return SynchronizationResult.OK;
|
||||
@@ -506,9 +518,22 @@ public class Synchronizer {
|
||||
byte[] peersLastBlockSignature = peerChainTipData.getLastBlockSignature();
|
||||
|
||||
byte[] ourLastBlockSignature = ourLatestBlockData.getSignature();
|
||||
LOGGER.debug(String.format("Synchronizing with peer %s at height %d, sig %.8s, ts %d; our height %d, sig %.8s, ts %d", peer,
|
||||
String syncString = String.format("Synchronizing with peer %s at height %d, sig %.8s, ts %d; our height %d, sig %.8s, ts %d", peer,
|
||||
peerHeight, Base58.encode(peersLastBlockSignature), peer.getChainTipData().getLastBlockTimestamp(),
|
||||
ourInitialHeight, Base58.encode(ourLastBlockSignature), ourLatestBlockData.getTimestamp()));
|
||||
ourInitialHeight, Base58.encode(ourLastBlockSignature), ourLatestBlockData.getTimestamp());
|
||||
|
||||
// If our latest block is very old, we should log that we're attempting to sync with a peer
|
||||
// Otherwise, it can appear as though nothing is happening for a while after launch
|
||||
final Long minLatestBlockTimestamp = Controller.getMinimumLatestBlockTimestamp();
|
||||
if (minLatestBlockTimestamp != null && ourLatestBlockData.getTimestamp() < minLatestBlockTimestamp) {
|
||||
LOGGER.info(syncString);
|
||||
}
|
||||
else {
|
||||
LOGGER.debug(syncString);
|
||||
}
|
||||
|
||||
// Reset last re-org size as we are starting a new sync round
|
||||
this.lastReorgSize = 0;
|
||||
|
||||
List<BlockSummaryData> peerBlockSummaries = new ArrayList<>();
|
||||
SynchronizationResult findCommonBlockResult = fetchSummariesFromCommonBlock(repository, peer, ourInitialHeight, force, peerBlockSummaries, true);
|
||||
@@ -567,10 +592,19 @@ public class Synchronizer {
|
||||
// Commit
|
||||
repository.saveChanges();
|
||||
|
||||
// Create string for logging
|
||||
final BlockData newLatestBlockData = repository.getBlockRepository().getLastBlock();
|
||||
LOGGER.info(String.format("Synchronized with peer %s to height %d, sig %.8s, ts: %d", peer,
|
||||
String syncLog = String.format("Synchronized with peer %s to height %d, sig %.8s, ts: %d", peer,
|
||||
newLatestBlockData.getHeight(), Base58.encode(newLatestBlockData.getSignature()),
|
||||
newLatestBlockData.getTimestamp()));
|
||||
newLatestBlockData.getTimestamp());
|
||||
|
||||
// Append re-org info
|
||||
if (this.lastReorgSize > 0) {
|
||||
syncLog = syncLog.concat(String.format(", size: %d", this.lastReorgSize));
|
||||
}
|
||||
|
||||
// Log sync info
|
||||
LOGGER.info(syncLog);
|
||||
|
||||
return SynchronizationResult.OK;
|
||||
} finally {
|
||||
@@ -765,7 +799,7 @@ public class Synchronizer {
|
||||
}
|
||||
|
||||
private SynchronizationResult syncToPeerChain(Repository repository, BlockData commonBlockData, int ourInitialHeight,
|
||||
Peer peer, final int peerHeight, List<BlockSummaryData> peerBlockSummaries) throws DataException, InterruptedException {
|
||||
Peer peer, final int peerHeight, List<BlockSummaryData> peerBlockSummaries) throws DataException, InterruptedException {
|
||||
final int commonBlockHeight = commonBlockData.getHeight();
|
||||
final byte[] commonBlockSig = commonBlockData.getSignature();
|
||||
String commonBlockSig58 = Base58.encode(commonBlockSig);
|
||||
@@ -795,19 +829,19 @@ public class Synchronizer {
|
||||
if (Controller.isStopping())
|
||||
return SynchronizationResult.SHUTTING_DOWN;
|
||||
|
||||
// Ensure we don't request more than MAXIMUM_REQUEST_SIZE
|
||||
int numberRequested = Math.min(numberSignaturesRequired, MAXIMUM_REQUEST_SIZE);
|
||||
// Ensure we don't request more than MAXIMUM_REQUEST_SIZE
|
||||
int numberRequested = Math.min(numberSignaturesRequired, MAXIMUM_REQUEST_SIZE);
|
||||
|
||||
// Do we need more signatures?
|
||||
// Do we need more signatures?
|
||||
if (peerBlockSignatures.isEmpty() && numberRequested > 0) {
|
||||
LOGGER.trace(String.format("Requesting %d signature%s after height %d, sig %.8s",
|
||||
numberRequested, (numberRequested != 1 ? "s" : ""), height, Base58.encode(latestPeerSignature)));
|
||||
LOGGER.trace(String.format("Requesting %d signature%s after height %d, sig %.8s",
|
||||
numberRequested, (numberRequested != 1 ? "s" : ""), height, Base58.encode(latestPeerSignature)));
|
||||
|
||||
peerBlockSignatures = this.getBlockSignatures(peer, latestPeerSignature, numberRequested);
|
||||
peerBlockSignatures = this.getBlockSignatures(peer, latestPeerSignature, numberRequested);
|
||||
|
||||
if (peerBlockSignatures == null || peerBlockSignatures.isEmpty()) {
|
||||
LOGGER.info(String.format("Peer %s failed to respond with more block signatures after height %d, sig %.8s", peer,
|
||||
height, Base58.encode(latestPeerSignature)));
|
||||
if (peerBlockSignatures == null || peerBlockSignatures.isEmpty()) {
|
||||
LOGGER.info(String.format("Peer %s failed to respond with more block signatures after height %d, sig %.8s", peer,
|
||||
height, Base58.encode(latestPeerSignature)));
|
||||
|
||||
// Clear our cache of common block summaries for this peer, as they are likely to be invalid
|
||||
CommonBlockData cachedCommonBlockData = peer.getCommonBlockData();
|
||||
@@ -817,7 +851,7 @@ public class Synchronizer {
|
||||
// If we have already received newer blocks from this peer that what we have already, go ahead and apply them
|
||||
if (peerBlocks.size() > 0) {
|
||||
final BlockData ourLatestBlockData = repository.getBlockRepository().getLastBlock();
|
||||
final Block peerLatestBlock = peerBlocks.get(peerBlocks.size() - 1);
|
||||
final Block peerLatestBlock = peerBlocks.get(peerBlocks.size() - 1);
|
||||
final Long minLatestBlockTimestamp = Controller.getMinimumLatestBlockTimestamp();
|
||||
if (ourLatestBlockData != null && peerLatestBlock != null && minLatestBlockTimestamp != null) {
|
||||
|
||||
@@ -840,8 +874,8 @@ public class Synchronizer {
|
||||
return SynchronizationResult.NO_REPLY;
|
||||
}
|
||||
|
||||
numberSignaturesRequired = peerHeight - height - peerBlockSignatures.size();
|
||||
LOGGER.trace(String.format("Received %s signature%s", peerBlockSignatures.size(), (peerBlockSignatures.size() != 1 ? "s" : "")));
|
||||
numberSignaturesRequired = peerHeight - height - peerBlockSignatures.size();
|
||||
LOGGER.trace(String.format("Received %s signature%s", peerBlockSignatures.size(), (peerBlockSignatures.size() != 1 ? "s" : "")));
|
||||
}
|
||||
|
||||
if (peerBlockSignatures.isEmpty()) {
|
||||
@@ -924,6 +958,7 @@ public class Synchronizer {
|
||||
// Unwind to common block (unless common block is our latest block)
|
||||
int ourHeight = ourInitialHeight;
|
||||
LOGGER.debug(String.format("Orphaning blocks back to common block height %d, sig %.8s. Our height: %d", commonBlockHeight, commonBlockSig58, ourHeight));
|
||||
int reorgSize = ourHeight - commonBlockHeight;
|
||||
|
||||
BlockData orphanBlockData = repository.getBlockRepository().fromHeight(ourInitialHeight);
|
||||
while (ourHeight > commonBlockHeight) {
|
||||
@@ -972,12 +1007,113 @@ public class Synchronizer {
|
||||
Controller.getInstance().onNewBlock(newBlock.getBlockData());
|
||||
}
|
||||
|
||||
this.lastReorgSize = reorgSize;
|
||||
return SynchronizationResult.OK;
|
||||
}
|
||||
|
||||
private SynchronizationResult applyNewBlocks(Repository repository, BlockData commonBlockData, int ourInitialHeight,
|
||||
Peer peer, int peerHeight, List<BlockSummaryData> peerBlockSummaries) throws InterruptedException, DataException {
|
||||
|
||||
final BlockData ourLatestBlockData = repository.getBlockRepository().getLastBlock();
|
||||
if (Settings.getInstance().isFastSyncEnabled() && peer.getPeersVersion() >= PEER_VERSION_160 && ourLatestBlockData.isTrimmed())
|
||||
// This peer supports syncing multiple blocks at once via GetBlocksMessage, and it is enabled in the settings
|
||||
return this.applyNewBlocksUsingFastSync(repository, commonBlockData, ourInitialHeight, peer, peerHeight, peerBlockSummaries);
|
||||
else
|
||||
// Older peer version, or fast sync is disabled in the settings - use slow sync
|
||||
return this.applyNewBlocksUsingSlowSync(repository, commonBlockData, ourInitialHeight, peer, peerHeight, peerBlockSummaries);
|
||||
|
||||
}
|
||||
|
||||
private SynchronizationResult applyNewBlocksUsingFastSync(Repository repository, BlockData commonBlockData, int ourInitialHeight,
|
||||
Peer peer, int peerHeight, List<BlockSummaryData> peerBlockSummaries) throws InterruptedException, DataException {
|
||||
LOGGER.debug(String.format("Fetching new blocks from peer %s using fast sync", peer));
|
||||
|
||||
final int commonBlockHeight = commonBlockData.getHeight();
|
||||
final byte[] commonBlockSig = commonBlockData.getSignature();
|
||||
byte[] latestPeerSignature = commonBlockSig;
|
||||
|
||||
int ourHeight = ourInitialHeight;
|
||||
|
||||
// Fetch, and apply, blocks from peer
|
||||
int maxBatchHeight = commonBlockHeight + SYNC_BATCH_SIZE;
|
||||
|
||||
// Ensure that we don't request more blocks than specified in the settings
|
||||
int maxBlocksPerRequest = Settings.getInstance().getMaxBlocksPerRequest();
|
||||
|
||||
while (ourHeight < peerHeight && ourHeight < maxBatchHeight) {
|
||||
if (Controller.isStopping())
|
||||
return SynchronizationResult.SHUTTING_DOWN;
|
||||
|
||||
int numberRequested = Math.min(maxBatchHeight - ourHeight, maxBlocksPerRequest);
|
||||
|
||||
LOGGER.trace(String.format("Fetching %d blocks after height %d, sig %.8s from %s", numberRequested, ourHeight, Base58.encode(latestPeerSignature), peer));
|
||||
List<Block> blocks = this.fetchBlocks(repository, peer, latestPeerSignature, numberRequested);
|
||||
if (blocks == null || blocks.isEmpty()) {
|
||||
LOGGER.info(String.format("Peer %s failed to respond with more blocks after height %d, sig %.8s", peer,
|
||||
ourHeight, Base58.encode(latestPeerSignature)));
|
||||
return SynchronizationResult.NO_REPLY;
|
||||
}
|
||||
LOGGER.trace(String.format("Received %d blocks after height %d, sig %.8s from %s", blocks.size(), ourHeight, Base58.encode(latestPeerSignature), peer));
|
||||
|
||||
for (Block newBlock : blocks) {
|
||||
++ourHeight;
|
||||
|
||||
if (Controller.isStopping())
|
||||
return SynchronizationResult.SHUTTING_DOWN;
|
||||
|
||||
if (newBlock == null) {
|
||||
LOGGER.info(String.format("Peer %s failed to respond with block for height %d, sig %.8s", peer,
|
||||
ourHeight, Base58.encode(latestPeerSignature)));
|
||||
return SynchronizationResult.NO_REPLY;
|
||||
}
|
||||
|
||||
if (!newBlock.isSignatureValid()) {
|
||||
LOGGER.info(String.format("Peer %s sent block with invalid signature for height %d, sig %.8s", peer,
|
||||
ourHeight, Base58.encode(latestPeerSignature)));
|
||||
return SynchronizationResult.INVALID_DATA;
|
||||
}
|
||||
|
||||
// Set the repository, because we couldn't do that when originally constructing the Block
|
||||
newBlock.setRepository(repository);
|
||||
|
||||
// Transactions are transmitted without approval status so determine that now
|
||||
for (Transaction transaction : newBlock.getTransactions()) {
|
||||
transaction.setInitialApprovalStatus();
|
||||
}
|
||||
|
||||
ValidationResult blockResult = newBlock.isValid();
|
||||
if (blockResult != ValidationResult.OK) {
|
||||
LOGGER.info(String.format("Peer %s sent invalid block for height %d, sig %.8s: %s", peer,
|
||||
ourHeight, Base58.encode(latestPeerSignature), blockResult.name()));
|
||||
return SynchronizationResult.INVALID_DATA;
|
||||
}
|
||||
|
||||
// Save transactions attached to this block
|
||||
for (Transaction transaction : newBlock.getTransactions()) {
|
||||
TransactionData transactionData = transaction.getTransactionData();
|
||||
repository.getTransactionRepository().save(transactionData);
|
||||
}
|
||||
|
||||
newBlock.process();
|
||||
|
||||
LOGGER.trace(String.format("Processed block height %d, sig %.8s", newBlock.getBlockData().getHeight(), Base58.encode(newBlock.getBlockData().getSignature())));
|
||||
|
||||
repository.saveChanges();
|
||||
|
||||
Controller.getInstance().onNewBlock(newBlock.getBlockData());
|
||||
|
||||
// Update latestPeerSignature so that subsequent batches start requesting from the correct block
|
||||
latestPeerSignature = newBlock.getSignature();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return SynchronizationResult.OK;
|
||||
}
|
||||
|
||||
private SynchronizationResult applyNewBlocksUsingSlowSync(Repository repository, BlockData commonBlockData, int ourInitialHeight,
|
||||
Peer peer, int peerHeight, List<BlockSummaryData> peerBlockSummaries) throws InterruptedException, DataException {
|
||||
LOGGER.debug(String.format("Fetching new blocks from peer %s", peer));
|
||||
LOGGER.debug(String.format("Fetching new blocks from peer %s using slow sync", peer));
|
||||
|
||||
final int commonBlockHeight = commonBlockData.getHeight();
|
||||
final byte[] commonBlockSig = commonBlockData.getSignature();
|
||||
@@ -1098,6 +1234,22 @@ public class Synchronizer {
|
||||
return new Block(repository, blockMessage.getBlockData(), blockMessage.getTransactions(), blockMessage.getAtStates());
|
||||
}
|
||||
|
||||
private List<Block> fetchBlocks(Repository repository, Peer peer, byte[] parentSignature, int numberRequested) throws InterruptedException {
|
||||
Message getBlocksMessage = new GetBlocksMessage(parentSignature, numberRequested);
|
||||
|
||||
Message message = peer.getResponseWithTimeout(getBlocksMessage, FETCH_BLOCKS_TIMEOUT);
|
||||
if (message == null || message.getType() != MessageType.BLOCKS) {
|
||||
return null;
|
||||
}
|
||||
|
||||
BlocksMessage blocksMessage = (BlocksMessage) message;
|
||||
if (blocksMessage == null || blocksMessage.getBlocks() == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return blocksMessage.getBlocks();
|
||||
}
|
||||
|
||||
private void populateBlockSummariesMinterLevels(Repository repository, List<BlockSummaryData> blockSummaries) throws DataException {
|
||||
final int firstBlockHeight = blockSummaries.get(0).getHeight();
|
||||
|
||||
|
@@ -383,15 +383,6 @@ public class BitcoinACCTv1TradeBot implements AcctTradeBot {
|
||||
atData = repository.getATRepository().fromATAddress(tradeBotData.getAtAddress());
|
||||
if (atData == null) {
|
||||
LOGGER.debug(() -> String.format("Unable to fetch trade AT %s from repository", tradeBotData.getAtAddress()));
|
||||
|
||||
// If it has been over 24 hours since we last updated this trade-bot entry then assume AT is never coming back
|
||||
// and so wipe the trade-bot entry
|
||||
if (tradeBotData.getTimestamp() + MAX_AT_CONFIRMATION_PERIOD < NTP.getTime()) {
|
||||
LOGGER.info(() -> String.format("AT %s has been gone for too long - deleting trade-bot entry", tradeBotData.getAtAddress()));
|
||||
repository.getCrossChainRepository().delete(tradeBotData.getTradePrivateKey());
|
||||
repository.saveChanges();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
@@ -387,15 +387,6 @@ public class LitecoinACCTv1TradeBot implements AcctTradeBot {
|
||||
atData = repository.getATRepository().fromATAddress(tradeBotData.getAtAddress());
|
||||
if (atData == null) {
|
||||
LOGGER.debug(() -> String.format("Unable to fetch trade AT %s from repository", tradeBotData.getAtAddress()));
|
||||
|
||||
// If it has been over 24 hours since we last updated this trade-bot entry then assume AT is never coming back
|
||||
// and so wipe the trade-bot entry
|
||||
if (tradeBotData.getTimestamp() + MAX_AT_CONFIRMATION_PERIOD < NTP.getTime()) {
|
||||
LOGGER.info(() -> String.format("AT %s has been gone for too long - deleting trade-bot entry", tradeBotData.getAtAddress()));
|
||||
repository.getCrossChainRepository().delete(tradeBotData.getTradePrivateKey());
|
||||
repository.saveChanges();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -725,9 +716,9 @@ public class LitecoinACCTv1TradeBot implements AcctTradeBot {
|
||||
// Not finished yet
|
||||
return;
|
||||
|
||||
// If AT is not REDEEMED then something has gone wrong
|
||||
if (crossChainTradeData.mode != AcctMode.REDEEMED) {
|
||||
// Not redeemed so must be refunded/cancelled
|
||||
// If AT is REFUNDED or CANCELLED then something has gone wrong
|
||||
if (crossChainTradeData.mode == AcctMode.REFUNDED || crossChainTradeData.mode == AcctMode.CANCELLED) {
|
||||
// Alice hasn't redeemed the QORT, so there is no point in trying to redeem the LTC
|
||||
TradeBot.updateTradeBotState(repository, tradeBotData, State.BOB_REFUNDED,
|
||||
() -> String.format("AT %s has auto-refunded - trade aborted", tradeBotData.getAtAddress()));
|
||||
|
||||
|
@@ -67,7 +67,11 @@ public class Bitcoin extends Bitcoiny {
|
||||
new Server("192.166.219.200", Server.ConnectionType.SSL, 50002),
|
||||
new Server("2ex.digitaleveryware.com", Server.ConnectionType.SSL, 50002),
|
||||
new Server("dxm.no-ip.biz", Server.ConnectionType.SSL, 50002),
|
||||
new Server("caleb.vegas", Server.ConnectionType.SSL, 50002));
|
||||
new Server("caleb.vegas", Server.ConnectionType.SSL, 50002),
|
||||
new Server("ecdsa.net", Server.ConnectionType.SSL, 110),
|
||||
new Server("electrum.hsmiths.com", Server.ConnectionType.SSL, 995),
|
||||
new Server("elec.luggs.co", Server.ConnectionType.SSL, 443),
|
||||
new Server("btc.smsys.me", Server.ConnectionType.SSL, 995));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@@ -51,7 +51,10 @@ public class Litecoin extends Bitcoiny {
|
||||
new Server("ltc.rentonisk.com", Server.ConnectionType.TCP, 50001),
|
||||
new Server("ltc.rentonisk.com", Server.ConnectionType.SSL, 50002),
|
||||
new Server("electrum-ltc.petrkr.net", Server.ConnectionType.SSL, 60002),
|
||||
new Server("ltc.litepay.ch", Server.ConnectionType.SSL, 50022));
|
||||
new Server("ltc.litepay.ch", Server.ConnectionType.SSL, 50022),
|
||||
new Server("electrum-ltc-bysh.me", Server.ConnectionType.TCP, 50002),
|
||||
new Server("electrum.jochen-hoenicke.de", Server.ConnectionType.TCP, 50005),
|
||||
new Server("node.ispol.sk", Server.ConnectionType.TCP, 50004));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@@ -9,7 +9,10 @@ import javax.xml.bind.annotation.XmlAccessorType;
|
||||
import javax.xml.bind.annotation.XmlElement;
|
||||
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
|
||||
|
||||
import org.qortal.block.BlockChain;
|
||||
import org.qortal.settings.Settings;
|
||||
import org.qortal.crypto.Crypto;
|
||||
import org.qortal.utils.NTP;
|
||||
|
||||
// All properties to be converted to JSON via JAX-RS
|
||||
@XmlAccessorType(XmlAccessType.FIELD)
|
||||
@@ -204,6 +207,13 @@ public class BlockData implements Serializable {
|
||||
return this.onlineAccountsSignatures;
|
||||
}
|
||||
|
||||
public boolean isTrimmed() {
|
||||
long onlineAccountSignaturesTrimmedTimestamp = NTP.getTime() - BlockChain.getInstance().getOnlineAccountSignaturesMaxLifetime();
|
||||
long currentTrimmableTimestamp = NTP.getTime() - Settings.getInstance().getAtStatesMaxLifetime();
|
||||
long blockTimestamp = this.getTimestamp();
|
||||
return blockTimestamp < onlineAccountSignaturesTrimmedTimestamp && blockTimestamp < currentTrimmableTimestamp;
|
||||
}
|
||||
|
||||
// JAXB special
|
||||
|
||||
@XmlElement(name = "minterAddress")
|
||||
|
@@ -72,7 +72,8 @@ public class Network {
|
||||
private static final String[] INITIAL_PEERS = new String[]{
|
||||
"node1.qortal.org", "node2.qortal.org", "node3.qortal.org", "node4.qortal.org", "node5.qortal.org",
|
||||
"node6.qortal.org", "node7.qortal.org", "node8.qortal.org", "node9.qortal.org", "node10.qortal.org",
|
||||
"node.qortal.ru", "node2.qortal.ru", "node3.qortal.ru", "node.qortal.uk"
|
||||
"node.qortal.ru", "node2.qortal.ru", "node3.qortal.ru", "node.qortal.uk", "node22.qortal.org",
|
||||
"cinfu1.crowetic.com", "node.cwd.systems"
|
||||
};
|
||||
|
||||
private static final long NETWORK_EPC_KEEPALIVE = 10L; // seconds
|
||||
@@ -80,6 +81,8 @@ public class Network {
|
||||
public static final int MAX_SIGNATURES_PER_REPLY = 500;
|
||||
public static final int MAX_BLOCK_SUMMARIES_PER_REPLY = 500;
|
||||
|
||||
private static final long DISCONNECTION_CHECK_INTERVAL = 10 * 1000L; // milliseconds
|
||||
|
||||
// Generate our node keys / ID
|
||||
private final Ed25519PrivateKeyParameters edPrivateKeyParams = new Ed25519PrivateKeyParameters(new SecureRandom());
|
||||
private final Ed25519PublicKeyParameters edPublicKeyParams = edPrivateKeyParams.generatePublicKey();
|
||||
@@ -89,6 +92,8 @@ public class Network {
|
||||
private final int minOutboundPeers;
|
||||
private final int maxPeers;
|
||||
|
||||
private long nextDisconnectionCheck = 0L;
|
||||
|
||||
private final List<PeerData> allKnownPeers = new ArrayList<>();
|
||||
private final List<Peer> connectedPeers = new ArrayList<>();
|
||||
private final List<PeerAddress> selfPeers = new ArrayList<>();
|
||||
@@ -150,9 +155,23 @@ public class Network {
|
||||
}
|
||||
|
||||
// Load all known peers from repository
|
||||
try (Repository repository = RepositoryManager.getRepository()) {
|
||||
synchronized (this.allKnownPeers) {
|
||||
this.allKnownPeers.addAll(repository.getNetworkRepository().getAllPeers());
|
||||
synchronized (this.allKnownPeers) { List<String> fixedNetwork = Settings.getInstance().getFixedNetwork();
|
||||
if (fixedNetwork != null && !fixedNetwork.isEmpty()) {
|
||||
Long addedWhen = NTP.getTime();
|
||||
String addedBy = "fixedNetwork";
|
||||
List<PeerAddress> peerAddresses = new ArrayList<>();
|
||||
for (String address : fixedNetwork) {
|
||||
PeerAddress peerAddress = PeerAddress.fromString(address);
|
||||
peerAddresses.add(peerAddress);
|
||||
}
|
||||
List<PeerData> peers = peerAddresses.stream()
|
||||
.map(peerAddress -> new PeerData(peerAddress, addedWhen, addedBy))
|
||||
.collect(Collectors.toList());
|
||||
this.allKnownPeers.addAll(peers);
|
||||
} else {
|
||||
try (Repository repository = RepositoryManager.getRepository()) {
|
||||
this.allKnownPeers.addAll(repository.getNetworkRepository().getAllPeers());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -508,14 +527,24 @@ public class Network {
|
||||
if (socketChannel == null) {
|
||||
return;
|
||||
}
|
||||
PeerAddress address = PeerAddress.fromSocket(socketChannel.socket());
|
||||
List<String> fixedNetwork = Settings.getInstance().getFixedNetwork();
|
||||
if (fixedNetwork != null && !fixedNetwork.isEmpty() && ipNotInFixedList(address, fixedNetwork)) {
|
||||
try {
|
||||
LOGGER.debug("Connection discarded from peer {} as not in the fixed network list", address);
|
||||
socketChannel.close();
|
||||
} catch (IOException e) {
|
||||
// IGNORE
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
final Long now = NTP.getTime();
|
||||
Peer newPeer;
|
||||
|
||||
try {
|
||||
if (now == null) {
|
||||
LOGGER.debug("Connection discarded from peer {} due to lack of NTP sync",
|
||||
PeerAddress.fromSocket(socketChannel.socket()));
|
||||
LOGGER.debug("Connection discarded from peer {} due to lack of NTP sync", address);
|
||||
socketChannel.close();
|
||||
return;
|
||||
}
|
||||
@@ -523,12 +552,12 @@ public class Network {
|
||||
synchronized (this.connectedPeers) {
|
||||
if (connectedPeers.size() >= maxPeers) {
|
||||
// We have enough peers
|
||||
LOGGER.debug("Connection discarded from peer {}", PeerAddress.fromSocket(socketChannel.socket()));
|
||||
LOGGER.debug("Connection discarded from peer {} because the server is full", address);
|
||||
socketChannel.close();
|
||||
return;
|
||||
}
|
||||
|
||||
LOGGER.debug("Connection accepted from peer {}", PeerAddress.fromSocket(socketChannel.socket()));
|
||||
LOGGER.debug("Connection accepted from peer {}", address);
|
||||
|
||||
newPeer = new Peer(socketChannel, channelSelector);
|
||||
this.connectedPeers.add(newPeer);
|
||||
@@ -536,6 +565,7 @@ public class Network {
|
||||
} catch (IOException e) {
|
||||
if (socketChannel.isOpen()) {
|
||||
try {
|
||||
LOGGER.debug("Connection failed from peer {} while connecting/closing", address);
|
||||
socketChannel.close();
|
||||
} catch (IOException ce) {
|
||||
// Couldn't close?
|
||||
@@ -547,6 +577,16 @@ public class Network {
|
||||
this.onPeerReady(newPeer);
|
||||
}
|
||||
|
||||
private boolean ipNotInFixedList(PeerAddress address, List<String> fixedNetwork) {
|
||||
for (String ipAddress : fixedNetwork) {
|
||||
String[] bits = ipAddress.split(":");
|
||||
if (bits.length >= 1 && bits.length <= 2 && address.getHost().equals(bits[0])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private Peer getConnectablePeer(final Long now) throws InterruptedException {
|
||||
// We can't block here so use tryRepository(). We don't NEED to connect a new peer.
|
||||
try (Repository repository = RepositoryManager.tryRepository()) {
|
||||
@@ -576,6 +616,8 @@ public class Network {
|
||||
// Don't consider already connected peers (resolved address match)
|
||||
// XXX This might be too slow if we end up waiting a long time for hostnames to resolve via DNS
|
||||
peers.removeIf(isResolvedAsConnectedPeer);
|
||||
|
||||
this.checkLongestConnection(now);
|
||||
}
|
||||
|
||||
// Any left?
|
||||
@@ -633,6 +675,29 @@ public class Network {
|
||||
return null;
|
||||
}
|
||||
|
||||
private void checkLongestConnection(Long now) {
|
||||
if (now == null || now < nextDisconnectionCheck) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Find peers that have reached their maximum connection age, and disconnect them
|
||||
List<Peer> peersToDisconnect = this.connectedPeers.stream()
|
||||
.filter(peer -> !peer.isSyncInProgress())
|
||||
.filter(peer -> peer.hasReachedMaxConnectionAge())
|
||||
.collect(Collectors.toList());
|
||||
|
||||
if (peersToDisconnect != null && peersToDisconnect.size() > 0) {
|
||||
for (Peer peer : peersToDisconnect) {
|
||||
LOGGER.info("Forcing disconnection of peer {} because connection age ({} ms) " +
|
||||
"has reached the maximum ({} ms)", peer, peer.getConnectionAge(), peer.getMaxConnectionAge());
|
||||
peer.disconnect("Connection age too old");
|
||||
}
|
||||
}
|
||||
|
||||
// Check again after a minimum fixed interval
|
||||
nextDisconnectionCheck = now + DISCONNECTION_CHECK_INTERVAL;
|
||||
}
|
||||
|
||||
// Peer callbacks
|
||||
|
||||
protected void wakeupChannelSelector() {
|
||||
@@ -1115,6 +1180,10 @@ public class Network {
|
||||
|
||||
private boolean mergePeers(Repository repository, String addedBy, long addedWhen, List<PeerAddress> peerAddresses)
|
||||
throws DataException {
|
||||
List<String> fixedNetwork = Settings.getInstance().getFixedNetwork();
|
||||
if (fixedNetwork != null && !fixedNetwork.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
List<PeerData> newPeers;
|
||||
synchronized (this.allKnownPeers) {
|
||||
for (PeerData knownPeerData : this.allKnownPeers) {
|
||||
|
@@ -47,6 +47,11 @@ public class Peer {
|
||||
*/
|
||||
private static final int RESPONSE_TIMEOUT = 3000; // ms
|
||||
|
||||
/**
|
||||
* Maximum time to wait for a peer to respond with blocks (ms)
|
||||
*/
|
||||
public static final int FETCH_BLOCKS_TIMEOUT = 10000;
|
||||
|
||||
/**
|
||||
* Interval between PING messages to a peer. (ms)
|
||||
* <p>
|
||||
@@ -79,6 +84,7 @@ public class Peer {
|
||||
private Handshake handshakeStatus = Handshake.STARTED;
|
||||
private volatile boolean handshakeMessagePending = false;
|
||||
private long handshakeComplete = -1L;
|
||||
private long maxConnectionAge = 0L;
|
||||
|
||||
/**
|
||||
* Timestamp of when socket was accepted, or connected.
|
||||
@@ -96,6 +102,8 @@ public class Peer {
|
||||
|
||||
byte[] ourChallenge;
|
||||
|
||||
private boolean syncInProgress = false;
|
||||
|
||||
// Versioning
|
||||
public static final Pattern VERSION_PATTERN = Pattern.compile(Controller.VERSION_PREFIX
|
||||
+ "(\\d{1,3})\\.(\\d{1,5})\\.(\\d{1,5})");
|
||||
@@ -192,10 +200,24 @@ public class Peer {
|
||||
this.handshakeStatus = handshakeStatus;
|
||||
if (handshakeStatus.equals(Handshake.COMPLETED)) {
|
||||
this.handshakeComplete = System.currentTimeMillis();
|
||||
this.generateRandomMaxConnectionAge();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void generateRandomMaxConnectionAge() {
|
||||
// Retrieve the min and max connection time from the settings, and calculate the range
|
||||
final int minPeerConnectionTime = Settings.getInstance().getMinPeerConnectionTime();
|
||||
final int maxPeerConnectionTime = Settings.getInstance().getMaxPeerConnectionTime();
|
||||
final int peerConnectionTimeRange = maxPeerConnectionTime - minPeerConnectionTime;
|
||||
|
||||
// Generate a random number between the min and the max
|
||||
Random random = new Random();
|
||||
this.maxConnectionAge = (random.nextInt(peerConnectionTimeRange) + minPeerConnectionTime) * 1000L;
|
||||
LOGGER.debug(String.format("Generated max connection age for peer %s. Min: %ds, max: %ds, range: %ds, random max: %dms", this, minPeerConnectionTime, maxPeerConnectionTime, peerConnectionTimeRange, this.maxConnectionAge));
|
||||
|
||||
}
|
||||
|
||||
protected void resetHandshakeMessagePending() {
|
||||
this.handshakeMessagePending = false;
|
||||
}
|
||||
@@ -325,6 +347,14 @@ public class Peer {
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isSyncInProgress() {
|
||||
return this.syncInProgress;
|
||||
}
|
||||
|
||||
public void setSyncInProgress(boolean syncInProgress) {
|
||||
this.syncInProgress = syncInProgress;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
// Easier, and nicer output, than peer.getRemoteSocketAddress()
|
||||
@@ -519,12 +549,22 @@ public class Peer {
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to send Message to peer.
|
||||
* Attempt to send Message to peer, using default RESPONSE_TIMEOUT.
|
||||
*
|
||||
* @param message message to be sent
|
||||
* @return <code>true</code> if message successfully sent; <code>false</code> otherwise
|
||||
*/
|
||||
public boolean sendMessage(Message message) {
|
||||
return this.sendMessageWithTimeout(message, RESPONSE_TIMEOUT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to send Message to peer, using custom timeout.
|
||||
*
|
||||
* @param message message to be sent
|
||||
* @return <code>true</code> if message successfully sent; <code>false</code> otherwise
|
||||
*/
|
||||
public boolean sendMessageWithTimeout(Message message, int timeout) {
|
||||
if (!this.socketChannel.isOpen()) {
|
||||
return false;
|
||||
}
|
||||
@@ -558,7 +598,7 @@ public class Peer {
|
||||
*/
|
||||
Thread.sleep(1L); //NOSONAR squid:S2276
|
||||
|
||||
if (System.currentTimeMillis() - sendStart > RESPONSE_TIMEOUT) {
|
||||
if (System.currentTimeMillis() - sendStart > timeout) {
|
||||
// We've taken too long to send this message
|
||||
return false;
|
||||
}
|
||||
@@ -579,7 +619,7 @@ public class Peer {
|
||||
}
|
||||
|
||||
/**
|
||||
* Send message to peer and await response.
|
||||
* Send message to peer and await response, using default RESPONSE_TIMEOUT.
|
||||
* <p>
|
||||
* Message is assigned a random ID and sent.
|
||||
* If a response with matching ID is received then it is returned to caller.
|
||||
@@ -593,6 +633,24 @@ public class Peer {
|
||||
* @throws InterruptedException if interrupted while waiting
|
||||
*/
|
||||
public Message getResponse(Message message) throws InterruptedException {
|
||||
return getResponseWithTimeout(message, RESPONSE_TIMEOUT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send message to peer and await response.
|
||||
* <p>
|
||||
* Message is assigned a random ID and sent.
|
||||
* If a response with matching ID is received then it is returned to caller.
|
||||
* <p>
|
||||
* If no response with matching ID within timeout, or some other error/exception occurs,
|
||||
* then return <code>null</code>.<br>
|
||||
* (Assume peer will be rapidly disconnected after this).
|
||||
*
|
||||
* @param message message to send
|
||||
* @return <code>Message</code> if valid response received; <code>null</code> if not or error/exception occurs
|
||||
* @throws InterruptedException if interrupted while waiting
|
||||
*/
|
||||
public Message getResponseWithTimeout(Message message, int timeout) throws InterruptedException {
|
||||
BlockingQueue<Message> blockingQueue = new ArrayBlockingQueue<>(1);
|
||||
|
||||
// Assign random ID to this message
|
||||
@@ -607,13 +665,13 @@ public class Peer {
|
||||
message.setId(id);
|
||||
|
||||
// Try to send message
|
||||
if (!this.sendMessage(message)) {
|
||||
if (!this.sendMessageWithTimeout(message, timeout)) {
|
||||
this.replyQueues.remove(id);
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
return blockingQueue.poll(RESPONSE_TIMEOUT, TimeUnit.MILLISECONDS);
|
||||
return blockingQueue.poll(timeout, TimeUnit.MILLISECONDS);
|
||||
} finally {
|
||||
this.replyQueues.remove(id);
|
||||
}
|
||||
@@ -777,4 +835,12 @@ public class Peer {
|
||||
}
|
||||
return handshakeComplete;
|
||||
}
|
||||
|
||||
public long getMaxConnectionAge() {
|
||||
return maxConnectionAge;
|
||||
}
|
||||
|
||||
public boolean hasReachedMaxConnectionAge() {
|
||||
return this.getConnectionAge() > this.getMaxConnectionAge();
|
||||
}
|
||||
}
|
||||
|
91
src/main/java/org/qortal/network/message/BlocksMessage.java
Normal file
91
src/main/java/org/qortal/network/message/BlocksMessage.java
Normal file
@@ -0,0 +1,91 @@
|
||||
package org.qortal.network.message;
|
||||
|
||||
import com.google.common.primitives.Ints;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.qortal.block.Block;
|
||||
import org.qortal.data.at.ATStateData;
|
||||
import org.qortal.data.block.BlockData;
|
||||
import org.qortal.data.transaction.TransactionData;
|
||||
import org.qortal.transform.TransformationException;
|
||||
import org.qortal.transform.block.BlockTransformer;
|
||||
import org.qortal.utils.Triple;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class BlocksMessage extends Message {
|
||||
|
||||
private static final Logger LOGGER = LogManager.getLogger(BlocksMessage.class);
|
||||
|
||||
private List<Block> blocks;
|
||||
|
||||
public BlocksMessage(List<Block> blocks) {
|
||||
this(-1, blocks);
|
||||
}
|
||||
|
||||
private BlocksMessage(int id, List<Block> blocks) {
|
||||
super(id, MessageType.BLOCKS);
|
||||
|
||||
this.blocks = blocks;
|
||||
}
|
||||
|
||||
public List<Block> getBlocks() {
|
||||
return this.blocks;
|
||||
}
|
||||
|
||||
public static Message fromByteBuffer(int id, ByteBuffer bytes) throws UnsupportedEncodingException {
|
||||
|
||||
int count = bytes.getInt();
|
||||
List<Block> blocks = new ArrayList<>();
|
||||
|
||||
for (int i = 0; i < count; ++i) {
|
||||
int height = bytes.getInt();
|
||||
|
||||
try {
|
||||
boolean finalBlockInBuffer = (i == count-1);
|
||||
|
||||
Triple<BlockData, List<TransactionData>, List<ATStateData>> blockInfo = null;
|
||||
blockInfo = BlockTransformer.fromByteBuffer(bytes, finalBlockInBuffer);
|
||||
BlockData blockData = blockInfo.getA();
|
||||
blockData.setHeight(height);
|
||||
|
||||
// We are unable to obtain a valid Repository instance here, so set it to null and we will attach it later
|
||||
Block block = new Block(null, blockData, blockInfo.getB(), blockInfo.getC());
|
||||
blocks.add(block);
|
||||
|
||||
} catch (TransformationException e) {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return new BlocksMessage(id, blocks);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected byte[] toData() {
|
||||
try {
|
||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
|
||||
|
||||
bytes.write(Ints.toByteArray(this.blocks.size()));
|
||||
|
||||
for (Block block : this.blocks) {
|
||||
bytes.write(Ints.toByteArray(block.getBlockData().getHeight()));
|
||||
bytes.write(BlockTransformer.toBytes(block));
|
||||
}
|
||||
LOGGER.trace(String.format("Total length of %d blocks is %d bytes", this.blocks.size(), bytes.size()));
|
||||
|
||||
return bytes.toByteArray();
|
||||
} catch (IOException e) {
|
||||
return null;
|
||||
} catch (TransformationException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,65 @@
|
||||
package org.qortal.network.message;
|
||||
|
||||
import com.google.common.primitives.Ints;
|
||||
import org.qortal.transform.Transformer;
|
||||
import org.qortal.transform.block.BlockTransformer;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public class GetBlocksMessage extends Message {
|
||||
|
||||
private static final int BLOCK_SIGNATURE_LENGTH = BlockTransformer.BLOCK_SIGNATURE_LENGTH;
|
||||
|
||||
private byte[] parentSignature;
|
||||
private int numberRequested;
|
||||
|
||||
public GetBlocksMessage(byte[] parentSignature, int numberRequested) {
|
||||
this(-1, parentSignature, numberRequested);
|
||||
}
|
||||
|
||||
private GetBlocksMessage(int id, byte[] parentSignature, int numberRequested) {
|
||||
super(id, MessageType.GET_BLOCKS);
|
||||
|
||||
this.parentSignature = parentSignature;
|
||||
this.numberRequested = numberRequested;
|
||||
}
|
||||
|
||||
public byte[] getParentSignature() {
|
||||
return this.parentSignature;
|
||||
}
|
||||
|
||||
public int getNumberRequested() {
|
||||
return this.numberRequested;
|
||||
}
|
||||
|
||||
public static Message fromByteBuffer(int id, ByteBuffer bytes) throws UnsupportedEncodingException {
|
||||
if (bytes.remaining() != BLOCK_SIGNATURE_LENGTH + Transformer.INT_LENGTH)
|
||||
return null;
|
||||
|
||||
byte[] parentSignature = new byte[BLOCK_SIGNATURE_LENGTH];
|
||||
bytes.get(parentSignature);
|
||||
|
||||
int numberRequested = bytes.getInt();
|
||||
|
||||
return new GetBlocksMessage(id, parentSignature, numberRequested);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected byte[] toData() {
|
||||
try {
|
||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
|
||||
|
||||
bytes.write(this.parentSignature);
|
||||
|
||||
bytes.write(Ints.toByteArray(this.numberRequested));
|
||||
|
||||
return bytes.toByteArray();
|
||||
} catch (IOException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -6,6 +6,7 @@ import java.io.UnsupportedEncodingException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.qortal.data.network.OnlineAccountData;
|
||||
import org.qortal.transform.Transformer;
|
||||
@@ -25,7 +26,7 @@ public class GetOnlineAccountsMessage extends Message {
|
||||
private GetOnlineAccountsMessage(int id, List<OnlineAccountData> onlineAccounts) {
|
||||
super(id, MessageType.GET_ONLINE_ACCOUNTS);
|
||||
|
||||
this.onlineAccounts = onlineAccounts;
|
||||
this.onlineAccounts = onlineAccounts.stream().limit(MAX_ACCOUNT_COUNT).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public List<OnlineAccountData> getOnlineAccounts() {
|
||||
@@ -35,12 +36,9 @@ public class GetOnlineAccountsMessage extends Message {
|
||||
public static Message fromByteBuffer(int id, ByteBuffer bytes) throws UnsupportedEncodingException {
|
||||
final int accountCount = bytes.getInt();
|
||||
|
||||
if (accountCount > MAX_ACCOUNT_COUNT)
|
||||
return null;
|
||||
|
||||
List<OnlineAccountData> onlineAccounts = new ArrayList<>(accountCount);
|
||||
|
||||
for (int i = 0; i < accountCount; ++i) {
|
||||
for (int i = 0; i < Math.min(MAX_ACCOUNT_COUNT, accountCount); ++i) {
|
||||
long timestamp = bytes.getLong();
|
||||
|
||||
byte[] publicKey = new byte[Transformer.PUBLIC_KEY_LENGTH];
|
||||
|
@@ -25,7 +25,7 @@ public abstract class Message {
|
||||
private static final int MAGIC_LENGTH = 4;
|
||||
private static final int CHECKSUM_LENGTH = 4;
|
||||
|
||||
private static final int MAX_DATA_SIZE = 1024 * 1024; // 1MB
|
||||
private static final int MAX_DATA_SIZE = 10 * 1024 * 1024; // 10MB
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
public static class MessageException extends Exception {
|
||||
@@ -80,7 +80,10 @@ public abstract class Message {
|
||||
GET_ONLINE_ACCOUNTS(81),
|
||||
|
||||
ARBITRARY_DATA(90),
|
||||
GET_ARBITRARY_DATA(91);
|
||||
GET_ARBITRARY_DATA(91),
|
||||
|
||||
BLOCKS(100),
|
||||
GET_BLOCKS(101);
|
||||
|
||||
public final int value;
|
||||
public final Method fromByteBufferMethod;
|
||||
|
@@ -6,6 +6,7 @@ import java.io.UnsupportedEncodingException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.qortal.data.network.OnlineAccountData;
|
||||
import org.qortal.transform.Transformer;
|
||||
@@ -25,7 +26,7 @@ public class OnlineAccountsMessage extends Message {
|
||||
private OnlineAccountsMessage(int id, List<OnlineAccountData> onlineAccounts) {
|
||||
super(id, MessageType.ONLINE_ACCOUNTS);
|
||||
|
||||
this.onlineAccounts = onlineAccounts;
|
||||
this.onlineAccounts = onlineAccounts.stream().limit(MAX_ACCOUNT_COUNT).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public List<OnlineAccountData> getOnlineAccounts() {
|
||||
@@ -35,12 +36,9 @@ public class OnlineAccountsMessage extends Message {
|
||||
public static Message fromByteBuffer(int id, ByteBuffer bytes) throws UnsupportedEncodingException {
|
||||
final int accountCount = bytes.getInt();
|
||||
|
||||
if (accountCount > MAX_ACCOUNT_COUNT)
|
||||
return null;
|
||||
|
||||
List<OnlineAccountData> onlineAccounts = new ArrayList<>(accountCount);
|
||||
|
||||
for (int i = 0; i < accountCount; ++i) {
|
||||
for (int i = 0; i < Math.min(MAX_ACCOUNT_COUNT, accountCount); ++i) {
|
||||
long timestamp = bytes.getLong();
|
||||
|
||||
byte[] signature = new byte[Transformer.SIGNATURE_LENGTH];
|
||||
|
@@ -5,6 +5,7 @@ import java.io.FileNotFoundException;
|
||||
import java.io.FileReader;
|
||||
import java.io.IOException;
|
||||
import java.io.Reader;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
import javax.xml.bind.JAXBContext;
|
||||
@@ -132,6 +133,22 @@ public class Settings {
|
||||
* If false, sync will be blocked both ways, and they will not appear in the peers list */
|
||||
private boolean allowConnectionsWithOlderPeerVersions = true;
|
||||
|
||||
/** Minimum time (in seconds) that we should attempt to remain connected to a peer for */
|
||||
private int minPeerConnectionTime = 2 * 60; // seconds
|
||||
/** Maximum time (in seconds) that we should attempt to remain connected to a peer for */
|
||||
private int maxPeerConnectionTime = 20 * 60; // seconds
|
||||
|
||||
/** Whether to sync multiple blocks at once in normal operation */
|
||||
private boolean fastSyncEnabled = true;
|
||||
/** Whether to sync multiple blocks at once when the peer has a different chain */
|
||||
private boolean fastSyncEnabledWhenResolvingFork = true;
|
||||
/** Maximum number of blocks to request at once */
|
||||
private int maxBlocksPerRequest = 100;
|
||||
/** Maximum number of blocks this node will serve in a single response */
|
||||
private int maxBlocksPerResponse = 200;
|
||||
/** Maximum number of untrimmed blocks this node will serve in a single response */
|
||||
private int maxUntrimmedBlocksPerResponse = 10;
|
||||
|
||||
// Which blockchains this node is running
|
||||
private String blockchainConfig = null; // use default from resources
|
||||
private BitcoinNet bitcoinNet = BitcoinNet.MAIN;
|
||||
@@ -147,6 +164,7 @@ public class Settings {
|
||||
private String repositoryPath = "db";
|
||||
/** Repository connection pool size. Needs to be a bit bigger than maxNetworkThreadPoolSize */
|
||||
private int repositoryConnectionPoolSize = 100;
|
||||
private List<String> fixedNetwork;
|
||||
|
||||
// Auto-update sources
|
||||
private String[] autoUpdateRepos = new String[] {
|
||||
@@ -423,6 +441,10 @@ public class Settings {
|
||||
|
||||
public boolean getAllowConnectionsWithOlderPeerVersions() { return this.allowConnectionsWithOlderPeerVersions; }
|
||||
|
||||
public int getMinPeerConnectionTime() { return this.minPeerConnectionTime; }
|
||||
|
||||
public int getMaxPeerConnectionTime() { return this.maxPeerConnectionTime; }
|
||||
|
||||
public String getBlockchainConfig() {
|
||||
return this.blockchainConfig;
|
||||
}
|
||||
@@ -451,6 +473,20 @@ public class Settings {
|
||||
return this.repositoryConnectionPoolSize;
|
||||
}
|
||||
|
||||
public boolean isFastSyncEnabled() {
|
||||
return this.fastSyncEnabled;
|
||||
}
|
||||
|
||||
public boolean isFastSyncEnabledWhenResolvingFork() {
|
||||
return this.fastSyncEnabledWhenResolvingFork;
|
||||
}
|
||||
|
||||
public int getMaxBlocksPerRequest() { return this.maxBlocksPerRequest; }
|
||||
|
||||
public int getMaxBlocksPerResponse() { return this.maxBlocksPerResponse; }
|
||||
|
||||
public int getMaxUntrimmedBlocksPerResponse() { return this.maxUntrimmedBlocksPerResponse; }
|
||||
|
||||
public boolean isAutoUpdateEnabled() {
|
||||
return this.autoUpdateEnabled;
|
||||
}
|
||||
@@ -507,4 +543,7 @@ public class Settings {
|
||||
return this.onlineSignaturesTrimBatchSize;
|
||||
}
|
||||
|
||||
public List<String> getFixedNetwork() {
|
||||
return fixedNetwork;
|
||||
}
|
||||
}
|
||||
|
@@ -315,6 +315,10 @@ public abstract class Transaction {
|
||||
return this.transactionData;
|
||||
}
|
||||
|
||||
public void setRepository(Repository repository) {
|
||||
this.repository = repository;
|
||||
}
|
||||
|
||||
// More information
|
||||
|
||||
public static long getDeadline(TransactionData transactionData) {
|
||||
|
@@ -74,19 +74,30 @@ public class BlockTransformer extends Transformer {
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract block data and transaction data from serialized bytes.
|
||||
*
|
||||
* Extract block data and transaction data from serialized bytes containing a single block.
|
||||
*
|
||||
* @param bytes
|
||||
* @return BlockData and a List of transactions.
|
||||
* @throws TransformationException
|
||||
*/
|
||||
public static Triple<BlockData, List<TransactionData>, List<ATStateData>> fromByteBuffer(ByteBuffer byteBuffer) throws TransformationException {
|
||||
return BlockTransformer.fromByteBuffer(byteBuffer, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract block data and transaction data from serialized bytes containing one or more blocks.
|
||||
*
|
||||
* @param bytes
|
||||
* @return the next block's BlockData and a List of transactions.
|
||||
* @throws TransformationException
|
||||
*/
|
||||
public static Triple<BlockData, List<TransactionData>, List<ATStateData>> fromByteBuffer(ByteBuffer byteBuffer, boolean finalBlockInBuffer) throws TransformationException {
|
||||
int version = byteBuffer.getInt();
|
||||
|
||||
if (byteBuffer.remaining() < BASE_LENGTH + AT_BYTES_LENGTH - VERSION_LENGTH)
|
||||
if (finalBlockInBuffer && byteBuffer.remaining() < BASE_LENGTH + AT_BYTES_LENGTH - VERSION_LENGTH)
|
||||
throw new TransformationException("Byte data too short for Block");
|
||||
|
||||
if (byteBuffer.remaining() > BlockChain.getInstance().getMaxBlockSize())
|
||||
if (finalBlockInBuffer && byteBuffer.remaining() > BlockChain.getInstance().getMaxBlockSize())
|
||||
throw new TransformationException("Byte data too long for Block");
|
||||
|
||||
long timestamp = byteBuffer.getLong();
|
||||
@@ -210,7 +221,8 @@ public class BlockTransformer extends Transformer {
|
||||
byteBuffer.get(onlineAccountsSignatures);
|
||||
}
|
||||
|
||||
if (byteBuffer.hasRemaining())
|
||||
// We should only complain about excess byte data if we aren't expecting more blocks in this ByteBuffer
|
||||
if (finalBlockInBuffer && byteBuffer.hasRemaining())
|
||||
throw new TransformationException("Excess byte data found after parsing Block");
|
||||
|
||||
// We don't have a height!
|
||||
|
@@ -69,11 +69,13 @@ function fetch_and_process_blocks {
|
||||
online_accounts_count=$(echo "${block_minting_info}" | jq -r .onlineAccountsCount)
|
||||
key_distance_ratio=$(echo "${block_minting_info}" | jq -r .keyDistanceRatio)
|
||||
time_delta=$(echo "${block_minting_info}" | jq -r .timeDelta)
|
||||
timestamp=$(echo "${block_minting_info}" | jq -r .timestamp)
|
||||
|
||||
time_offset=$(calculate_time_offset "${key_distance_ratio}")
|
||||
block_time=$((target-deviation+time_offset))
|
||||
|
||||
echo "=== BLOCK ${height} ==="
|
||||
echo "Timestamp: ${timestamp}"
|
||||
echo "Minter level: ${minter_level}"
|
||||
echo "Online accounts: ${online_accounts_count}"
|
||||
echo "Key distance ratio: ${key_distance_ratio}"
|
||||
|
@@ -1,7 +1,8 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Change this to where AdvancedInstaller outputs built EXE installers
|
||||
WINDOWS_INSTALLER_DIR=/home/transfer/Qortal/Qortal-SetupFiles
|
||||
SCRIPT_DIR=$(dirname $(realpath "$0"))
|
||||
WINDOWS_INSTALLER_DIR="${SCRIPT_DIR}/../WindowsInstaller/Qortal-SetupFiles"
|
||||
|
||||
set -e
|
||||
shopt -s expand_aliases
|
||||
@@ -16,6 +17,16 @@ saved_pwd=$PWD
|
||||
|
||||
alias SHA256='(sha256 -q || sha256sum | cut -d" " -f1) 2>/dev/null'
|
||||
|
||||
function 3hash {
|
||||
local zip_src=$1
|
||||
local md5hash=$(md5 ${zip_src} | awk '{ print $NF }')
|
||||
local sha1hash=$(shasum ${zip_src} | awk '{ print $1 }')
|
||||
local sha256hash=$(sha256sum ${zip_src} | awk '{ print $1 }')
|
||||
echo "\`MD5: ${md5hash}\`"
|
||||
echo "\`SHA1: ${sha1hash}\`"
|
||||
echo "\`SHA256: ${sha256hash}\`"
|
||||
}
|
||||
|
||||
# Check we are within a git repo
|
||||
git_dir=$( git rev-parse --show-toplevel )
|
||||
if [ -z "${git_dir}" ]; then
|
||||
@@ -27,7 +38,7 @@ fi
|
||||
cd ${git_dir}
|
||||
|
||||
# Check we are in 'master' branch
|
||||
# branch_name=$( git symbolic-ref -q HEAD )
|
||||
# branch_name=$( git symbolic-ref -q HEAD ) || echo "Cannot determine branch name" && exit 1
|
||||
# branch_name=${branch_name##refs/heads/}
|
||||
# if [ "${branch_name}" != "master" ]; then
|
||||
# echo "Unexpected current branch '${branch_name}' - expecting 'master'"
|
||||
@@ -56,7 +67,7 @@ git_url=https://github.com/${git_url##*:}
|
||||
git_url=${git_url%%.git}
|
||||
|
||||
# Check for EXE
|
||||
exe=${project^}-${git_tag#v}.exe
|
||||
exe=${project}-${git_tag#v}.exe
|
||||
exe_src="${WINDOWS_INSTALLER_DIR}/${exe}"
|
||||
if [ ! -r "${exe_src}" ]; then
|
||||
echo "Cannot find EXE installer at ${exe_src}"
|
||||
|
@@ -21,13 +21,13 @@ fi
|
||||
cd ${git_dir}
|
||||
|
||||
# Check we are in 'master' branch
|
||||
branch_name=$( git symbolic-ref -q HEAD )
|
||||
branch_name=${branch_name##refs/heads/}
|
||||
echo "Current git branch: ${branch_name}"
|
||||
if [ "${branch_name}" != "master" ]; then
|
||||
echo "Unexpected current branch '${branch_name}' - expecting 'master'"
|
||||
exit 1
|
||||
fi
|
||||
# branch_name=$( git symbolic-ref -q HEAD ) || echo "Cannot determine branch name" && exit 1
|
||||
# branch_name=${branch_name##refs/heads/}
|
||||
# echo "Current git branch: ${branch_name}"
|
||||
# if [ "${branch_name}" != "master" ]; then
|
||||
# echo "Unexpected current branch '${branch_name}' - expecting 'master'"
|
||||
# exit 1
|
||||
# fi
|
||||
|
||||
# Determine project name
|
||||
project=$( perl -n -e 'if (m/<artifactId>(\w+)<.artifactId>/) { print $1; exit }' pom.xml $)
|
||||
@@ -60,7 +60,7 @@ git show HEAD:stop.sh > ${build_dir}/stop.sh
|
||||
|
||||
printf "{\n}\n" > ${build_dir}/settings.json
|
||||
|
||||
touch -d ${commit_ts%%+??:??} ${build_dir} ${build_dir}/*
|
||||
gtouch -d ${commit_ts%%+??:??} ${build_dir} ${build_dir}/*
|
||||
|
||||
rm -f ${saved_pwd}/${project}.zip
|
||||
(cd ${build_dir}/..; 7z a -r -tzip ${saved_pwd}/${project}-${git_tag#v}.zip ${project}/)
|
||||
|
Reference in New Issue
Block a user