mirror of
https://github.com/Qortal/qortal.git
synced 2025-07-30 05:31:23 +00:00
Compare commits
107 Commits
v3.2.2
...
qdn-direct
Author | SHA1 | Date | |
---|---|---|---|
|
aaa0b25106 | ||
|
1d7203a6fb | ||
|
1030b00f0a | ||
|
0c16d1fc11 | ||
|
ed04375385 | ||
|
6e49d20383 | ||
|
dc34eed203 | ||
|
fbe4f3fad8 | ||
|
599877195b | ||
|
0695039ee3 | ||
|
a4bcd4451c | ||
|
e5b4b61832 | ||
|
dd55dc277b | ||
|
81ef1ae964 | ||
|
46701e4de7 | ||
|
568497e1c5 | ||
|
f3f8e0013d | ||
|
d03c145189 | ||
|
682a5fde94 | ||
|
cca5bac30a | ||
|
64e102a8c6 | ||
|
df290950ea | ||
|
ae64be4802 | ||
|
d98678fc5f | ||
|
311f41c610 | ||
|
0a156c76a2 | ||
|
70eaaa9e3b | ||
|
3e622f7185 | ||
|
3f12be50ac | ||
|
68412b49a1 | ||
|
c9b2620461 | ||
|
337b03aa68 | ||
|
df3f16ccf1 | ||
|
22aa5c41b5 | ||
|
8e09567221 | ||
|
3505788d42 | ||
|
91e0c9b940 | ||
|
00996b047f | ||
|
44fc0f367d | ||
|
b0e6259073 | ||
|
6255b2a907 | ||
|
a5fb0be274 | ||
|
e835f6d998 | ||
|
3d99f86630 | ||
|
8d1a58ec06 | ||
|
2e5a7cb5a1 | ||
|
895f02f178 | ||
|
c59869982b | ||
|
3b3368f950 | ||
|
3f02c760c2 | ||
|
fee603e500 | ||
|
ad31d8014d | ||
|
58a0ac74d2 | ||
|
8388aa9c23 | ||
|
c1894d8c00 | ||
|
f7f9cdc518 | ||
|
850d7f8220 | ||
|
051043283c | ||
|
15bc69de01 | ||
|
ee3cfa4d6d | ||
|
df1f3079a5 | ||
|
d9ae8a5552 | ||
|
2326c31ee7 | ||
|
91cb0f30dd | ||
|
c0307c352c | ||
|
8fd7c1b313 | ||
|
b8147659b1 | ||
|
7a1bac682f | ||
|
9fdb7c977f | ||
|
4f3948323b | ||
|
70fcc1f712 | ||
|
f20fe9199f | ||
|
91dee4a3b8 | ||
|
0b89b8084e | ||
|
a5a80302b2 | ||
|
e61a24ee7b | ||
|
55ed342b59 | ||
|
3c6f79eec0 | ||
|
590800ac1d | ||
|
95c412b946 | ||
|
a232395750 | ||
|
6edbc8b6a5 | ||
|
f8ffeed302 | ||
|
e2ee68427c | ||
|
74ff23239d | ||
|
f1fa2ba2f6 | ||
|
e1522cec94 | ||
|
8841b3cbb1 | ||
|
94260bd93f | ||
|
15ff8af7ac | ||
|
d420033b36 | ||
|
bda63f0310 | ||
|
54add26ccb | ||
|
089b068362 | ||
|
fe474b4507 | ||
|
bbe15b563c | ||
|
59025b8f47 | ||
|
1b42c5edb1 | ||
|
362335913d | ||
|
4340dac595 | ||
|
f3e1fc884c | ||
|
39c06d8817 | ||
|
91cee36c21 | ||
|
6bef883942 | ||
|
25ba2406c0 | ||
|
e604a19bce | ||
|
eb9b94b9c6 |
@@ -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:{CACF3376-2BAD-4028-8FB7-896EB938A9AF} 1049:{7C3C3F91-A84E-4740-96F1-E4948E4F1C8E} 2052:{1A3E70EF-EC7A-4500-A3DF-9F63B0AF210F} 2057:{F6397B6B-FAC0-4A76-AA8B-98A56ECC9A50} " Type="16"/>
|
||||
<ROW Property="ProductCode" Value="1033:{8C9CFB9D-BC4C-4142-A5A5-5551BF3B9467} 1049:{4A5BDDD9-ED71-431A-A46F-D19E9DE17216} 2052:{0B9DCE00-BE23-434D-BD6A-1CFA6AB3CA43} 2057:{23D81967-556A-41B8-9981-A739E2820624} " Type="16"/>
|
||||
<ROW Property="ProductLanguage" Value="2057"/>
|
||||
<ROW Property="ProductName" Value="Qortal"/>
|
||||
<ROW Property="ProductVersion" Value="3.2.1" Type="32"/>
|
||||
<ROW Property="ProductVersion" Value="3.2.5" 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="{25A8E4DC-F9CC-4CE3-B193-56F22590C19C}" Directory_="APPDIR" Attributes="260" KeyPath="DisplayName" Options="1"/>
|
||||
<ROW Component="AI_CustomARPName" ComponentId="{D5A1DC7D-914F-4425-8BA6-A1AE05D0F361}" 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"/>
|
||||
|
17
pom.xml
17
pom.xml
@@ -3,7 +3,7 @@
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>org.qortal</groupId>
|
||||
<artifactId>qortal</artifactId>
|
||||
<version>3.2.2</version>
|
||||
<version>3.2.5</version>
|
||||
<packaging>jar</packaging>
|
||||
<properties>
|
||||
<skipTests>true</skipTests>
|
||||
@@ -21,6 +21,8 @@
|
||||
<dagger.version>1.2.2</dagger.version>
|
||||
<guava.version>28.1-jre</guava.version>
|
||||
<hsqldb.version>2.5.1</hsqldb.version>
|
||||
<homoglyph.version>1.2.1</homoglyph.version>
|
||||
<icu4j.version>70.1</icu4j.version>
|
||||
<upnp.version>1.1</upnp.version>
|
||||
<jersey.version>2.29.1</jersey.version>
|
||||
<jetty.version>9.4.29.v20200521</jetty.version>
|
||||
@@ -568,7 +570,18 @@
|
||||
<dependency>
|
||||
<groupId>net.codebox</groupId>
|
||||
<artifactId>homoglyph</artifactId>
|
||||
<version>1.2.0</version>
|
||||
<version>${homoglyph.version}</version>
|
||||
</dependency>
|
||||
<!-- Unicode support -->
|
||||
<dependency>
|
||||
<groupId>com.ibm.icu</groupId>
|
||||
<artifactId>icu4j</artifactId>
|
||||
<version>${icu4j.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.ibm.icu</groupId>
|
||||
<artifactId>icu4j-charset</artifactId>
|
||||
<version>${icu4j.version}</version>
|
||||
</dependency>
|
||||
<!-- Jetty -->
|
||||
<dependency>
|
||||
|
@@ -205,6 +205,12 @@ public class Account {
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Returns account's blockMinted (0+) or null if account not found in repository. */
|
||||
public Integer getBlocksMinted() throws DataException {
|
||||
return this.repository.getAccountRepository().getMintedBlockCount(this.address);
|
||||
}
|
||||
|
||||
|
||||
/** Returns whether account can build reward-shares.
|
||||
* <p>
|
||||
* To be able to create reward-shares, the account needs to pass at least one of these tests:<br>
|
||||
|
@@ -381,6 +381,10 @@ public class AdminResource {
|
||||
) @QueryParam("limit") Integer limit, @Parameter(
|
||||
ref = "offset"
|
||||
) @QueryParam("offset") Integer offset, @Parameter(
|
||||
name = "tail",
|
||||
description = "Fetch most recent log lines",
|
||||
schema = @Schema(type = "boolean")
|
||||
) @QueryParam("tail") Boolean tail, @Parameter(
|
||||
ref = "reverse"
|
||||
) @QueryParam("reverse") Boolean reverse) {
|
||||
LoggerContext loggerContext = (LoggerContext) LogManager.getContext();
|
||||
@@ -396,6 +400,13 @@ public class AdminResource {
|
||||
if (reverse != null && reverse)
|
||||
logLines = Lists.reverse(logLines);
|
||||
|
||||
// Tail mode - return the last X lines (where X = limit)
|
||||
if (tail != null && tail) {
|
||||
if (limit != null && limit > 0) {
|
||||
offset = logLines.size() - limit;
|
||||
}
|
||||
}
|
||||
|
||||
// offset out of bounds?
|
||||
if (offset != null && (offset < 0 || offset >= logLines.size()))
|
||||
return "";
|
||||
@@ -416,7 +427,7 @@ public class AdminResource {
|
||||
|
||||
limit = Math.min(limit, logLines.size());
|
||||
|
||||
logLines.subList(limit - 1, logLines.size()).clear();
|
||||
logLines.subList(limit, logLines.size()).clear();
|
||||
|
||||
return String.join("\n", logLines);
|
||||
} catch (IOException e) {
|
||||
@@ -588,10 +599,6 @@ public class AdminResource {
|
||||
public String importRepository(@HeaderParam(Security.API_KEY_HEADER) String apiKey, String filename) {
|
||||
Security.checkApiCallAllowed(request);
|
||||
|
||||
// Hard-coded because it's too dangerous to allow user-supplied filenames in weaker security contexts
|
||||
if (Settings.getInstance().getApiKey() == null)
|
||||
filename = "qortal-backup/TradeBotStates.json";
|
||||
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
ReentrantLock blockchainLock = Controller.getInstance().getBlockchainLock();
|
||||
|
||||
|
@@ -1267,13 +1267,19 @@ public class ArbitraryResource {
|
||||
// Determine and add the status of each resource
|
||||
List<ArbitraryResourceInfo> updatedResources = new ArrayList<>();
|
||||
for (ArbitraryResourceInfo resourceInfo : resources) {
|
||||
ArbitraryDataResource resource = new ArbitraryDataResource(resourceInfo.name, ResourceIdType.NAME,
|
||||
resourceInfo.service, resourceInfo.identifier);
|
||||
ArbitraryResourceStatus status = resource.getStatus(true);
|
||||
if (status != null) {
|
||||
resourceInfo.status = status;
|
||||
try {
|
||||
ArbitraryDataResource resource = new ArbitraryDataResource(resourceInfo.name, ResourceIdType.NAME,
|
||||
resourceInfo.service, resourceInfo.identifier);
|
||||
ArbitraryResourceStatus status = resource.getStatus(true);
|
||||
if (status != null) {
|
||||
resourceInfo.status = status;
|
||||
}
|
||||
updatedResources.add(resourceInfo);
|
||||
|
||||
} catch (Exception e) {
|
||||
// Catch and log all exceptions, since some systems are experiencing 500 errors when including statuses
|
||||
LOGGER.info("Caught exception when adding status to resource %s: %s", resourceInfo, e.toString());
|
||||
}
|
||||
updatedResources.add(resourceInfo);
|
||||
}
|
||||
return updatedResources;
|
||||
}
|
||||
|
@@ -98,7 +98,15 @@ public class GroupsResource {
|
||||
ref = "reverse"
|
||||
) @QueryParam("reverse") Boolean reverse) {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
return repository.getGroupRepository().getAllGroups(limit, offset, reverse);
|
||||
List<GroupData> allGroupData = repository.getGroupRepository().getAllGroups(limit, offset, reverse);
|
||||
allGroupData.forEach(groupData -> {
|
||||
try {
|
||||
groupData.memberCount = repository.getGroupRepository().countGroupMembers(groupData.getGroupId());
|
||||
} catch (DataException e) {
|
||||
// Exclude memberCount for this group
|
||||
}
|
||||
});
|
||||
return allGroupData;
|
||||
} catch (DataException e) {
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e);
|
||||
}
|
||||
@@ -150,7 +158,15 @@ public class GroupsResource {
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_ADDRESS);
|
||||
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
return repository.getGroupRepository().getGroupsWithMember(member);
|
||||
List<GroupData> allGroupData = repository.getGroupRepository().getGroupsWithMember(member);
|
||||
allGroupData.forEach(groupData -> {
|
||||
try {
|
||||
groupData.memberCount = repository.getGroupRepository().countGroupMembers(groupData.getGroupId());
|
||||
} catch (DataException e) {
|
||||
// Exclude memberCount for this group
|
||||
}
|
||||
});
|
||||
return allGroupData;
|
||||
} catch (DataException e) {
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e);
|
||||
}
|
||||
@@ -177,6 +193,7 @@ public class GroupsResource {
|
||||
if (groupData == null)
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.GROUP_UNKNOWN);
|
||||
|
||||
groupData.memberCount = repository.getGroupRepository().countGroupMembers(groupId);
|
||||
return groupData;
|
||||
} catch (DataException e) {
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e);
|
||||
@@ -922,4 +939,4 @@ public class GroupsResource {
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@@ -20,6 +20,11 @@ import javax.ws.rs.*;
|
||||
import javax.ws.rs.core.Context;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
|
||||
import org.apache.logging.log4j.Level;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.core.config.Configuration;
|
||||
import org.apache.logging.log4j.core.config.LoggerConfig;
|
||||
import org.apache.logging.log4j.core.LoggerContext;
|
||||
import org.qortal.api.*;
|
||||
import org.qortal.api.model.ConnectedPeer;
|
||||
import org.qortal.api.model.PeersSummary;
|
||||
@@ -127,9 +132,29 @@ public class PeersResource {
|
||||
}
|
||||
)
|
||||
@SecurityRequirement(name = "apiKey")
|
||||
public ExecuteProduceConsume.StatsSnapshot getEngineStats(@HeaderParam(Security.API_KEY_HEADER) String apiKey) {
|
||||
public ExecuteProduceConsume.StatsSnapshot getEngineStats(@HeaderParam(Security.API_KEY_HEADER) String apiKey, @QueryParam("newLoggingLevel") Level newLoggingLevel) {
|
||||
Security.checkApiCallAllowed(request);
|
||||
|
||||
if (newLoggingLevel != null) {
|
||||
final LoggerContext ctx = (LoggerContext) LogManager.getContext(false);
|
||||
final Configuration config = ctx.getConfiguration();
|
||||
|
||||
String epcClassName = "org.qortal.network.Network.NetworkProcessor";
|
||||
LoggerConfig loggerConfig = config.getLoggerConfig(epcClassName);
|
||||
LoggerConfig specificConfig = loggerConfig;
|
||||
|
||||
// We need a specific configuration for this logger,
|
||||
// otherwise we would change the level of all other loggers
|
||||
// having the original configuration as parent as well
|
||||
if (!loggerConfig.getName().equals(epcClassName)) {
|
||||
specificConfig = new LoggerConfig(epcClassName, newLoggingLevel, true);
|
||||
specificConfig.setParent(loggerConfig);
|
||||
config.addLogger(epcClassName, specificConfig);
|
||||
}
|
||||
specificConfig.setLevel(newLoggingLevel);
|
||||
ctx.updateLoggers();
|
||||
}
|
||||
|
||||
return Network.getInstance().getStatsSnapshot();
|
||||
}
|
||||
|
||||
|
@@ -93,10 +93,12 @@ public class ArbitraryDataFile {
|
||||
File outputFile = outputFilePath.toFile();
|
||||
try (FileOutputStream outputStream = new FileOutputStream(outputFile)) {
|
||||
outputStream.write(fileContent);
|
||||
outputStream.close();
|
||||
this.filePath = outputFilePath;
|
||||
// Verify hash
|
||||
if (!this.hash58.equals(this.digest58())) {
|
||||
LOGGER.error("Hash {} does not match file digest {}", this.hash58, this.digest58());
|
||||
String digest58 = this.digest58();
|
||||
if (!this.hash58.equals(digest58)) {
|
||||
LOGGER.error("Hash {} does not match file digest {} for signature: {}", this.hash58, digest58, Base58.encode(signature));
|
||||
this.delete();
|
||||
throw new DataException("Data file digest validation failed");
|
||||
}
|
||||
@@ -478,6 +480,14 @@ public class ArbitraryDataFile {
|
||||
|
||||
// Read the metadata
|
||||
List<byte[]> chunks = metadata.getChunks();
|
||||
|
||||
// If the chunks array is empty, then this resource has no chunks,
|
||||
// so we must return false to avoid confusing the caller.
|
||||
if (chunks.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Otherwise, we need to check each chunk individually
|
||||
for (byte[] chunkHash : chunks) {
|
||||
ArbitraryDataFileChunk chunk = ArbitraryDataFileChunk.fromHash(chunkHash, this.signature);
|
||||
if (!chunk.exists()) {
|
||||
|
@@ -551,7 +551,7 @@ public class QortalATAPI extends API {
|
||||
* <p>
|
||||
* Otherwise, assume B is a public key.
|
||||
*/
|
||||
private Account getAccountFromB(MachineState state) {
|
||||
/*package*/ Account getAccountFromB(MachineState state) {
|
||||
byte[] bBytes = this.getB(state);
|
||||
|
||||
if ((bBytes[0] == Crypto.ADDRESS_VERSION || bBytes[0] == Crypto.AT_ADDRESS_VERSION)
|
||||
|
@@ -10,9 +10,11 @@ import org.ciyam.at.ExecutionException;
|
||||
import org.ciyam.at.FunctionData;
|
||||
import org.ciyam.at.IllegalFunctionCodeException;
|
||||
import org.ciyam.at.MachineState;
|
||||
import org.qortal.account.Account;
|
||||
import org.qortal.crosschain.Bitcoin;
|
||||
import org.qortal.crypto.Crypto;
|
||||
import org.qortal.data.transaction.TransactionData;
|
||||
import org.qortal.repository.DataException;
|
||||
import org.qortal.settings.Settings;
|
||||
|
||||
/**
|
||||
@@ -160,6 +162,68 @@ public enum QortalFunctionCode {
|
||||
protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException {
|
||||
convertAddressInB(Crypto.ADDRESS_VERSION, state);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Returns account level of account in B.<br>
|
||||
* <tt>0x0520</tt><br>
|
||||
* B should contain either Qortal address or public key,<br>
|
||||
* e.g. as a result of calling function {@link org.ciyam.at.FunctionCode#PUT_ADDRESS_FROM_TX_IN_A_INTO_B}</code>.
|
||||
* <p></p>
|
||||
* Returns account level, or -1 if account unknown.
|
||||
* <p></p>
|
||||
* @see QortalATAPI#getAccountFromB(MachineState)
|
||||
*/
|
||||
GET_ACCOUNT_LEVEL_FROM_ACCOUNT_IN_B(0x0520, 0, true) {
|
||||
@Override
|
||||
protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException {
|
||||
QortalATAPI api = (QortalATAPI) state.getAPI();
|
||||
Account account = api.getAccountFromB(state);
|
||||
|
||||
Integer accountLevel = null;
|
||||
|
||||
if (account != null) {
|
||||
try {
|
||||
accountLevel = account.getLevel();
|
||||
} catch (DataException e) {
|
||||
throw new RuntimeException("AT API unable to fetch account level?", e);
|
||||
}
|
||||
}
|
||||
|
||||
functionData.returnValue = accountLevel != null
|
||||
? accountLevel.longValue()
|
||||
: -1;
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Returns account's minted block count of account in B.<br>
|
||||
* <tt>0x0521</tt><br>
|
||||
* B should contain either Qortal address or public key,<br>
|
||||
* e.g. as a result of calling function {@link org.ciyam.at.FunctionCode#PUT_ADDRESS_FROM_TX_IN_A_INTO_B}</code>.
|
||||
* <p></p>
|
||||
* Returns account level, or -1 if account unknown.
|
||||
* <p></p>
|
||||
* @see QortalATAPI#getAccountFromB(MachineState)
|
||||
*/
|
||||
GET_BLOCKS_MINTED_FROM_ACCOUNT_IN_B(0x0521, 0, true) {
|
||||
@Override
|
||||
protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException {
|
||||
QortalATAPI api = (QortalATAPI) state.getAPI();
|
||||
Account account = api.getAccountFromB(state);
|
||||
|
||||
Integer blocksMinted = null;
|
||||
|
||||
if (account != null) {
|
||||
try {
|
||||
blocksMinted = account.getBlocksMinted();
|
||||
} catch (DataException e) {
|
||||
throw new RuntimeException("AT API unable to fetch account's minted block count?", e);
|
||||
}
|
||||
}
|
||||
|
||||
functionData.returnValue = blocksMinted != null
|
||||
? blocksMinted.longValue()
|
||||
: -1;
|
||||
}
|
||||
};
|
||||
|
||||
public final short value;
|
||||
|
@@ -73,9 +73,13 @@ public class BlockChain {
|
||||
}
|
||||
|
||||
// Custom transaction fees
|
||||
@XmlJavaTypeAdapter(value = org.qortal.api.AmountTypeAdapter.class)
|
||||
private long nameRegistrationUnitFee;
|
||||
private long nameRegistrationUnitFeeTimestamp;
|
||||
/** Unit fees by transaction timestamp */
|
||||
public static class UnitFeesByTimestamp {
|
||||
public long timestamp;
|
||||
@XmlJavaTypeAdapter(value = org.qortal.api.AmountTypeAdapter.class)
|
||||
public long fee;
|
||||
}
|
||||
private List<UnitFeesByTimestamp> nameRegistrationUnitFees;
|
||||
|
||||
/** Map of which blockchain features are enabled when (height/timestamp) */
|
||||
@XmlJavaTypeAdapter(StringLongMapXmlAdapter.class)
|
||||
@@ -306,16 +310,6 @@ public class BlockChain {
|
||||
return this.maxBlockSize;
|
||||
}
|
||||
|
||||
// Custom transaction fees
|
||||
public long getNameRegistrationUnitFee() {
|
||||
return this.nameRegistrationUnitFee;
|
||||
}
|
||||
|
||||
public long getNameRegistrationUnitFeeTimestamp() {
|
||||
// FUTURE: we could use a separate structure to indicate fee adjustments for different transaction types
|
||||
return this.nameRegistrationUnitFeeTimestamp;
|
||||
}
|
||||
|
||||
/** Returns true if approval-needing transaction types require a txGroupId other than NO_GROUP. */
|
||||
public boolean getRequireGroupForApproval() {
|
||||
return this.requireGroupForApproval;
|
||||
@@ -430,6 +424,15 @@ public class BlockChain {
|
||||
throw new IllegalStateException(String.format("No block timing info available for height %d", ourHeight));
|
||||
}
|
||||
|
||||
public long getNameRegistrationUnitFeeAtTimestamp(long ourTimestamp) {
|
||||
for (int i = nameRegistrationUnitFees.size() - 1; i >= 0; --i)
|
||||
if (nameRegistrationUnitFees.get(i).timestamp <= ourTimestamp)
|
||||
return nameRegistrationUnitFees.get(i).fee;
|
||||
|
||||
// Default to system-wide unit fee
|
||||
return this.getUnitFee();
|
||||
}
|
||||
|
||||
/** Validate blockchain config read from JSON */
|
||||
private void validateConfig() {
|
||||
if (this.genesisInfo == null)
|
||||
|
@@ -219,7 +219,7 @@ public class BlockMinter extends Thread {
|
||||
// The last iteration found a higher weight block in the network, so sleep for a while
|
||||
// to allow is to sync the higher weight chain. We are sleeping here rather than when
|
||||
// detected as we don't want to hold the blockchain lock open.
|
||||
LOGGER.info("Sleeping for 10 seconds...");
|
||||
LOGGER.debug("Sleeping for 10 seconds...");
|
||||
Thread.sleep(10 * 1000L);
|
||||
}
|
||||
|
||||
@@ -328,13 +328,13 @@ public class BlockMinter extends Thread {
|
||||
// If less than 30 seconds has passed since first detection the higher weight chain,
|
||||
// we should skip our block submission to give us the opportunity to sync to the better chain
|
||||
if (NTP.getTime() - timeOfLastLowWeightBlock < 30*1000L) {
|
||||
LOGGER.info("Higher weight chain found in peers, so not signing a block this round");
|
||||
LOGGER.info("Time since detected: {}", NTP.getTime() - timeOfLastLowWeightBlock);
|
||||
LOGGER.debug("Higher weight chain found in peers, so not signing a block this round");
|
||||
LOGGER.debug("Time since detected: {}ms", NTP.getTime() - timeOfLastLowWeightBlock);
|
||||
continue;
|
||||
}
|
||||
else {
|
||||
// More than 30 seconds have passed, so we should submit our block candidate anyway.
|
||||
LOGGER.info("More than 30 seconds passed, so proceeding to submit block candidate...");
|
||||
LOGGER.debug("More than 30 seconds passed, so proceeding to submit block candidate...");
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
@@ -58,6 +58,7 @@ import org.qortal.repository.hsqldb.HSQLDBRepositoryFactory;
|
||||
import org.qortal.settings.Settings;
|
||||
import org.qortal.transaction.Transaction;
|
||||
import org.qortal.transaction.Transaction.TransactionType;
|
||||
import org.qortal.transform.TransformationException;
|
||||
import org.qortal.utils.*;
|
||||
|
||||
public class Controller extends Thread {
|
||||
@@ -573,15 +574,20 @@ public class Controller extends Thread {
|
||||
MessageType.INFO);
|
||||
|
||||
LOGGER.info("Starting scheduled repository maintenance. This can take a while...");
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
int attempts = 0;
|
||||
while (attempts <= 5) {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
attempts++;
|
||||
|
||||
// Timeout if the database isn't ready for maintenance after 60 seconds
|
||||
long timeout = 60 * 1000L;
|
||||
repository.performPeriodicMaintenance(timeout);
|
||||
// Timeout if the database isn't ready for maintenance after 60 seconds
|
||||
long timeout = 60 * 1000L;
|
||||
repository.performPeriodicMaintenance(timeout);
|
||||
|
||||
LOGGER.info("Scheduled repository maintenance completed");
|
||||
} catch (DataException | TimeoutException e) {
|
||||
LOGGER.error("Scheduled repository maintenance failed", e);
|
||||
LOGGER.info("Scheduled repository maintenance completed");
|
||||
break;
|
||||
} catch (DataException | TimeoutException e) {
|
||||
LOGGER.info("Scheduled repository maintenance failed. Retrying up to 5 times...", e);
|
||||
}
|
||||
}
|
||||
|
||||
// Get a new random interval
|
||||
@@ -675,7 +681,7 @@ public class Controller extends Thread {
|
||||
public static final Predicate<Peer> hasInferiorChainTip = peer -> {
|
||||
final PeerChainTipData peerChainTipData = peer.getChainTipData();
|
||||
final List<ByteArray> inferiorChainTips = Synchronizer.getInstance().inferiorChainSignatures;
|
||||
return peerChainTipData == null || peerChainTipData.getLastBlockSignature() == null || inferiorChainTips.contains(new ByteArray(peerChainTipData.getLastBlockSignature()));
|
||||
return peerChainTipData == null || peerChainTipData.getLastBlockSignature() == null || inferiorChainTips.contains(ByteArray.wrap(peerChainTipData.getLastBlockSignature()));
|
||||
};
|
||||
|
||||
public static final Predicate<Peer> hasOldVersion = peer -> {
|
||||
@@ -1203,7 +1209,7 @@ public class Controller extends Thread {
|
||||
byte[] signature = getBlockMessage.getSignature();
|
||||
this.stats.getBlockMessageStats.requests.incrementAndGet();
|
||||
|
||||
ByteArray signatureAsByteArray = new ByteArray(signature);
|
||||
ByteArray signatureAsByteArray = ByteArray.wrap(signature);
|
||||
|
||||
CachedBlockMessage cachedBlockMessage = this.blockMessageCache.get(signatureAsByteArray);
|
||||
int blockCacheSize = Settings.getInstance().getBlockCacheSize();
|
||||
@@ -1213,7 +1219,7 @@ public class Controller extends Thread {
|
||||
this.stats.getBlockMessageStats.cacheHits.incrementAndGet();
|
||||
|
||||
// We need to duplicate it to prevent multiple threads setting ID on the same message
|
||||
CachedBlockMessage clonedBlockMessage = cachedBlockMessage.cloneWithNewId(message.getId());
|
||||
CachedBlockMessage clonedBlockMessage = Message.cloneWithNewId(cachedBlockMessage, message.getId());
|
||||
|
||||
if (!peer.sendMessage(clonedBlockMessage))
|
||||
peer.disconnect("failed to send block");
|
||||
@@ -1272,7 +1278,6 @@ public class Controller extends Thread {
|
||||
CachedBlockMessage blockMessage = new CachedBlockMessage(block);
|
||||
blockMessage.setId(message.getId());
|
||||
|
||||
// This call also causes the other needed data to be pulled in from repository
|
||||
if (!peer.sendMessage(blockMessage)) {
|
||||
peer.disconnect("failed to send block");
|
||||
// Don't fall-through to caching because failure to send might be from failure to build message
|
||||
@@ -1283,10 +1288,12 @@ public class Controller extends Thread {
|
||||
if (getChainHeight() - blockData.getHeight() <= blockCacheSize) {
|
||||
this.stats.getBlockMessageStats.cacheFills.incrementAndGet();
|
||||
|
||||
this.blockMessageCache.put(new ByteArray(blockData.getSignature()), blockMessage);
|
||||
this.blockMessageCache.put(ByteArray.wrap(blockData.getSignature()), blockMessage);
|
||||
}
|
||||
} catch (DataException e) {
|
||||
LOGGER.error(String.format("Repository issue while send block %s to peer %s", Base58.encode(signature), peer), e);
|
||||
LOGGER.error(String.format("Repository issue while sending block %s to peer %s", Base58.encode(signature), peer), e);
|
||||
} catch (TransformationException e) {
|
||||
LOGGER.error(String.format("Serialization issue while sending block %s to peer %s", Base58.encode(signature), peer), e);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -33,7 +33,7 @@ import org.qortal.network.message.GetBlockSummariesMessage;
|
||||
import org.qortal.network.message.GetSignaturesV2Message;
|
||||
import org.qortal.network.message.Message;
|
||||
import org.qortal.network.message.SignaturesMessage;
|
||||
import org.qortal.network.message.Message.MessageType;
|
||||
import org.qortal.network.message.MessageType;
|
||||
import org.qortal.repository.DataException;
|
||||
import org.qortal.repository.Repository;
|
||||
import org.qortal.repository.RepositoryManager;
|
||||
@@ -314,7 +314,7 @@ public class Synchronizer extends Thread {
|
||||
|
||||
case INFERIOR_CHAIN: {
|
||||
// Update our list of inferior chain tips
|
||||
ByteArray inferiorChainSignature = new ByteArray(peer.getChainTipData().getLastBlockSignature());
|
||||
ByteArray inferiorChainSignature = ByteArray.wrap(peer.getChainTipData().getLastBlockSignature());
|
||||
if (!inferiorChainSignatures.contains(inferiorChainSignature))
|
||||
inferiorChainSignatures.add(inferiorChainSignature);
|
||||
|
||||
@@ -343,7 +343,7 @@ public class Synchronizer extends Thread {
|
||||
// fall-through...
|
||||
case NOTHING_TO_DO: {
|
||||
// Update our list of inferior chain tips
|
||||
ByteArray inferiorChainSignature = new ByteArray(peer.getChainTipData().getLastBlockSignature());
|
||||
ByteArray inferiorChainSignature = ByteArray.wrap(peer.getChainTipData().getLastBlockSignature());
|
||||
if (!inferiorChainSignatures.contains(inferiorChainSignature))
|
||||
inferiorChainSignatures.add(inferiorChainSignature);
|
||||
|
||||
@@ -419,7 +419,7 @@ public class Synchronizer extends Thread {
|
||||
|
||||
public void addInferiorChainSignature(byte[] inferiorSignature) {
|
||||
// Update our list of inferior chain tips
|
||||
ByteArray inferiorChainSignature = new ByteArray(inferiorSignature);
|
||||
ByteArray inferiorChainSignature = ByteArray.wrap(inferiorSignature);
|
||||
if (!inferiorChainSignatures.contains(inferiorChainSignature))
|
||||
inferiorChainSignatures.add(inferiorChainSignature);
|
||||
}
|
||||
|
@@ -12,6 +12,7 @@ import org.qortal.repository.DataException;
|
||||
import org.qortal.repository.Repository;
|
||||
import org.qortal.repository.RepositoryManager;
|
||||
import org.qortal.transaction.Transaction;
|
||||
import org.qortal.transform.TransformationException;
|
||||
import org.qortal.utils.Base58;
|
||||
import org.qortal.utils.NTP;
|
||||
|
||||
@@ -289,7 +290,9 @@ public class TransactionImporter extends Thread {
|
||||
if (!peer.sendMessage(transactionMessage))
|
||||
peer.disconnect("failed to send transaction");
|
||||
} catch (DataException e) {
|
||||
LOGGER.error(String.format("Repository issue while send transaction %s to peer %s", Base58.encode(signature), peer), e);
|
||||
LOGGER.error(String.format("Repository issue while sending transaction %s to peer %s", Base58.encode(signature), peer), e);
|
||||
} catch (TransformationException e) {
|
||||
LOGGER.error(String.format("Serialization issue while sending transaction %s to peer %s", Base58.encode(signature), peer), e);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -283,8 +283,8 @@ public class ArbitraryDataFileListManager {
|
||||
|
||||
LOGGER.debug(String.format("Sending data file list request for signature %s with %d hashes to %d peers...", signature58, hashCount, handshakedPeers.size()));
|
||||
|
||||
// FUTURE: send our address as requestingPeer once enough peers have switched to the new protocol
|
||||
String requestingPeer = null; // Network.getInstance().getOurExternalIpAddressAndPort();
|
||||
// Send our address as requestingPeer, to allow for potential direct connections with seeds/peers
|
||||
String requestingPeer = Network.getInstance().getOurExternalIpAddressAndPort();
|
||||
|
||||
// Build request
|
||||
Message getArbitraryDataFileListMessage = new GetArbitraryDataFileListMessage(signature, missingHashes, now, 0, requestingPeer);
|
||||
@@ -511,18 +511,23 @@ public class ArbitraryDataFileListManager {
|
||||
|
||||
// Bump requestHops if it exists
|
||||
if (requestHops != null) {
|
||||
arbitraryDataFileListMessage.setRequestHops(++requestHops);
|
||||
requestHops++;
|
||||
}
|
||||
|
||||
ArbitraryDataFileListMessage forwardArbitraryDataFileListMessage;
|
||||
|
||||
// Remove optional parameters if the requesting peer doesn't support it yet
|
||||
// A message with less statistical data is better than no message at all
|
||||
if (!requestingPeer.isAtLeastVersion(MIN_PEER_VERSION_FOR_FILE_LIST_STATS)) {
|
||||
arbitraryDataFileListMessage.removeOptionalStats();
|
||||
forwardArbitraryDataFileListMessage = new ArbitraryDataFileListMessage(signature, hashes);
|
||||
} else {
|
||||
forwardArbitraryDataFileListMessage = new ArbitraryDataFileListMessage(signature, hashes, requestTime, requestHops,
|
||||
arbitraryDataFileListMessage.getPeerAddress(), arbitraryDataFileListMessage.isRelayPossible());
|
||||
}
|
||||
|
||||
// Forward to requesting peer
|
||||
LOGGER.debug("Forwarding file list with {} hashes to requesting peer: {}", hashes.size(), requestingPeer);
|
||||
if (!requestingPeer.sendMessage(arbitraryDataFileListMessage)) {
|
||||
if (!requestingPeer.sendMessage(forwardArbitraryDataFileListMessage)) {
|
||||
requestingPeer.disconnect("failed to forward arbitrary data file list");
|
||||
}
|
||||
}
|
||||
@@ -631,6 +636,9 @@ public class ArbitraryDataFileListManager {
|
||||
// We should only respond if we have at least one hash
|
||||
if (hashes.size() > 0) {
|
||||
|
||||
// Firstly we should keep track of the requesting peer, to allow for potential direct connections later
|
||||
ArbitraryDataFileManager.getInstance().addRecentDataRequest(requestingPeer);
|
||||
|
||||
// We have all the chunks, so update requests map to reflect that we've sent it
|
||||
// There is no need to keep track of the request, as we can serve all the chunks
|
||||
if (allChunksExist) {
|
||||
@@ -639,16 +647,19 @@ public class ArbitraryDataFileListManager {
|
||||
}
|
||||
|
||||
String ourAddress = Network.getInstance().getOurExternalIpAddressAndPort();
|
||||
ArbitraryDataFileListMessage arbitraryDataFileListMessage = new ArbitraryDataFileListMessage(signature,
|
||||
hashes, NTP.getTime(), 0, ourAddress, true);
|
||||
arbitraryDataFileListMessage.setId(message.getId());
|
||||
ArbitraryDataFileListMessage arbitraryDataFileListMessage;
|
||||
|
||||
// Remove optional parameters if the requesting peer doesn't support it yet
|
||||
// A message with less statistical data is better than no message at all
|
||||
if (!peer.isAtLeastVersion(MIN_PEER_VERSION_FOR_FILE_LIST_STATS)) {
|
||||
arbitraryDataFileListMessage.removeOptionalStats();
|
||||
arbitraryDataFileListMessage = new ArbitraryDataFileListMessage(signature, hashes);
|
||||
} else {
|
||||
arbitraryDataFileListMessage = new ArbitraryDataFileListMessage(signature,
|
||||
hashes, NTP.getTime(), 0, ourAddress, true);
|
||||
}
|
||||
|
||||
arbitraryDataFileListMessage.setId(message.getId());
|
||||
|
||||
if (!peer.sendMessage(arbitraryDataFileListMessage)) {
|
||||
LOGGER.debug("Couldn't send list of hashes");
|
||||
peer.disconnect("failed to send list of hashes");
|
||||
@@ -670,8 +681,7 @@ public class ArbitraryDataFileListManager {
|
||||
// In relay mode - so ask our other peers if they have it
|
||||
|
||||
long requestTime = getArbitraryDataFileListMessage.getRequestTime();
|
||||
int requestHops = getArbitraryDataFileListMessage.getRequestHops();
|
||||
getArbitraryDataFileListMessage.setRequestHops(++requestHops);
|
||||
int requestHops = getArbitraryDataFileListMessage.getRequestHops() + 1;
|
||||
long totalRequestTime = now - requestTime;
|
||||
|
||||
if (totalRequestTime < RELAY_REQUEST_MAX_DURATION) {
|
||||
@@ -679,11 +689,13 @@ public class ArbitraryDataFileListManager {
|
||||
if (requestHops < RELAY_REQUEST_MAX_HOPS) {
|
||||
// Relay request hasn't reached the maximum number of hops yet, so can be rebroadcast
|
||||
|
||||
Message relayGetArbitraryDataFileListMessage = new GetArbitraryDataFileListMessage(signature, hashes, requestTime, requestHops, requestingPeer);
|
||||
|
||||
LOGGER.debug("Rebroadcasting hash list request from peer {} for signature {} to our other peers... totalRequestTime: {}, requestHops: {}", peer, Base58.encode(signature), totalRequestTime, requestHops);
|
||||
Network.getInstance().broadcast(
|
||||
broadcastPeer -> broadcastPeer == peer ||
|
||||
Objects.equals(broadcastPeer.getPeerData().getAddress().getHost(), peer.getPeerData().getAddress().getHost())
|
||||
? null : getArbitraryDataFileListMessage);
|
||||
? null : relayGetArbitraryDataFileListMessage);
|
||||
|
||||
}
|
||||
else {
|
||||
|
@@ -1,5 +1,6 @@
|
||||
package org.qortal.controller.arbitrary;
|
||||
|
||||
import com.google.common.net.InetAddresses;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.qortal.arbitrary.ArbitraryDataFile;
|
||||
@@ -7,7 +8,6 @@ import org.qortal.controller.Controller;
|
||||
import org.qortal.data.arbitrary.ArbitraryDirectConnectionInfo;
|
||||
import org.qortal.data.arbitrary.ArbitraryFileListResponseInfo;
|
||||
import org.qortal.data.arbitrary.ArbitraryRelayInfo;
|
||||
import org.qortal.data.network.ArbitraryPeerData;
|
||||
import org.qortal.data.network.PeerData;
|
||||
import org.qortal.data.transaction.ArbitraryTransactionData;
|
||||
import org.qortal.network.Network;
|
||||
@@ -55,6 +55,13 @@ public class ArbitraryDataFileManager extends Thread {
|
||||
*/
|
||||
private List<ArbitraryDirectConnectionInfo> directConnectionInfo = Collections.synchronizedList(new ArrayList<>());
|
||||
|
||||
/**
|
||||
* Map to keep track of peers requesting QDN data that we hold.
|
||||
* Key = peer address string, value = time of last request.
|
||||
* This allows for additional "burst" connections beyond existing limits.
|
||||
*/
|
||||
private Map<String, Long> recentDataRequests = Collections.synchronizedMap(new HashMap<>());
|
||||
|
||||
|
||||
public static int MAX_FILE_HASH_RESPONSES = 1000;
|
||||
|
||||
@@ -109,6 +116,9 @@ public class ArbitraryDataFileManager extends Thread {
|
||||
|
||||
final long directConnectionInfoMinimumTimestamp = now - ArbitraryDataManager.getInstance().ARBITRARY_DIRECT_CONNECTION_INFO_TIMEOUT;
|
||||
directConnectionInfo.removeIf(entry -> entry.getTimestamp() < directConnectionInfoMinimumTimestamp);
|
||||
|
||||
final long recentDataRequestMinimumTimestamp = now - ArbitraryDataManager.getInstance().ARBITRARY_RECENT_DATA_REQUESTS_TIMEOUT;
|
||||
recentDataRequests.entrySet().removeIf(entry -> entry.getValue() < recentDataRequestMinimumTimestamp);
|
||||
}
|
||||
|
||||
|
||||
@@ -140,7 +150,7 @@ public class ArbitraryDataFileManager extends Thread {
|
||||
Long startTime = NTP.getTime();
|
||||
ArbitraryDataFileMessage receivedArbitraryDataFileMessage = fetchArbitraryDataFile(peer, null, signature, hash, null);
|
||||
Long endTime = NTP.getTime();
|
||||
if (receivedArbitraryDataFileMessage != null) {
|
||||
if (receivedArbitraryDataFileMessage != null && receivedArbitraryDataFileMessage.getArbitraryDataFile() != null) {
|
||||
LOGGER.debug("Received data file {} from peer {}. Time taken: {} ms", receivedArbitraryDataFileMessage.getArbitraryDataFile().getHash58(), peer, (endTime-startTime));
|
||||
receivedAtLeastOneFile = true;
|
||||
|
||||
@@ -187,7 +197,7 @@ public class ArbitraryDataFileManager extends Thread {
|
||||
ArbitraryDataFile existingFile = ArbitraryDataFile.fromHash(hash, signature);
|
||||
boolean fileAlreadyExists = existingFile.exists();
|
||||
String hash58 = Base58.encode(hash);
|
||||
Message message = null;
|
||||
ArbitraryDataFileMessage arbitraryDataFileMessage;
|
||||
|
||||
// Fetch the file if it doesn't exist locally
|
||||
if (!fileAlreadyExists) {
|
||||
@@ -195,10 +205,11 @@ public class ArbitraryDataFileManager extends Thread {
|
||||
arbitraryDataFileRequests.put(hash58, NTP.getTime());
|
||||
Message getArbitraryDataFileMessage = new GetArbitraryDataFileMessage(signature, hash);
|
||||
|
||||
Message response = null;
|
||||
try {
|
||||
message = peer.getResponseWithTimeout(getArbitraryDataFileMessage, (int) ArbitraryDataManager.ARBITRARY_REQUEST_TIMEOUT);
|
||||
response = peer.getResponseWithTimeout(getArbitraryDataFileMessage, (int) ArbitraryDataManager.ARBITRARY_REQUEST_TIMEOUT);
|
||||
} catch (InterruptedException e) {
|
||||
// Will return below due to null message
|
||||
// Will return below due to null response
|
||||
}
|
||||
arbitraryDataFileRequests.remove(hash58);
|
||||
LOGGER.trace(String.format("Removed hash %.8s from arbitraryDataFileRequests", hash58));
|
||||
@@ -206,22 +217,24 @@ public class ArbitraryDataFileManager extends Thread {
|
||||
// We may need to remove the file list request, if we have all the files for this transaction
|
||||
this.handleFileListRequests(signature);
|
||||
|
||||
if (message == null) {
|
||||
LOGGER.debug("Received null message from peer {}", peer);
|
||||
if (response == null) {
|
||||
LOGGER.debug("Received null response from peer {}", peer);
|
||||
return null;
|
||||
}
|
||||
if (message.getType() != Message.MessageType.ARBITRARY_DATA_FILE) {
|
||||
LOGGER.debug("Received message with invalid type: {} from peer {}", message.getType(), peer);
|
||||
if (response.getType() != MessageType.ARBITRARY_DATA_FILE) {
|
||||
LOGGER.debug("Received response with invalid type: {} from peer {}", response.getType(), peer);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
||||
ArbitraryDataFileMessage peersArbitraryDataFileMessage = (ArbitraryDataFileMessage) response;
|
||||
arbitraryDataFileMessage = new ArbitraryDataFileMessage(signature, peersArbitraryDataFileMessage.getArbitraryDataFile());
|
||||
} else {
|
||||
LOGGER.debug(String.format("File hash %s already exists, so skipping the request", hash58));
|
||||
arbitraryDataFileMessage = new ArbitraryDataFileMessage(signature, existingFile);
|
||||
}
|
||||
ArbitraryDataFileMessage arbitraryDataFileMessage = (ArbitraryDataFileMessage) message;
|
||||
|
||||
// We might want to forward the request to the peer that originally requested it
|
||||
this.handleArbitraryDataFileForwarding(requestingPeer, message, originalMessage);
|
||||
this.handleArbitraryDataFileForwarding(requestingPeer, arbitraryDataFileMessage, originalMessage);
|
||||
|
||||
boolean isRelayRequest = (requestingPeer != null);
|
||||
if (isRelayRequest) {
|
||||
@@ -488,6 +501,45 @@ public class ArbitraryDataFileManager extends Thread {
|
||||
}
|
||||
|
||||
|
||||
// Peers requesting QDN data from us
|
||||
|
||||
/**
|
||||
* Add an address string of a peer that is trying to request data from us.
|
||||
* @param peerAddress
|
||||
*/
|
||||
public void addRecentDataRequest(String peerAddress) {
|
||||
if (peerAddress == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Long now = NTP.getTime();
|
||||
if (now == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Make sure to remove the port, since it isn't guaranteed to match next time
|
||||
String[] parts = peerAddress.split(":");
|
||||
if (parts.length == 0) {
|
||||
return;
|
||||
}
|
||||
String host = parts[0];
|
||||
if (!InetAddresses.isInetAddress(host)) {
|
||||
// Invalid host
|
||||
return;
|
||||
}
|
||||
|
||||
this.recentDataRequests.put(host, now);
|
||||
}
|
||||
|
||||
public boolean isPeerRequestingData(String peerAddressWithoutPort) {
|
||||
return this.recentDataRequests.containsKey(peerAddressWithoutPort);
|
||||
}
|
||||
|
||||
public boolean hasPendingDataRequest() {
|
||||
return !this.recentDataRequests.isEmpty();
|
||||
}
|
||||
|
||||
|
||||
// Network handlers
|
||||
|
||||
public void onNetworkGetArbitraryDataFileMessage(Peer peer, Message message) {
|
||||
|
@@ -47,6 +47,9 @@ public class ArbitraryDataManager extends Thread {
|
||||
/** Maximum time to hold direct peer connection information */
|
||||
public static final long ARBITRARY_DIRECT_CONNECTION_INFO_TIMEOUT = 2 * 60 * 1000L; // ms
|
||||
|
||||
/** Maximum time to hold information about recent data requests that we can fulfil */
|
||||
public static final long ARBITRARY_RECENT_DATA_REQUESTS_TIMEOUT = 2 * 60 * 1000L; // ms
|
||||
|
||||
/** Maximum number of hops that an arbitrary signatures request is allowed to make */
|
||||
private static int ARBITRARY_SIGNATURES_REQUEST_MAX_HOPS = 3;
|
||||
|
||||
|
@@ -216,9 +216,19 @@ public class ArbitraryMetadataManager {
|
||||
|
||||
long timeSinceLastAttempt = NTP.getTime() - lastAttemptTimestamp;
|
||||
|
||||
// Allow a second attempt after 15 seconds, and another after 30 seconds
|
||||
if (timeSinceLastAttempt > 15 * 1000L) {
|
||||
// We haven't tried for at least 15 seconds
|
||||
// Allow a second attempt after 60 seconds
|
||||
if (timeSinceLastAttempt > 60 * 1000L) {
|
||||
// We haven't tried for at least 60 seconds
|
||||
|
||||
if (networkBroadcastCount < 2) {
|
||||
// We've made less than 2 total attempts
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Then allow another attempt after 60 minutes
|
||||
if (timeSinceLastAttempt > 60 * 60 * 1000L) {
|
||||
// We haven't tried for at least 60 minutes
|
||||
|
||||
if (networkBroadcastCount < 3) {
|
||||
// We've made less than 3 total attempts
|
||||
@@ -226,22 +236,6 @@ public class ArbitraryMetadataManager {
|
||||
}
|
||||
}
|
||||
|
||||
// Then allow another 5 attempts, each 5 minutes apart
|
||||
if (timeSinceLastAttempt > 5 * 60 * 1000L) {
|
||||
// We haven't tried for at least 5 minutes
|
||||
|
||||
if (networkBroadcastCount < 5) {
|
||||
// We've made less than 5 total attempts
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// From then on, only try once every 24 hours, to reduce network spam
|
||||
if (timeSinceLastAttempt > 24 * 60 * 60 * 1000L) {
|
||||
// We haven't tried for at least 24 hours
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -344,9 +338,11 @@ public class ArbitraryMetadataManager {
|
||||
Peer requestingPeer = request.getB();
|
||||
if (requestingPeer != null) {
|
||||
|
||||
ArbitraryMetadataMessage forwardArbitraryMetadataMessage = new ArbitraryMetadataMessage(signature, arbitraryMetadataMessage.getArbitraryMetadataFile());
|
||||
|
||||
// Forward to requesting peer
|
||||
LOGGER.debug("Forwarding metadata to requesting peer: {}", requestingPeer);
|
||||
if (!requestingPeer.sendMessage(arbitraryMetadataMessage)) {
|
||||
if (!requestingPeer.sendMessage(forwardArbitraryMetadataMessage)) {
|
||||
requestingPeer.disconnect("failed to forward arbitrary metadata");
|
||||
}
|
||||
}
|
||||
@@ -429,8 +425,7 @@ public class ArbitraryMetadataManager {
|
||||
// In relay mode - so ask our other peers if they have it
|
||||
|
||||
long requestTime = getArbitraryMetadataMessage.getRequestTime();
|
||||
int requestHops = getArbitraryMetadataMessage.getRequestHops();
|
||||
getArbitraryMetadataMessage.setRequestHops(++requestHops);
|
||||
int requestHops = getArbitraryMetadataMessage.getRequestHops() + 1;
|
||||
long totalRequestTime = now - requestTime;
|
||||
|
||||
if (totalRequestTime < RELAY_REQUEST_MAX_DURATION) {
|
||||
@@ -438,11 +433,13 @@ public class ArbitraryMetadataManager {
|
||||
if (requestHops < RELAY_REQUEST_MAX_HOPS) {
|
||||
// Relay request hasn't reached the maximum number of hops yet, so can be rebroadcast
|
||||
|
||||
Message relayGetArbitraryMetadataMessage = new GetArbitraryMetadataMessage(signature, requestTime, requestHops);
|
||||
|
||||
LOGGER.debug("Rebroadcasting metadata request from peer {} for signature {} to our other peers... totalRequestTime: {}, requestHops: {}", peer, Base58.encode(signature), totalRequestTime, requestHops);
|
||||
Network.getInstance().broadcast(
|
||||
broadcastPeer -> broadcastPeer == peer ||
|
||||
Objects.equals(broadcastPeer.getPeerData().getAddress().getHost(), peer.getPeerData().getAddress().getHost())
|
||||
? null : getArbitraryMetadataMessage);
|
||||
? null : relayGetArbitraryMetadataMessage);
|
||||
|
||||
}
|
||||
else {
|
||||
|
@@ -29,6 +29,15 @@ public class NamesDatabaseIntegrityCheck {
|
||||
private List<TransactionData> nameTransactions = new ArrayList<>();
|
||||
|
||||
public int rebuildName(String name, Repository repository) {
|
||||
return this.rebuildName(name, repository, null);
|
||||
}
|
||||
|
||||
public int rebuildName(String name, Repository repository, List<String> referenceNames) {
|
||||
// "referenceNames" tracks the linked names that have already been rebuilt, to prevent circular dependencies
|
||||
if (referenceNames == null) {
|
||||
referenceNames = new ArrayList<>();
|
||||
}
|
||||
|
||||
int modificationCount = 0;
|
||||
try {
|
||||
List<TransactionData> transactions = this.fetchAllTransactionsInvolvingName(name, repository);
|
||||
@@ -56,7 +65,14 @@ public class NamesDatabaseIntegrityCheck {
|
||||
if (Objects.equals(updateNameTransactionData.getNewName(), name) &&
|
||||
!Objects.equals(updateNameTransactionData.getName(), updateNameTransactionData.getNewName())) {
|
||||
// This renames an existing name, so we need to process that instead
|
||||
this.rebuildName(updateNameTransactionData.getName(), repository);
|
||||
|
||||
if (!referenceNames.contains(name)) {
|
||||
referenceNames.add(name);
|
||||
this.rebuildName(updateNameTransactionData.getName(), repository, referenceNames);
|
||||
}
|
||||
else {
|
||||
// We've already processed this name so there's nothing more to do
|
||||
}
|
||||
}
|
||||
else {
|
||||
Name nameObj = new Name(repository, name);
|
||||
@@ -193,7 +209,12 @@ public class NamesDatabaseIntegrityCheck {
|
||||
newName = registeredName;
|
||||
}
|
||||
NameData newNameData = repository.getNameRepository().fromName(newName);
|
||||
if (!Objects.equals(creator.getAddress(), newNameData.getOwner())) {
|
||||
if (newNameData == null) {
|
||||
LOGGER.info("Error: registered name {} has no new name data. This is likely due to account {} " +
|
||||
"being renamed another time, which is a scenario that is not yet checked automatically.",
|
||||
updateNameTransactionData.getNewName(), creator.getAddress());
|
||||
}
|
||||
else if (!Objects.equals(creator.getAddress(), newNameData.getOwner())) {
|
||||
LOGGER.info("Error: registered name {} is owned by {}, but it should be {}",
|
||||
updateNameTransactionData.getNewName(), newNameData.getOwner(), creator.getAddress());
|
||||
integrityCheckFailed = true;
|
||||
@@ -313,6 +334,10 @@ public class NamesDatabaseIntegrityCheck {
|
||||
transactions.add(transactionData);
|
||||
}
|
||||
}
|
||||
|
||||
// Sort by lowest timestamp first
|
||||
transactions.sort(Comparator.comparingLong(TransactionData::getTimestamp));
|
||||
|
||||
return transactions;
|
||||
}
|
||||
|
||||
|
@@ -239,6 +239,11 @@ public class TradeBot implements Listener {
|
||||
if (!(event instanceof Synchronizer.NewChainTipEvent))
|
||||
return;
|
||||
|
||||
// Don't process trade bots or broadcast presence timestamps if our chain is more than 30 minutes old
|
||||
final Long minLatestBlockTimestamp = NTP.getTime() - (30 * 60 * 1000L);
|
||||
if (!Controller.getInstance().isUpToDate(minLatestBlockTimestamp))
|
||||
return;
|
||||
|
||||
synchronized (this) {
|
||||
expireOldPresenceTimestamps();
|
||||
|
||||
@@ -397,7 +402,7 @@ public class TradeBot implements Listener {
|
||||
|
||||
long now = NTP.getTime();
|
||||
long newExpiry = generateExpiry(now);
|
||||
ByteArray pubkeyByteArray = ByteArray.of(tradeNativeAccount.getPublicKey());
|
||||
ByteArray pubkeyByteArray = ByteArray.wrap(tradeNativeAccount.getPublicKey());
|
||||
|
||||
// If map entry's timestamp is missing, or within early renewal period, use the new expiry - otherwise use existing timestamp.
|
||||
synchronized (this.ourTradePresenceTimestampsByPubkey) {
|
||||
@@ -484,7 +489,7 @@ public class TradeBot implements Listener {
|
||||
int knownCount = entriesUnknownToPeer.size();
|
||||
|
||||
for (TradePresenceData peersTradePresence : peersTradePresences) {
|
||||
ByteArray pubkeyByteArray = ByteArray.of(peersTradePresence.getPublicKey());
|
||||
ByteArray pubkeyByteArray = ByteArray.wrap(peersTradePresence.getPublicKey());
|
||||
|
||||
TradePresenceData ourEntry = entriesUnknownToPeer.get(pubkeyByteArray);
|
||||
|
||||
@@ -541,7 +546,7 @@ public class TradeBot implements Listener {
|
||||
continue;
|
||||
}
|
||||
|
||||
ByteArray pubkeyByteArray = ByteArray.of(peersTradePresence.getPublicKey());
|
||||
ByteArray pubkeyByteArray = ByteArray.wrap(peersTradePresence.getPublicKey());
|
||||
|
||||
// Ignore if we've previously verified this timestamp+publickey combo or sent timestamp is older
|
||||
TradePresenceData existingTradeData = this.safeAllTradePresencesByPubkey.get(pubkeyByteArray);
|
||||
@@ -584,7 +589,7 @@ public class TradeBot implements Listener {
|
||||
continue;
|
||||
}
|
||||
|
||||
ByteArray atCodeHash = new ByteArray(atData.getCodeHash());
|
||||
ByteArray atCodeHash = ByteArray.wrap(atData.getCodeHash());
|
||||
Supplier<ACCT> acctSupplier = acctSuppliersByCodeHash.get(atCodeHash);
|
||||
if (acctSupplier == null) {
|
||||
LOGGER.trace("Ignoring trade presence {} from peer {} as AT isn't a known ACCT?",
|
||||
@@ -637,7 +642,7 @@ public class TradeBot implements Listener {
|
||||
|
||||
public void bridgePresence(long timestamp, byte[] publicKey, byte[] signature, String atAddress) {
|
||||
long expiry = generateExpiry(timestamp);
|
||||
ByteArray pubkeyByteArray = ByteArray.of(publicKey);
|
||||
ByteArray pubkeyByteArray = ByteArray.wrap(publicKey);
|
||||
|
||||
TradePresenceData fakeTradePresenceData = new TradePresenceData(expiry, publicKey, signature, atAddress);
|
||||
|
||||
|
@@ -42,30 +42,40 @@ public class Bitcoin extends Bitcoiny {
|
||||
public Collection<ElectrumX.Server> getServers() {
|
||||
return Arrays.asList(
|
||||
// Servers chosen on NO BASIS WHATSOEVER from various sources!
|
||||
new Server("hodlers.beer", Server.ConnectionType.SSL, 50002),
|
||||
new Server("btc.lastingcoin.net", Server.ConnectionType.SSL, 50002),
|
||||
new Server("electrum.bitaroo.net", Server.ConnectionType.SSL, 50002),
|
||||
new Server("bitcoin.grey.pw", Server.ConnectionType.SSL, 50002),
|
||||
// Status verified at https://1209k.com/bitcoin-eye/ele.php?chain=btc
|
||||
//CLOSED new Server("bitcoin.grey.pw", Server.ConnectionType.SSL, 50002),
|
||||
//CLOSED new Server("btc.litepay.ch", Server.ConnectionType.SSL, 50002),
|
||||
//CLOSED new Server("electrum.pabu.io", Server.ConnectionType.SSL, 50002),
|
||||
//CLOSED new Server("electrumx.hodlwallet.com", Server.ConnectionType.SSL, 50002),
|
||||
//CLOSED new Server("gd42.org", Server.ConnectionType.SSL, 50002),
|
||||
//CLOSED new Server("korea.electrum-server.com", Server.ConnectionType.SSL, 50002),
|
||||
//CLOSED new Server("prospero.bitsrc.net", Server.ConnectionType.SSL, 50002),
|
||||
//1.15.0 new Server("alviss.coinjoined.com", Server.ConnectionType.SSL, 50002),
|
||||
//1.15.0 new Server("electrum.acinq.co", Server.ConnectionType.SSL, 50002),
|
||||
//1.14.0 new Server("electrum.coinext.com.br", Server.ConnectionType.SSL, 50002),
|
||||
new Server("104.248.139.211", Server.ConnectionType.SSL, 50002),
|
||||
new Server("142.93.6.38", Server.ConnectionType.SSL, 50002),
|
||||
new Server("157.245.172.236", Server.ConnectionType.SSL, 50002),
|
||||
new Server("167.172.226.175", Server.ConnectionType.SSL, 50002),
|
||||
new Server("167.172.42.31", Server.ConnectionType.SSL, 50002),
|
||||
new Server("178.62.80.20", Server.ConnectionType.SSL, 50002),
|
||||
new Server("185.64.116.15", Server.ConnectionType.SSL, 50002),
|
||||
new Server("alviss.coinjoined.com", Server.ConnectionType.SSL, 50002),
|
||||
new Server("btc.litepay.ch", Server.ConnectionType.SSL, 50002),
|
||||
new Server("xtrum.com", Server.ConnectionType.SSL, 50002),
|
||||
new Server("electrum.acinq.co", Server.ConnectionType.SSL, 50002),
|
||||
new Server("caleb.vegas", Server.ConnectionType.SSL, 50002),
|
||||
new Server("electrum.coinext.com.br", Server.ConnectionType.TCP, 50001),
|
||||
new Server("korea.electrum-server.com", Server.ConnectionType.TCP, 50001),
|
||||
new Server("eai.coincited.net", Server.ConnectionType.TCP, 50001),
|
||||
new Server("electrum.coinext.com.br", Server.ConnectionType.SSL, 50002),
|
||||
new Server("node1.btccuracao.com", Server.ConnectionType.SSL, 50002),
|
||||
new Server("korea.electrum-server.com", Server.ConnectionType.SSL, 50002),
|
||||
new Server("btce.iiiiiii.biz", Server.ConnectionType.SSL, 50002),
|
||||
new Server("68.183.188.105", Server.ConnectionType.SSL, 50002),
|
||||
new Server("bitcoin.lukechilds.co", Server.ConnectionType.SSL, 50002),
|
||||
new Server("guichet.centure.cc", Server.ConnectionType.SSL, 50002),
|
||||
new Server("electrumx.hodlwallet.com", Server.ConnectionType.SSL, 50002),
|
||||
new Server("blkhub.net", Server.ConnectionType.SSL, 50002),
|
||||
new Server("btc.lastingcoin.net", Server.ConnectionType.SSL, 50002),
|
||||
new Server("btce.iiiiiii.biz", Server.ConnectionType.SSL, 50002),
|
||||
new Server("caleb.vegas", Server.ConnectionType.SSL, 50002),
|
||||
new Server("eai.coincited.net", Server.ConnectionType.SSL, 50002),
|
||||
new Server("prospero.bitsrc.net", Server.ConnectionType.SSL, 50002),
|
||||
new Server("gd42.org", Server.ConnectionType.SSL, 50002),
|
||||
new Server("electrum.pabu.io", Server.ConnectionType.SSL, 50002));
|
||||
new Server("electrum.bitaroo.net", Server.ConnectionType.SSL, 50002),
|
||||
new Server("electrumx.dev", Server.ConnectionType.SSL, 50002),
|
||||
new Server("elx.bitske.com", Server.ConnectionType.SSL, 50002),
|
||||
new Server("fortress.qtornado.com", Server.ConnectionType.SSL, 50002),
|
||||
new Server("guichet.centure.cc", Server.ConnectionType.SSL, 50002),
|
||||
new Server("kareoke.qoppa.org", Server.ConnectionType.SSL, 50002),
|
||||
new Server("hodlers.beer", Server.ConnectionType.SSL, 50002),
|
||||
new Server("node1.btccuracao.com", Server.ConnectionType.SSL, 50002),
|
||||
new Server("xtrum.com", Server.ConnectionType.SSL, 50002));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@@ -45,9 +45,9 @@ public class Dogecoin extends Bitcoiny {
|
||||
public Collection<Server> getServers() {
|
||||
return Arrays.asList(
|
||||
// Servers chosen on NO BASIS WHATSOEVER from various sources!
|
||||
new Server("electrum1.cipig.net", ConnectionType.TCP, 10060),
|
||||
new Server("electrum2.cipig.net", ConnectionType.TCP, 10060),
|
||||
new Server("electrum3.cipig.net", ConnectionType.TCP, 10060));
|
||||
new Server("electrum1.cipig.net", ConnectionType.SSL, 20060),
|
||||
new Server("electrum2.cipig.net", ConnectionType.SSL, 20060),
|
||||
new Server("electrum3.cipig.net", ConnectionType.SSL, 20060));
|
||||
// TODO: add more mainnet servers. It's too centralized.
|
||||
}
|
||||
|
||||
|
@@ -44,23 +44,17 @@ public class Litecoin extends Bitcoiny {
|
||||
public Collection<ElectrumX.Server> getServers() {
|
||||
return Arrays.asList(
|
||||
// Servers chosen on NO BASIS WHATSOEVER from various sources!
|
||||
new Server("electrum-ltc.someguy123.net", Server.ConnectionType.SSL, 50002),
|
||||
new Server("backup.electrum-ltc.org", Server.ConnectionType.TCP, 50001),
|
||||
// Status verified at https://1209k.com/bitcoin-eye/ele.php?chain=ltc
|
||||
//CLOSED new Server("electrum-ltc.petrkr.net", Server.ConnectionType.SSL, 60002),
|
||||
//CLOSED new Server("electrum-ltc.someguy123.net", Server.ConnectionType.SSL, 50002),
|
||||
//PHISHY new Server("electrum-ltc.bysh.me", Server.ConnectionType.SSL, 50002),
|
||||
new Server("backup.electrum-ltc.org", Server.ConnectionType.SSL, 443),
|
||||
new Server("electrum.ltc.xurious.com", Server.ConnectionType.TCP, 50001),
|
||||
new Server("electrum.ltc.xurious.com", Server.ConnectionType.SSL, 50002),
|
||||
new Server("electrum-ltc.bysh.me", Server.ConnectionType.SSL, 50002),
|
||||
new Server("electrum1.cipig.net", Server.ConnectionType.SSL, 20063),
|
||||
new Server("electrum2.cipig.net", Server.ConnectionType.SSL, 20063),
|
||||
new Server("electrum3.cipig.net", Server.ConnectionType.SSL, 20063),
|
||||
new Server("electrum3.cipig.net", ConnectionType.TCP, 10063),
|
||||
new Server("electrum2.cipig.net", Server.ConnectionType.TCP, 10063),
|
||||
new Server("electrum1.cipig.net", Server.ConnectionType.SSL, 20063),
|
||||
new Server("electrum1.cipig.net", Server.ConnectionType.TCP, 10063),
|
||||
new Server("electrum-ltc.petrkr.net", Server.ConnectionType.SSL, 60002),
|
||||
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));
|
||||
new Server("ltc.rentonrisk.com", Server.ConnectionType.SSL, 50002));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@@ -62,7 +62,7 @@ public enum SupportedBlockchain {
|
||||
private static final Map<ByteArray, Supplier<ACCT>> supportedAcctsByCodeHash = Arrays.stream(SupportedBlockchain.values())
|
||||
.map(supportedBlockchain -> supportedBlockchain.supportedAccts)
|
||||
.flatMap(List::stream)
|
||||
.collect(Collectors.toUnmodifiableMap(triple -> new ByteArray(triple.getB()), Triple::getC));
|
||||
.collect(Collectors.toUnmodifiableMap(triple -> ByteArray.wrap(triple.getB()), Triple::getC));
|
||||
|
||||
private static final Map<String, Supplier<ACCT>> supportedAcctsByName = Arrays.stream(SupportedBlockchain.values())
|
||||
.map(supportedBlockchain -> supportedBlockchain.supportedAccts)
|
||||
@@ -94,7 +94,7 @@ public enum SupportedBlockchain {
|
||||
return getAcctMap();
|
||||
|
||||
return blockchain.supportedAccts.stream()
|
||||
.collect(Collectors.toUnmodifiableMap(triple -> new ByteArray(triple.getB()), Triple::getC));
|
||||
.collect(Collectors.toUnmodifiableMap(triple -> ByteArray.wrap(triple.getB()), Triple::getC));
|
||||
}
|
||||
|
||||
public static Map<ByteArray, Supplier<ACCT>> getFilteredAcctMap(String specificBlockchain) {
|
||||
@@ -109,7 +109,7 @@ public enum SupportedBlockchain {
|
||||
}
|
||||
|
||||
public static ACCT getAcctByCodeHash(byte[] codeHash) {
|
||||
ByteArray wrappedCodeHash = new ByteArray(codeHash);
|
||||
ByteArray wrappedCodeHash = ByteArray.wrap(codeHash);
|
||||
|
||||
Supplier<ACCT> acctInstanceSupplier = supportedAcctsByCodeHash.get(wrappedCodeHash);
|
||||
|
||||
|
@@ -23,6 +23,7 @@ public class GroupData {
|
||||
private ApprovalThreshold approvalThreshold;
|
||||
private int minimumBlockDelay;
|
||||
private int maximumBlockDelay;
|
||||
public int memberCount;
|
||||
|
||||
/** Reference to CREATE_GROUP or UPDATE_GROUP transaction, used to rebuild group during orphaning. */
|
||||
// No need to ever expose this via API
|
||||
|
@@ -48,6 +48,7 @@ public class UpdateNameTransactionData extends TransactionData {
|
||||
|
||||
public void afterUnmarshal(Unmarshaller u, Object parent) {
|
||||
this.creatorPublicKey = this.ownerPublicKey;
|
||||
this.reducedNewName = this.newName != null ? Unicode.sanitize(this.newName) : null;
|
||||
}
|
||||
|
||||
/** From repository */
|
||||
@@ -62,7 +63,7 @@ public class UpdateNameTransactionData extends TransactionData {
|
||||
this.nameReference = nameReference;
|
||||
}
|
||||
|
||||
/** From network/API */
|
||||
/** From network */
|
||||
public UpdateNameTransactionData(BaseTransactionData baseTransactionData, String name, String newName, String newData) {
|
||||
this(baseTransactionData, name, newName, newData, Unicode.sanitize(newName), null);
|
||||
}
|
||||
|
@@ -4,6 +4,7 @@ import java.awt.GraphicsEnvironment;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.ServiceConfigurationError;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import javax.swing.JOptionPane;
|
||||
@@ -49,7 +50,7 @@ public class Gui {
|
||||
protected static BufferedImage loadImage(String resourceName) {
|
||||
try (InputStream in = Gui.class.getResourceAsStream("/images/" + resourceName)) {
|
||||
return ImageIO.read(in);
|
||||
} catch (IllegalArgumentException | IOException e) {
|
||||
} catch (IllegalArgumentException | IOException | ServiceConfigurationError e) {
|
||||
LOGGER.warn(String.format("Couldn't locate image resource \"images/%s\"", resourceName));
|
||||
return null;
|
||||
}
|
||||
|
@@ -13,7 +13,7 @@ import org.qortal.crypto.MemoryPoW;
|
||||
import org.qortal.network.message.ChallengeMessage;
|
||||
import org.qortal.network.message.HelloMessage;
|
||||
import org.qortal.network.message.Message;
|
||||
import org.qortal.network.message.Message.MessageType;
|
||||
import org.qortal.network.message.MessageType;
|
||||
import org.qortal.settings.Settings;
|
||||
import org.qortal.network.message.ResponseMessage;
|
||||
import org.qortal.utils.DaemonThreadFactory;
|
||||
|
@@ -8,11 +8,13 @@ import org.bouncycastle.crypto.params.Ed25519PublicKeyParameters;
|
||||
import org.qortal.block.BlockChain;
|
||||
import org.qortal.controller.Controller;
|
||||
import org.qortal.controller.arbitrary.ArbitraryDataFileListManager;
|
||||
import org.qortal.controller.arbitrary.ArbitraryDataManager;
|
||||
import org.qortal.crypto.Crypto;
|
||||
import org.qortal.data.block.BlockData;
|
||||
import org.qortal.data.network.PeerData;
|
||||
import org.qortal.data.transaction.TransactionData;
|
||||
import org.qortal.network.message.*;
|
||||
import org.qortal.network.task.*;
|
||||
import org.qortal.repository.DataException;
|
||||
import org.qortal.repository.Repository;
|
||||
import org.qortal.repository.RepositoryManager;
|
||||
@@ -32,6 +34,7 @@ import java.nio.channels.*;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
import java.util.function.Function;
|
||||
@@ -41,9 +44,8 @@ import java.util.stream.Collectors;
|
||||
// For managing peers
|
||||
public class Network {
|
||||
private static final Logger LOGGER = LogManager.getLogger(Network.class);
|
||||
private static Network instance;
|
||||
|
||||
private static final int LISTEN_BACKLOG = 10;
|
||||
private static final int LISTEN_BACKLOG = 5;
|
||||
/**
|
||||
* How long before retrying after a connection failure, in milliseconds.
|
||||
*/
|
||||
@@ -122,14 +124,8 @@ public class Network {
|
||||
private final ExecuteProduceConsume networkEPC;
|
||||
private Selector channelSelector;
|
||||
private ServerSocketChannel serverChannel;
|
||||
private Iterator<SelectionKey> channelIterator = null;
|
||||
|
||||
// volatile because value is updated inside any one of the EPC threads
|
||||
private volatile long nextConnectTaskTimestamp = 0L; // ms - try first connect once NTP syncs
|
||||
|
||||
private final ExecutorService broadcastExecutor = Executors.newCachedThreadPool();
|
||||
// volatile because value is updated inside any one of the EPC threads
|
||||
private volatile long nextBroadcastTimestamp = 0L; // ms - try first broadcast once NTP syncs
|
||||
private SelectionKey serverSelectionKey;
|
||||
private final Set<SelectableChannel> channelsPendingWrite = ConcurrentHashMap.newKeySet();
|
||||
|
||||
private final Lock mergePeersLock = new ReentrantLock();
|
||||
|
||||
@@ -137,6 +133,8 @@ public class Network {
|
||||
private String ourExternalIpAddress = null;
|
||||
private int ourExternalPort = Settings.getInstance().getListenPort();
|
||||
|
||||
private volatile boolean isShuttingDown = false;
|
||||
|
||||
// Constructors
|
||||
|
||||
private Network() {
|
||||
@@ -170,7 +168,7 @@ public class Network {
|
||||
serverChannel.configureBlocking(false);
|
||||
serverChannel.setOption(StandardSocketOptions.SO_REUSEADDR, true);
|
||||
serverChannel.bind(endpoint, LISTEN_BACKLOG);
|
||||
serverChannel.register(channelSelector, SelectionKey.OP_ACCEPT);
|
||||
serverSelectionKey = serverChannel.register(channelSelector, SelectionKey.OP_ACCEPT);
|
||||
} catch (UnknownHostException e) {
|
||||
LOGGER.error("Can't bind listen socket to address {}", Settings.getInstance().getBindAddress());
|
||||
throw new IOException("Can't bind listen socket to address", e);
|
||||
@@ -180,7 +178,8 @@ public class Network {
|
||||
}
|
||||
|
||||
// Load all known peers from repository
|
||||
synchronized (this.allKnownPeers) { List<String> fixedNetwork = Settings.getInstance().getFixedNetwork();
|
||||
synchronized (this.allKnownPeers) {
|
||||
List<String> fixedNetwork = Settings.getInstance().getFixedNetwork();
|
||||
if (fixedNetwork != null && !fixedNetwork.isEmpty()) {
|
||||
Long addedWhen = NTP.getTime();
|
||||
String addedBy = "fixedNetwork";
|
||||
@@ -214,12 +213,16 @@ public class Network {
|
||||
|
||||
// Getters / setters
|
||||
|
||||
public static synchronized Network getInstance() {
|
||||
if (instance == null) {
|
||||
instance = new Network();
|
||||
}
|
||||
private static class SingletonContainer {
|
||||
private static final Network INSTANCE = new Network();
|
||||
}
|
||||
|
||||
return instance;
|
||||
public static Network getInstance() {
|
||||
return SingletonContainer.INSTANCE;
|
||||
}
|
||||
|
||||
public int getMaxPeers() {
|
||||
return this.maxPeers;
|
||||
}
|
||||
|
||||
public byte[] getMessageMagic() {
|
||||
@@ -257,6 +260,18 @@ public class Network {
|
||||
return this.immutableConnectedPeers;
|
||||
}
|
||||
|
||||
public List<Peer> getImmutableConnectedDataPeers() {
|
||||
return this.getImmutableConnectedPeers().stream()
|
||||
.filter(p -> p.isDataPeer())
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public List<Peer> getImmutableConnectedNonDataPeers() {
|
||||
return this.getImmutableConnectedPeers().stream()
|
||||
.filter(p -> !p.isDataPeer())
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public void addConnectedPeer(Peer peer) {
|
||||
this.connectedPeers.add(peer); // thread safe thanks to synchronized list
|
||||
this.immutableConnectedPeers = List.copyOf(this.connectedPeers); // also thread safe thanks to synchronized collection's toArray() being fed to List.of(array)
|
||||
@@ -323,6 +338,7 @@ public class Network {
|
||||
// Add this signature to the list of pending requests for this peer
|
||||
LOGGER.info("Making connection to peer {} to request files for signature {}...", peerAddressString, Base58.encode(signature));
|
||||
Peer peer = new Peer(peerData);
|
||||
peer.setIsDataPeer(true);
|
||||
peer.addPendingSignatureRequest(signature);
|
||||
return this.connectPeer(peer);
|
||||
// If connection (and handshake) is successful, data will automatically be requested
|
||||
@@ -453,6 +469,11 @@ public class Network {
|
||||
|
||||
class NetworkProcessor extends ExecuteProduceConsume {
|
||||
|
||||
private final AtomicLong nextConnectTaskTimestamp = new AtomicLong(0L); // ms - try first connect once NTP syncs
|
||||
private final AtomicLong nextBroadcastTimestamp = new AtomicLong(0L); // ms - try first broadcast once NTP syncs
|
||||
|
||||
private Iterator<SelectionKey> channelIterator = null;
|
||||
|
||||
NetworkProcessor(ExecutorService executor) {
|
||||
super(executor);
|
||||
}
|
||||
@@ -494,43 +515,23 @@ public class Network {
|
||||
}
|
||||
|
||||
private Task maybeProducePeerMessageTask() {
|
||||
for (Peer peer : getImmutableConnectedPeers()) {
|
||||
Task peerTask = peer.getMessageTask();
|
||||
if (peerTask != null) {
|
||||
return peerTask;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
return getImmutableConnectedPeers().stream()
|
||||
.map(Peer::getMessageTask)
|
||||
.filter(Objects::nonNull)
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
private Task maybeProducePeerPingTask(Long now) {
|
||||
// Ask connected peers whether they need a ping
|
||||
for (Peer peer : getImmutableHandshakedPeers()) {
|
||||
Task peerTask = peer.getPingTask(now);
|
||||
if (peerTask != null) {
|
||||
return peerTask;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
class PeerConnectTask implements ExecuteProduceConsume.Task {
|
||||
private final Peer peer;
|
||||
|
||||
PeerConnectTask(Peer peer) {
|
||||
this.peer = peer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void perform() throws InterruptedException {
|
||||
connectPeer(peer);
|
||||
}
|
||||
return getImmutableHandshakedPeers().stream()
|
||||
.map(peer -> peer.getPingTask(now))
|
||||
.filter(Objects::nonNull)
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
private Task maybeProduceConnectPeerTask(Long now) throws InterruptedException {
|
||||
if (now == null || now < nextConnectTaskTimestamp) {
|
||||
if (now == null || now < nextConnectTaskTimestamp.get()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -538,7 +539,7 @@ public class Network {
|
||||
return null;
|
||||
}
|
||||
|
||||
nextConnectTaskTimestamp = now + 1000L;
|
||||
nextConnectTaskTimestamp.set(now + 1000L);
|
||||
|
||||
Peer targetPeer = getConnectablePeer(now);
|
||||
if (targetPeer == null) {
|
||||
@@ -550,66 +551,15 @@ public class Network {
|
||||
}
|
||||
|
||||
private Task maybeProduceBroadcastTask(Long now) {
|
||||
if (now == null || now < nextBroadcastTimestamp) {
|
||||
if (now == null || now < nextBroadcastTimestamp.get()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
nextBroadcastTimestamp = now + BROADCAST_INTERVAL;
|
||||
return () -> Controller.getInstance().doNetworkBroadcast();
|
||||
}
|
||||
|
||||
class ChannelTask implements ExecuteProduceConsume.Task {
|
||||
private final SelectionKey selectionKey;
|
||||
|
||||
ChannelTask(SelectionKey selectionKey) {
|
||||
this.selectionKey = selectionKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void perform() throws InterruptedException {
|
||||
try {
|
||||
LOGGER.trace("Thread {} has pending channel: {}, with ops {}",
|
||||
Thread.currentThread().getId(), selectionKey.channel(), selectionKey.readyOps());
|
||||
|
||||
// process pending channel task
|
||||
if (selectionKey.isReadable()) {
|
||||
connectionRead((SocketChannel) selectionKey.channel());
|
||||
} else if (selectionKey.isAcceptable()) {
|
||||
acceptConnection((ServerSocketChannel) selectionKey.channel());
|
||||
}
|
||||
|
||||
LOGGER.trace("Thread {} processed channel: {}",
|
||||
Thread.currentThread().getId(), selectionKey.channel());
|
||||
} catch (CancelledKeyException e) {
|
||||
LOGGER.trace("Thread {} encountered cancelled channel: {}",
|
||||
Thread.currentThread().getId(), selectionKey.channel());
|
||||
}
|
||||
}
|
||||
|
||||
private void connectionRead(SocketChannel socketChannel) {
|
||||
Peer peer = getPeerFromChannel(socketChannel);
|
||||
if (peer == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
peer.readChannel();
|
||||
} catch (IOException e) {
|
||||
if (e.getMessage() != null && e.getMessage().toLowerCase().contains("connection reset")) {
|
||||
peer.disconnect("Connection reset");
|
||||
return;
|
||||
}
|
||||
|
||||
LOGGER.trace("[{}] Network thread {} encountered I/O error: {}", peer.getPeerConnectionId(),
|
||||
Thread.currentThread().getId(), e.getMessage(), e);
|
||||
peer.disconnect("I/O error");
|
||||
}
|
||||
}
|
||||
nextBroadcastTimestamp.set(now + BROADCAST_INTERVAL);
|
||||
return new BroadcastTask();
|
||||
}
|
||||
|
||||
private Task maybeProduceChannelTask(boolean canBlock) throws InterruptedException {
|
||||
final SelectionKey nextSelectionKey;
|
||||
|
||||
// Synchronization here to enforce thread-safety on channelIterator
|
||||
synchronized (channelSelector) {
|
||||
// anything to do?
|
||||
@@ -630,91 +580,73 @@ public class Network {
|
||||
}
|
||||
|
||||
channelIterator = channelSelector.selectedKeys().iterator();
|
||||
LOGGER.trace("Thread {}, after {} select, channelIterator now {}",
|
||||
Thread.currentThread().getId(),
|
||||
canBlock ? "blocking": "non-blocking",
|
||||
channelIterator);
|
||||
}
|
||||
|
||||
if (channelIterator.hasNext()) {
|
||||
nextSelectionKey = channelIterator.next();
|
||||
channelIterator.remove();
|
||||
} else {
|
||||
nextSelectionKey = null;
|
||||
if (!channelIterator.hasNext()) {
|
||||
channelIterator = null; // Nothing to do so reset iterator to cause new select
|
||||
|
||||
LOGGER.trace("Thread {}, channelIterator now null", Thread.currentThread().getId());
|
||||
return null;
|
||||
}
|
||||
|
||||
LOGGER.trace("Thread {}, nextSelectionKey {}, channelIterator now {}",
|
||||
Thread.currentThread().getId(), nextSelectionKey, channelIterator);
|
||||
}
|
||||
final SelectionKey nextSelectionKey = channelIterator.next();
|
||||
channelIterator.remove();
|
||||
|
||||
if (nextSelectionKey == null) {
|
||||
return null;
|
||||
}
|
||||
// Just in case underlying socket channel already closed elsewhere, etc.
|
||||
if (!nextSelectionKey.isValid())
|
||||
return null;
|
||||
|
||||
return new ChannelTask(nextSelectionKey);
|
||||
}
|
||||
}
|
||||
LOGGER.trace("Thread {}, nextSelectionKey {}", Thread.currentThread().getId(), nextSelectionKey);
|
||||
|
||||
private void acceptConnection(ServerSocketChannel serverSocketChannel) throws InterruptedException {
|
||||
SocketChannel socketChannel;
|
||||
SelectableChannel socketChannel = nextSelectionKey.channel();
|
||||
|
||||
try {
|
||||
socketChannel = serverSocketChannel.accept();
|
||||
} catch (IOException e) {
|
||||
return;
|
||||
}
|
||||
|
||||
// No connection actually accepted?
|
||||
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", address);
|
||||
socketChannel.close();
|
||||
return;
|
||||
}
|
||||
|
||||
if (getImmutableConnectedPeers().size() >= maxPeers) {
|
||||
// We have enough peers
|
||||
LOGGER.debug("Connection discarded from peer {} because the server is full", address);
|
||||
socketChannel.close();
|
||||
return;
|
||||
}
|
||||
|
||||
LOGGER.debug("Connection accepted from peer {}", address);
|
||||
|
||||
newPeer = new Peer(socketChannel, channelSelector);
|
||||
this.addConnectedPeer(newPeer);
|
||||
|
||||
} 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?
|
||||
if (nextSelectionKey.isReadable()) {
|
||||
clearInterestOps(nextSelectionKey, SelectionKey.OP_READ);
|
||||
Peer peer = getPeerFromChannel((SocketChannel) socketChannel);
|
||||
if (peer == null)
|
||||
return null;
|
||||
|
||||
return new ChannelReadTask((SocketChannel) socketChannel, peer);
|
||||
}
|
||||
|
||||
if (nextSelectionKey.isWritable()) {
|
||||
clearInterestOps(nextSelectionKey, SelectionKey.OP_WRITE);
|
||||
Peer peer = getPeerFromChannel((SocketChannel) socketChannel);
|
||||
if (peer == null)
|
||||
return null;
|
||||
|
||||
// Any thread that queues a message to send can set OP_WRITE,
|
||||
// but we only allow one pending/active ChannelWriteTask per Peer
|
||||
if (!channelsPendingWrite.add(socketChannel))
|
||||
return null;
|
||||
|
||||
return new ChannelWriteTask((SocketChannel) socketChannel, peer);
|
||||
}
|
||||
|
||||
if (nextSelectionKey.isAcceptable()) {
|
||||
clearInterestOps(nextSelectionKey, SelectionKey.OP_ACCEPT);
|
||||
return new ChannelAcceptTask((ServerSocketChannel) socketChannel);
|
||||
}
|
||||
} catch (CancelledKeyException e) {
|
||||
/*
|
||||
* Sometimes nextSelectionKey is cancelled / becomes invalid between the isValid() test at line 586
|
||||
* and later calls to isReadable() / isWritable() / isAcceptable() which themselves call isValid()!
|
||||
* Those isXXXable() calls could throw CancelledKeyException, so we catch it here and return null.
|
||||
*/
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
this.onPeerReady(newPeer);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean ipNotInFixedList(PeerAddress address, List<String> fixedNetwork) {
|
||||
public 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])) {
|
||||
@@ -750,8 +682,9 @@ public class Network {
|
||||
peers.removeIf(isConnectedPeer);
|
||||
|
||||
// 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);
|
||||
// Disabled because this might be too slow if we end up waiting a long time for hostnames to resolve via DNS
|
||||
// Which is ok because duplicate connections to the same peer are handled during handshaking
|
||||
// peers.removeIf(isResolvedAsConnectedPeer);
|
||||
|
||||
this.checkLongestConnection(now);
|
||||
|
||||
@@ -766,6 +699,7 @@ public class Network {
|
||||
// Pick candidate
|
||||
PeerData peerData = peers.get(peerIndex);
|
||||
Peer newPeer = new Peer(peerData);
|
||||
newPeer.setIsDataPeer(false);
|
||||
|
||||
// Update connection attempt info
|
||||
peerData.setLastAttempted(now);
|
||||
@@ -781,8 +715,12 @@ public class Network {
|
||||
}
|
||||
}
|
||||
|
||||
private boolean connectPeer(Peer newPeer) throws InterruptedException {
|
||||
SocketChannel socketChannel = newPeer.connect(this.channelSelector);
|
||||
public boolean connectPeer(Peer newPeer) throws InterruptedException {
|
||||
// Also checked before creating PeerConnectTask
|
||||
if (getImmutableOutboundHandshakedPeers().size() >= minOutboundPeers)
|
||||
return false;
|
||||
|
||||
SocketChannel socketChannel = newPeer.connect();
|
||||
if (socketChannel == null) {
|
||||
return false;
|
||||
}
|
||||
@@ -797,7 +735,7 @@ public class Network {
|
||||
return true;
|
||||
}
|
||||
|
||||
private Peer getPeerFromChannel(SocketChannel socketChannel) {
|
||||
public Peer getPeerFromChannel(SocketChannel socketChannel) {
|
||||
for (Peer peer : this.getImmutableConnectedPeers()) {
|
||||
if (peer.getSocketChannel() == socketChannel) {
|
||||
return peer;
|
||||
@@ -830,7 +768,74 @@ public class Network {
|
||||
nextDisconnectionCheck = now + DISCONNECTION_CHECK_INTERVAL;
|
||||
}
|
||||
|
||||
// Peer callbacks
|
||||
// SocketChannel interest-ops manipulations
|
||||
|
||||
private static final String[] OP_NAMES = new String[SelectionKey.OP_ACCEPT * 2];
|
||||
static {
|
||||
for (int i = 0; i < OP_NAMES.length; i++) {
|
||||
StringJoiner joiner = new StringJoiner(",");
|
||||
|
||||
if ((i & SelectionKey.OP_READ) != 0) joiner.add("OP_READ");
|
||||
if ((i & SelectionKey.OP_WRITE) != 0) joiner.add("OP_WRITE");
|
||||
if ((i & SelectionKey.OP_CONNECT) != 0) joiner.add("OP_CONNECT");
|
||||
if ((i & SelectionKey.OP_ACCEPT) != 0) joiner.add("OP_ACCEPT");
|
||||
|
||||
OP_NAMES[i] = joiner.toString();
|
||||
}
|
||||
}
|
||||
|
||||
public void clearInterestOps(SelectableChannel socketChannel, int interestOps) {
|
||||
SelectionKey selectionKey = socketChannel.keyFor(channelSelector);
|
||||
if (selectionKey == null)
|
||||
return;
|
||||
|
||||
clearInterestOps(selectionKey, interestOps);
|
||||
}
|
||||
|
||||
private void clearInterestOps(SelectionKey selectionKey, int interestOps) {
|
||||
if (!selectionKey.channel().isOpen())
|
||||
return;
|
||||
|
||||
LOGGER.trace("Thread {} clearing {} interest-ops on channel: {}",
|
||||
Thread.currentThread().getId(),
|
||||
OP_NAMES[interestOps],
|
||||
selectionKey.channel());
|
||||
|
||||
selectionKey.interestOpsAnd(~interestOps);
|
||||
}
|
||||
|
||||
public void setInterestOps(SelectableChannel socketChannel, int interestOps) {
|
||||
SelectionKey selectionKey = socketChannel.keyFor(channelSelector);
|
||||
if (selectionKey == null) {
|
||||
try {
|
||||
selectionKey = socketChannel.register(this.channelSelector, interestOps);
|
||||
} catch (ClosedChannelException e) {
|
||||
// Channel already closed so ignore
|
||||
return;
|
||||
}
|
||||
// Fall-through to allow logging
|
||||
}
|
||||
|
||||
setInterestOps(selectionKey, interestOps);
|
||||
}
|
||||
|
||||
private void setInterestOps(SelectionKey selectionKey, int interestOps) {
|
||||
if (!selectionKey.channel().isOpen())
|
||||
return;
|
||||
|
||||
LOGGER.trace("Thread {} setting {} interest-ops on channel: {}",
|
||||
Thread.currentThread().getId(),
|
||||
OP_NAMES[interestOps],
|
||||
selectionKey.channel());
|
||||
|
||||
selectionKey.interestOpsOr(interestOps);
|
||||
}
|
||||
|
||||
// Peer / Task callbacks
|
||||
|
||||
public void notifyChannelNotWriting(SelectableChannel socketChannel) {
|
||||
this.channelsPendingWrite.remove(socketChannel);
|
||||
}
|
||||
|
||||
protected void wakeupChannelSelector() {
|
||||
this.channelSelector.wakeup();
|
||||
@@ -856,8 +861,6 @@ public class Network {
|
||||
}
|
||||
|
||||
public void onDisconnect(Peer peer) {
|
||||
// Notify Controller
|
||||
Controller.getInstance().onPeerDisconnect(peer);
|
||||
if (peer.getConnectionEstablishedTime() > 0L) {
|
||||
LOGGER.debug("[{}] Disconnected from peer {}", peer.getPeerConnectionId(), peer);
|
||||
} else {
|
||||
@@ -865,6 +868,25 @@ public class Network {
|
||||
}
|
||||
|
||||
this.removeConnectedPeer(peer);
|
||||
this.channelsPendingWrite.remove(peer.getSocketChannel());
|
||||
|
||||
if (this.isShuttingDown)
|
||||
// No need to do any further processing, like re-enabling listen socket or notifying Controller
|
||||
return;
|
||||
|
||||
if (getImmutableConnectedPeers().size() < maxPeers - 1
|
||||
&& serverSelectionKey.isValid()
|
||||
&& (serverSelectionKey.interestOps() & SelectionKey.OP_ACCEPT) == 0) {
|
||||
try {
|
||||
LOGGER.debug("Re-enabling accepting incoming connections because the server is not longer full");
|
||||
setInterestOps(serverSelectionKey, SelectionKey.OP_ACCEPT);
|
||||
} catch (CancelledKeyException e) {
|
||||
LOGGER.error("Failed to re-enable accepting of incoming connections: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// Notify Controller
|
||||
Controller.getInstance().onPeerDisconnect(peer);
|
||||
}
|
||||
|
||||
public void peerMisbehaved(Peer peer) {
|
||||
@@ -1302,8 +1324,9 @@ public class Network {
|
||||
try {
|
||||
InetSocketAddress knownAddress = peerAddress.toSocketAddress();
|
||||
|
||||
List<Peer> peers = this.getImmutableConnectedPeers();
|
||||
peers.removeIf(peer -> !Peer.addressEquals(knownAddress, peer.getResolvedAddress()));
|
||||
List<Peer> peers = this.getImmutableConnectedPeers().stream()
|
||||
.filter(peer -> Peer.addressEquals(knownAddress, peer.getResolvedAddress()))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
for (Peer peer : peers) {
|
||||
peer.disconnect("to be forgotten");
|
||||
@@ -1461,54 +1484,27 @@ public class Network {
|
||||
}
|
||||
|
||||
public void broadcast(Function<Peer, Message> peerMessageBuilder) {
|
||||
class Broadcaster implements Runnable {
|
||||
private final Random random = new Random();
|
||||
for (Peer peer : getImmutableHandshakedPeers()) {
|
||||
if (this.isShuttingDown)
|
||||
return;
|
||||
|
||||
private List<Peer> targetPeers;
|
||||
private Function<Peer, Message> peerMessageBuilder;
|
||||
Message message = peerMessageBuilder.apply(peer);
|
||||
|
||||
Broadcaster(List<Peer> targetPeers, Function<Peer, Message> peerMessageBuilder) {
|
||||
this.targetPeers = targetPeers;
|
||||
this.peerMessageBuilder = peerMessageBuilder;
|
||||
if (message == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
Thread.currentThread().setName("Network Broadcast");
|
||||
|
||||
for (Peer peer : targetPeers) {
|
||||
// Very short sleep to reduce strain, improve multi-threading and catch interrupts
|
||||
try {
|
||||
Thread.sleep(random.nextInt(20) + 20L);
|
||||
} catch (InterruptedException e) {
|
||||
break;
|
||||
}
|
||||
|
||||
Message message = peerMessageBuilder.apply(peer);
|
||||
|
||||
if (message == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!peer.sendMessage(message)) {
|
||||
peer.disconnect("failed to broadcast message");
|
||||
}
|
||||
}
|
||||
|
||||
Thread.currentThread().setName("Network Broadcast (dormant)");
|
||||
if (!peer.sendMessage(message)) {
|
||||
peer.disconnect("failed to broadcast message");
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
broadcastExecutor.execute(new Broadcaster(this.getImmutableHandshakedPeers(), peerMessageBuilder));
|
||||
} catch (RejectedExecutionException e) {
|
||||
// Can't execute - probably because we're shutting down, so ignore
|
||||
}
|
||||
}
|
||||
|
||||
// Shutdown
|
||||
|
||||
public void shutdown() {
|
||||
this.isShuttingDown = true;
|
||||
|
||||
// Close listen socket to prevent more incoming connections
|
||||
if (this.serverChannel.isOpen()) {
|
||||
try {
|
||||
@@ -1527,16 +1523,6 @@ public class Network {
|
||||
LOGGER.warn("Interrupted while waiting for networking threads to terminate");
|
||||
}
|
||||
|
||||
// Stop broadcasts
|
||||
this.broadcastExecutor.shutdownNow();
|
||||
try {
|
||||
if (!this.broadcastExecutor.awaitTermination(1000, TimeUnit.MILLISECONDS)) {
|
||||
LOGGER.warn("Broadcast threads failed to terminate");
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
LOGGER.warn("Interrupted while waiting for broadcast threads failed to terminate");
|
||||
}
|
||||
|
||||
// Close all peer connections
|
||||
for (Peer peer : this.getImmutableConnectedPeers()) {
|
||||
peer.shutdown();
|
||||
|
@@ -11,25 +11,21 @@ import org.qortal.data.network.PeerChainTipData;
|
||||
import org.qortal.data.network.PeerData;
|
||||
import org.qortal.network.message.ChallengeMessage;
|
||||
import org.qortal.network.message.Message;
|
||||
import org.qortal.network.message.Message.MessageException;
|
||||
import org.qortal.network.message.Message.MessageType;
|
||||
import org.qortal.network.message.PingMessage;
|
||||
import org.qortal.network.message.MessageException;
|
||||
import org.qortal.network.task.MessageTask;
|
||||
import org.qortal.network.task.PingTask;
|
||||
import org.qortal.settings.Settings;
|
||||
import org.qortal.utils.ExecuteProduceConsume;
|
||||
import org.qortal.utils.ExecuteProduceConsume.Task;
|
||||
import org.qortal.utils.NTP;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.*;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.SelectionKey;
|
||||
import java.nio.channels.Selector;
|
||||
import java.nio.channels.SocketChannel;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ArrayBlockingQueue;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@@ -48,9 +44,9 @@ public class Peer {
|
||||
private static final int RESPONSE_TIMEOUT = 3000; // ms
|
||||
|
||||
/**
|
||||
* Maximum time to wait for a peer to respond with blocks (ms)
|
||||
* Maximum time to wait for a message to be added to sendQueue (ms)
|
||||
*/
|
||||
public static final int FETCH_BLOCKS_TIMEOUT = 10000;
|
||||
private static final int QUEUE_TIMEOUT = 1000; // ms
|
||||
|
||||
/**
|
||||
* Interval between PING messages to a peer. (ms)
|
||||
@@ -68,13 +64,22 @@ public class Peer {
|
||||
*/
|
||||
private boolean isLocal;
|
||||
|
||||
/**
|
||||
* True if connected for the purposes of transfering specific QDN data
|
||||
*/
|
||||
private boolean isDataPeer;
|
||||
|
||||
private final UUID peerConnectionId = UUID.randomUUID();
|
||||
private final Object byteBufferLock = new Object();
|
||||
private ByteBuffer byteBuffer;
|
||||
|
||||
private Map<Integer, BlockingQueue<Message>> replyQueues;
|
||||
private LinkedBlockingQueue<Message> pendingMessages;
|
||||
|
||||
private TransferQueue<Message> sendQueue;
|
||||
private ByteBuffer outputBuffer;
|
||||
private String outputMessageType;
|
||||
private int outputMessageId;
|
||||
|
||||
/**
|
||||
* True if we created connection to peer, false if we accepted incoming connection from peer.
|
||||
*/
|
||||
@@ -98,7 +103,7 @@ public class Peer {
|
||||
/**
|
||||
* When last PING message was sent, or null if pings not started yet.
|
||||
*/
|
||||
private Long lastPingSent;
|
||||
private Long lastPingSent = null;
|
||||
|
||||
byte[] ourChallenge;
|
||||
|
||||
@@ -160,10 +165,10 @@ public class Peer {
|
||||
/**
|
||||
* Construct Peer using existing, connected socket
|
||||
*/
|
||||
public Peer(SocketChannel socketChannel, Selector channelSelector) throws IOException {
|
||||
public Peer(SocketChannel socketChannel) throws IOException {
|
||||
this.isOutbound = false;
|
||||
this.socketChannel = socketChannel;
|
||||
sharedSetup(channelSelector);
|
||||
sharedSetup();
|
||||
|
||||
this.resolvedAddress = ((InetSocketAddress) socketChannel.socket().getRemoteSocketAddress());
|
||||
this.isLocal = isAddressLocal(this.resolvedAddress.getAddress());
|
||||
@@ -194,6 +199,14 @@ public class Peer {
|
||||
return this.isOutbound;
|
||||
}
|
||||
|
||||
public boolean isDataPeer() {
|
||||
return isDataPeer;
|
||||
}
|
||||
|
||||
public void setIsDataPeer(boolean isDataPeer) {
|
||||
this.isDataPeer = isDataPeer;
|
||||
}
|
||||
|
||||
public Handshake getHandshakeStatus() {
|
||||
synchronized (this.handshakingLock) {
|
||||
return this.handshakeStatus;
|
||||
@@ -211,6 +224,11 @@ public class Peer {
|
||||
}
|
||||
|
||||
private void generateRandomMaxConnectionAge() {
|
||||
if (this.maxConnectionAge > 0L) {
|
||||
// Already generated, so we don't want to overwrite the existing value
|
||||
return;
|
||||
}
|
||||
|
||||
// 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();
|
||||
@@ -276,7 +294,7 @@ public class Peer {
|
||||
}
|
||||
}
|
||||
|
||||
protected void setLastPing(long lastPing) {
|
||||
public void setLastPing(long lastPing) {
|
||||
synchronized (this.peerInfoLock) {
|
||||
this.lastPing = lastPing;
|
||||
}
|
||||
@@ -346,12 +364,6 @@ public class Peer {
|
||||
}
|
||||
}
|
||||
|
||||
protected void queueMessage(Message message) {
|
||||
if (!this.pendingMessages.offer(message)) {
|
||||
LOGGER.info("[{}] No room to queue message from peer {} - discarding", this.peerConnectionId, this);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isSyncInProgress() {
|
||||
return this.syncInProgress;
|
||||
}
|
||||
@@ -396,13 +408,14 @@ public class Peer {
|
||||
|
||||
// Processing
|
||||
|
||||
private void sharedSetup(Selector channelSelector) throws IOException {
|
||||
private void sharedSetup() throws IOException {
|
||||
this.connectionTimestamp = NTP.getTime();
|
||||
this.socketChannel.setOption(StandardSocketOptions.TCP_NODELAY, true);
|
||||
this.socketChannel.configureBlocking(false);
|
||||
this.socketChannel.register(channelSelector, SelectionKey.OP_READ);
|
||||
Network.getInstance().setInterestOps(this.socketChannel, SelectionKey.OP_READ);
|
||||
this.byteBuffer = null; // Defer allocation to when we need it, to save memory. Sorry GC!
|
||||
this.replyQueues = Collections.synchronizedMap(new HashMap<Integer, BlockingQueue<Message>>());
|
||||
this.sendQueue = new LinkedTransferQueue<>();
|
||||
this.replyQueues = new ConcurrentHashMap<>();
|
||||
this.pendingMessages = new LinkedBlockingQueue<>();
|
||||
|
||||
Random random = new SecureRandom();
|
||||
@@ -410,7 +423,7 @@ public class Peer {
|
||||
random.nextBytes(this.ourChallenge);
|
||||
}
|
||||
|
||||
public SocketChannel connect(Selector channelSelector) {
|
||||
public SocketChannel connect() {
|
||||
LOGGER.trace("[{}] Connecting to peer {}", this.peerConnectionId, this);
|
||||
|
||||
try {
|
||||
@@ -418,6 +431,8 @@ public class Peer {
|
||||
this.isLocal = isAddressLocal(this.resolvedAddress.getAddress());
|
||||
|
||||
this.socketChannel = SocketChannel.open();
|
||||
InetAddress bindAddr = InetAddress.getByName(Settings.getInstance().getBindAddress());
|
||||
this.socketChannel.socket().bind(new InetSocketAddress(bindAddr, 0));
|
||||
this.socketChannel.socket().connect(resolvedAddress, CONNECT_TIMEOUT);
|
||||
} catch (SocketTimeoutException e) {
|
||||
LOGGER.trace("[{}] Connection timed out to peer {}", this.peerConnectionId, this);
|
||||
@@ -432,7 +447,7 @@ public class Peer {
|
||||
|
||||
try {
|
||||
LOGGER.debug("[{}] Connected to peer {}", this.peerConnectionId, this);
|
||||
sharedSetup(channelSelector);
|
||||
sharedSetup();
|
||||
return socketChannel;
|
||||
} catch (IOException e) {
|
||||
LOGGER.trace("[{}] Post-connection setup failed, peer {}", this.peerConnectionId, this);
|
||||
@@ -450,7 +465,7 @@ public class Peer {
|
||||
*
|
||||
* @throws IOException If this channel is not yet connected
|
||||
*/
|
||||
protected void readChannel() throws IOException {
|
||||
public void readChannel() throws IOException {
|
||||
synchronized (this.byteBufferLock) {
|
||||
while (true) {
|
||||
if (!this.socketChannel.isOpen() || this.socketChannel.socket().isClosed()) {
|
||||
@@ -556,7 +571,67 @@ public class Peer {
|
||||
}
|
||||
}
|
||||
|
||||
protected ExecuteProduceConsume.Task getMessageTask() {
|
||||
/** Maybe send some pending outgoing messages.
|
||||
*
|
||||
* @return true if more data is pending to be sent
|
||||
*/
|
||||
public boolean writeChannel() throws IOException {
|
||||
// It is the responsibility of ChannelWriteTask's producer to produce only one call to writeChannel() at a time
|
||||
|
||||
while (true) {
|
||||
// If output byte buffer is null, fetch next message from queue (if any)
|
||||
while (this.outputBuffer == null) {
|
||||
Message message;
|
||||
|
||||
try {
|
||||
// Allow other thread time to add message to queue having raised OP_WRITE.
|
||||
// Timeout is overkill but not excessive enough to clog up networking / EPC.
|
||||
// This is to avoid race condition in sendMessageWithTimeout() below.
|
||||
message = this.sendQueue.poll(QUEUE_TIMEOUT, TimeUnit.MILLISECONDS);
|
||||
} catch (InterruptedException e) {
|
||||
// Shutdown situation
|
||||
return false;
|
||||
}
|
||||
|
||||
// No message? No further work to be done
|
||||
if (message == null)
|
||||
return false;
|
||||
|
||||
try {
|
||||
this.outputBuffer = ByteBuffer.wrap(message.toBytes());
|
||||
this.outputMessageType = message.getType().name();
|
||||
this.outputMessageId = message.getId();
|
||||
|
||||
LOGGER.trace("[{}] Sending {} message with ID {} to peer {}",
|
||||
this.peerConnectionId, this.outputMessageType, this.outputMessageId, this);
|
||||
} catch (MessageException e) {
|
||||
// Something went wrong converting message to bytes, so discard but allow another round
|
||||
LOGGER.warn("[{}] Failed to send {} message with ID {} to peer {}: {}", this.peerConnectionId,
|
||||
message.getType().name(), message.getId(), this, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// If output byte buffer is not null, send from that
|
||||
int bytesWritten = this.socketChannel.write(outputBuffer);
|
||||
|
||||
LOGGER.trace("[{}] Sent {} bytes of {} message with ID {} to peer {} ({} total)", this.peerConnectionId,
|
||||
bytesWritten, this.outputMessageType, this.outputMessageId, this, outputBuffer.limit());
|
||||
|
||||
// If we've sent 0 bytes then socket buffer is full so we need to wait until it's empty again
|
||||
if (bytesWritten == 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// If we then exhaust the byte buffer, set it to null (otherwise loop and try to send more)
|
||||
if (!this.outputBuffer.hasRemaining()) {
|
||||
this.outputMessageType = null;
|
||||
this.outputMessageId = 0;
|
||||
this.outputBuffer = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected Task getMessageTask() {
|
||||
/*
|
||||
* If we are still handshaking and there is a message yet to be processed then
|
||||
* don't produce another message task. This allows us to process handshake
|
||||
@@ -580,7 +655,7 @@ public class Peer {
|
||||
}
|
||||
|
||||
// Return a task to process message in queue
|
||||
return () -> Network.getInstance().onMessage(this, nextMessage);
|
||||
return new MessageTask(this, nextMessage);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -605,54 +680,25 @@ public class Peer {
|
||||
}
|
||||
|
||||
try {
|
||||
// Send message
|
||||
LOGGER.trace("[{}] Sending {} message with ID {} to peer {}", this.peerConnectionId,
|
||||
// Queue message, to be picked up by ChannelWriteTask and then peer.writeChannel()
|
||||
LOGGER.trace("[{}] Queuing {} message with ID {} to peer {}", this.peerConnectionId,
|
||||
message.getType().name(), message.getId(), this);
|
||||
|
||||
ByteBuffer outputBuffer = ByteBuffer.wrap(message.toBytes());
|
||||
// Check message properly constructed
|
||||
message.checkValidOutgoing();
|
||||
|
||||
synchronized (this.socketChannel) {
|
||||
final long sendStart = System.currentTimeMillis();
|
||||
long totalBytes = 0;
|
||||
|
||||
while (outputBuffer.hasRemaining()) {
|
||||
int bytesWritten = this.socketChannel.write(outputBuffer);
|
||||
totalBytes += bytesWritten;
|
||||
|
||||
LOGGER.trace("[{}] Sent {} bytes of {} message with ID {} to peer {} ({} total)", this.peerConnectionId,
|
||||
bytesWritten, message.getType().name(), message.getId(), this, totalBytes);
|
||||
|
||||
if (bytesWritten == 0) {
|
||||
// Underlying socket's internal buffer probably full,
|
||||
// so wait a short while for bytes to actually be transmitted over the wire
|
||||
|
||||
/*
|
||||
* NOSONAR squid:S2276 - we don't want to use this.socketChannel.wait()
|
||||
* as this releases the lock held by synchronized() above
|
||||
* and would allow another thread to send another message,
|
||||
* potentially interleaving them on-the-wire, causing checksum failures
|
||||
* and connection loss.
|
||||
*/
|
||||
Thread.sleep(1L); //NOSONAR squid:S2276
|
||||
|
||||
if (System.currentTimeMillis() - sendStart > timeout) {
|
||||
// We've taken too long to send this message
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (MessageException e) {
|
||||
LOGGER.warn("[{}] Failed to send {} message with ID {} to peer {}: {}", this.peerConnectionId,
|
||||
message.getType().name(), message.getId(), this, e.getMessage());
|
||||
return false;
|
||||
} catch (IOException | InterruptedException e) {
|
||||
// Possible race condition:
|
||||
// We set OP_WRITE, EPC creates ChannelWriteTask which calls Peer.writeChannel, writeChannel's poll() finds no message to send
|
||||
// Avoided by poll-with-timeout in writeChannel() above.
|
||||
Network.getInstance().setInterestOps(this.socketChannel, SelectionKey.OP_WRITE);
|
||||
return this.sendQueue.tryTransfer(message, timeout, TimeUnit.MILLISECONDS);
|
||||
} catch (InterruptedException e) {
|
||||
// Send failure
|
||||
return false;
|
||||
} catch (MessageException e) {
|
||||
LOGGER.error(e.getMessage(), e);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Sent OK
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -720,7 +766,7 @@ public class Peer {
|
||||
this.lastPingSent = NTP.getTime();
|
||||
}
|
||||
|
||||
protected ExecuteProduceConsume.Task getPingTask(Long now) {
|
||||
protected Task getPingTask(Long now) {
|
||||
// Pings not enabled yet?
|
||||
if (now == null || this.lastPingSent == null) {
|
||||
return null;
|
||||
@@ -734,19 +780,7 @@ public class Peer {
|
||||
// Not strictly true, but prevents this peer from being immediately chosen again
|
||||
this.lastPingSent = now;
|
||||
|
||||
return () -> {
|
||||
PingMessage pingMessage = new PingMessage();
|
||||
Message message = this.getResponse(pingMessage);
|
||||
|
||||
if (message == null || message.getType() != MessageType.PING) {
|
||||
LOGGER.debug("[{}] Didn't receive reply from {} for PING ID {}", this.peerConnectionId, this,
|
||||
pingMessage.getId());
|
||||
this.disconnect("no ping received");
|
||||
return;
|
||||
}
|
||||
|
||||
this.setLastPing(NTP.getTime() - now);
|
||||
};
|
||||
return new PingTask(this, now);
|
||||
}
|
||||
|
||||
public void disconnect(String reason) {
|
||||
@@ -877,6 +911,10 @@ public class Peer {
|
||||
return maxConnectionAge;
|
||||
}
|
||||
|
||||
public void setMaxConnectionAge(long maxConnectionAge) {
|
||||
this.maxConnectionAge = maxConnectionAge;
|
||||
}
|
||||
|
||||
public boolean hasReachedMaxConnectionAge() {
|
||||
return this.getConnectionAge() > this.getMaxConnectionAge();
|
||||
}
|
||||
|
@@ -9,38 +9,59 @@ import org.qortal.utils.Serialization;
|
||||
|
||||
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 ArbitraryDataFileListMessage extends Message {
|
||||
|
||||
private static final int SIGNATURE_LENGTH = Transformer.SIGNATURE_LENGTH;
|
||||
private static final int HASH_LENGTH = Transformer.SHA256_LENGTH;
|
||||
private static final int MAX_PEER_ADDRESS_LENGTH = PeerData.MAX_PEER_ADDRESS_SIZE;
|
||||
|
||||
private final byte[] signature;
|
||||
private final List<byte[]> hashes;
|
||||
private byte[] signature;
|
||||
private List<byte[]> hashes;
|
||||
private Long requestTime;
|
||||
private Integer requestHops;
|
||||
private String peerAddress;
|
||||
private Boolean isRelayPossible;
|
||||
|
||||
|
||||
public ArbitraryDataFileListMessage(byte[] signature, List<byte[]> hashes, Long requestTime,
|
||||
Integer requestHops, String peerAddress, boolean isRelayPossible) {
|
||||
Integer requestHops, String peerAddress, Boolean isRelayPossible) {
|
||||
super(MessageType.ARBITRARY_DATA_FILE_LIST);
|
||||
|
||||
this.signature = signature;
|
||||
this.hashes = hashes;
|
||||
this.requestTime = requestTime;
|
||||
this.requestHops = requestHops;
|
||||
this.peerAddress = peerAddress;
|
||||
this.isRelayPossible = isRelayPossible;
|
||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
|
||||
|
||||
try {
|
||||
bytes.write(signature);
|
||||
|
||||
bytes.write(Ints.toByteArray(hashes.size()));
|
||||
|
||||
for (byte[] hash : hashes) {
|
||||
bytes.write(hash);
|
||||
}
|
||||
|
||||
if (requestTime != null) {
|
||||
// The remaining fields are optional
|
||||
|
||||
bytes.write(Longs.toByteArray(requestTime));
|
||||
|
||||
bytes.write(Ints.toByteArray(requestHops));
|
||||
|
||||
Serialization.serializeSizedStringV2(bytes, peerAddress);
|
||||
|
||||
bytes.write(Ints.toByteArray(Boolean.TRUE.equals(isRelayPossible) ? 1 : 0));
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new AssertionError("IOException shouldn't occur with ByteArrayOutputStream");
|
||||
}
|
||||
|
||||
this.dataBytes = bytes.toByteArray();
|
||||
this.checksumBytes = Message.generateChecksum(this.dataBytes);
|
||||
}
|
||||
|
||||
public ArbitraryDataFileListMessage(int id, byte[] signature, List<byte[]> hashes, Long requestTime,
|
||||
/** Legacy version */
|
||||
public ArbitraryDataFileListMessage(byte[] signature, List<byte[]> hashes) {
|
||||
this(signature, hashes, null, null, null, null);
|
||||
}
|
||||
|
||||
private ArbitraryDataFileListMessage(int id, byte[] signature, List<byte[]> hashes, Long requestTime,
|
||||
Integer requestHops, String peerAddress, boolean isRelayPossible) {
|
||||
super(id, MessageType.ARBITRARY_DATA_FILE_LIST);
|
||||
|
||||
@@ -52,24 +73,39 @@ public class ArbitraryDataFileListMessage extends Message {
|
||||
this.isRelayPossible = isRelayPossible;
|
||||
}
|
||||
|
||||
public List<byte[]> getHashes() {
|
||||
return this.hashes;
|
||||
}
|
||||
|
||||
public byte[] getSignature() {
|
||||
return this.signature;
|
||||
}
|
||||
|
||||
public static Message fromByteBuffer(int id, ByteBuffer bytes) throws UnsupportedEncodingException, TransformationException {
|
||||
byte[] signature = new byte[SIGNATURE_LENGTH];
|
||||
public List<byte[]> getHashes() {
|
||||
return this.hashes;
|
||||
}
|
||||
|
||||
public Long getRequestTime() {
|
||||
return this.requestTime;
|
||||
}
|
||||
|
||||
public Integer getRequestHops() {
|
||||
return this.requestHops;
|
||||
}
|
||||
|
||||
public String getPeerAddress() {
|
||||
return this.peerAddress;
|
||||
}
|
||||
|
||||
public Boolean isRelayPossible() {
|
||||
return this.isRelayPossible;
|
||||
}
|
||||
|
||||
public static Message fromByteBuffer(int id, ByteBuffer bytes) throws MessageException {
|
||||
byte[] signature = new byte[Transformer.SIGNATURE_LENGTH];
|
||||
bytes.get(signature);
|
||||
|
||||
int count = bytes.getInt();
|
||||
|
||||
List<byte[]> hashes = new ArrayList<>();
|
||||
for (int i = 0; i < count; ++i) {
|
||||
|
||||
byte[] hash = new byte[HASH_LENGTH];
|
||||
byte[] hash = new byte[Transformer.SHA256_LENGTH];
|
||||
bytes.get(hash);
|
||||
hashes.add(hash);
|
||||
}
|
||||
@@ -80,99 +116,21 @@ public class ArbitraryDataFileListMessage extends Message {
|
||||
boolean isRelayPossible = true; // Legacy versions only send this message when relaying is possible
|
||||
|
||||
// The remaining fields are optional
|
||||
|
||||
if (bytes.hasRemaining()) {
|
||||
try {
|
||||
requestTime = bytes.getLong();
|
||||
|
||||
requestTime = bytes.getLong();
|
||||
requestHops = bytes.getInt();
|
||||
|
||||
requestHops = bytes.getInt();
|
||||
|
||||
peerAddress = Serialization.deserializeSizedStringV2(bytes, MAX_PEER_ADDRESS_LENGTH);
|
||||
|
||||
isRelayPossible = bytes.getInt() > 0;
|
||||
peerAddress = Serialization.deserializeSizedStringV2(bytes, PeerData.MAX_PEER_ADDRESS_SIZE);
|
||||
|
||||
isRelayPossible = bytes.getInt() > 0;
|
||||
} catch (TransformationException e) {
|
||||
throw new MessageException(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
return new ArbitraryDataFileListMessage(id, signature, hashes, requestTime, requestHops, peerAddress, isRelayPossible);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected byte[] toData() {
|
||||
try {
|
||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
|
||||
|
||||
bytes.write(this.signature);
|
||||
|
||||
bytes.write(Ints.toByteArray(this.hashes.size()));
|
||||
|
||||
for (byte[] hash : this.hashes) {
|
||||
bytes.write(hash);
|
||||
}
|
||||
|
||||
if (this.requestTime == null) { // To maintain backwards support
|
||||
return bytes.toByteArray();
|
||||
}
|
||||
|
||||
// The remaining fields are optional
|
||||
|
||||
bytes.write(Longs.toByteArray(this.requestTime));
|
||||
|
||||
bytes.write(Ints.toByteArray(this.requestHops));
|
||||
|
||||
Serialization.serializeSizedStringV2(bytes, this.peerAddress);
|
||||
|
||||
bytes.write(Ints.toByteArray(this.isRelayPossible ? 1 : 0));
|
||||
|
||||
return bytes.toByteArray();
|
||||
} catch (IOException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public ArbitraryDataFileListMessage cloneWithNewId(int newId) {
|
||||
ArbitraryDataFileListMessage clone = new ArbitraryDataFileListMessage(this.signature, this.hashes,
|
||||
this.requestTime, this.requestHops, this.peerAddress, this.isRelayPossible);
|
||||
clone.setId(newId);
|
||||
return clone;
|
||||
}
|
||||
|
||||
public void removeOptionalStats() {
|
||||
this.requestTime = null;
|
||||
this.requestHops = null;
|
||||
this.peerAddress = null;
|
||||
this.isRelayPossible = null;
|
||||
}
|
||||
|
||||
public Long getRequestTime() {
|
||||
return this.requestTime;
|
||||
}
|
||||
|
||||
public void setRequestTime(Long requestTime) {
|
||||
this.requestTime = requestTime;
|
||||
}
|
||||
|
||||
public Integer getRequestHops() {
|
||||
return this.requestHops;
|
||||
}
|
||||
|
||||
public void setRequestHops(Integer requestHops) {
|
||||
this.requestHops = requestHops;
|
||||
}
|
||||
|
||||
public String getPeerAddress() {
|
||||
return this.peerAddress;
|
||||
}
|
||||
|
||||
public void setPeerAddress(String peerAddress) {
|
||||
this.peerAddress = peerAddress;
|
||||
}
|
||||
|
||||
public Boolean isRelayPossible() {
|
||||
return this.isRelayPossible;
|
||||
}
|
||||
|
||||
public void setIsRelayPossible(Boolean isRelayPossible) {
|
||||
this.isRelayPossible = isRelayPossible;
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -9,44 +9,60 @@ import org.qortal.transform.Transformer;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.nio.BufferUnderflowException;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public class ArbitraryDataFileMessage extends Message {
|
||||
|
||||
private static final Logger LOGGER = LogManager.getLogger(ArbitraryDataFileMessage.class);
|
||||
|
||||
private static final int SIGNATURE_LENGTH = Transformer.SIGNATURE_LENGTH;
|
||||
|
||||
private final byte[] signature;
|
||||
private final ArbitraryDataFile arbitraryDataFile;
|
||||
private byte[] signature;
|
||||
private ArbitraryDataFile arbitraryDataFile;
|
||||
|
||||
public ArbitraryDataFileMessage(byte[] signature, ArbitraryDataFile arbitraryDataFile) {
|
||||
super(MessageType.ARBITRARY_DATA_FILE);
|
||||
|
||||
this.signature = signature;
|
||||
this.arbitraryDataFile = arbitraryDataFile;
|
||||
byte[] data = arbitraryDataFile.getBytes();
|
||||
|
||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
|
||||
|
||||
try {
|
||||
bytes.write(signature);
|
||||
|
||||
bytes.write(Ints.toByteArray(data.length));
|
||||
|
||||
bytes.write(data);
|
||||
} catch (IOException e) {
|
||||
throw new AssertionError("IOException shouldn't occur with ByteArrayOutputStream");
|
||||
}
|
||||
|
||||
this.dataBytes = bytes.toByteArray();
|
||||
this.checksumBytes = Message.generateChecksum(this.dataBytes);
|
||||
}
|
||||
|
||||
public ArbitraryDataFileMessage(int id, byte[] signature, ArbitraryDataFile arbitraryDataFile) {
|
||||
private ArbitraryDataFileMessage(int id, byte[] signature, ArbitraryDataFile arbitraryDataFile) {
|
||||
super(id, MessageType.ARBITRARY_DATA_FILE);
|
||||
|
||||
this.signature = signature;
|
||||
this.arbitraryDataFile = arbitraryDataFile;
|
||||
}
|
||||
|
||||
public byte[] getSignature() {
|
||||
return this.signature;
|
||||
}
|
||||
|
||||
public ArbitraryDataFile getArbitraryDataFile() {
|
||||
return this.arbitraryDataFile;
|
||||
}
|
||||
|
||||
public static Message fromByteBuffer(int id, ByteBuffer byteBuffer) throws UnsupportedEncodingException {
|
||||
byte[] signature = new byte[SIGNATURE_LENGTH];
|
||||
public static Message fromByteBuffer(int id, ByteBuffer byteBuffer) throws MessageException {
|
||||
byte[] signature = new byte[Transformer.SIGNATURE_LENGTH];
|
||||
byteBuffer.get(signature);
|
||||
|
||||
int dataLength = byteBuffer.getInt();
|
||||
|
||||
if (byteBuffer.remaining() != dataLength)
|
||||
return null;
|
||||
if (byteBuffer.remaining() < dataLength)
|
||||
throw new BufferUnderflowException();
|
||||
|
||||
byte[] data = new byte[dataLength];
|
||||
byteBuffer.get(data);
|
||||
@@ -54,43 +70,10 @@ public class ArbitraryDataFileMessage extends Message {
|
||||
try {
|
||||
ArbitraryDataFile arbitraryDataFile = new ArbitraryDataFile(data, signature);
|
||||
return new ArbitraryDataFileMessage(id, signature, arbitraryDataFile);
|
||||
}
|
||||
catch (DataException e) {
|
||||
} catch (DataException e) {
|
||||
LOGGER.info("Unable to process received file: {}", e.getMessage());
|
||||
return null;
|
||||
throw new MessageException("Unable to process received file: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected byte[] toData() {
|
||||
if (this.arbitraryDataFile == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
byte[] data = this.arbitraryDataFile.getBytes();
|
||||
if (data == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
|
||||
|
||||
bytes.write(signature);
|
||||
|
||||
bytes.write(Ints.toByteArray(data.length));
|
||||
|
||||
bytes.write(data);
|
||||
|
||||
return bytes.toByteArray();
|
||||
} catch (IOException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public ArbitraryDataFileMessage cloneWithNewId(int newId) {
|
||||
ArbitraryDataFileMessage clone = new ArbitraryDataFileMessage(this.signature, this.arbitraryDataFile);
|
||||
clone.setId(newId);
|
||||
return clone;
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -2,7 +2,7 @@ package org.qortal.network.message;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.nio.BufferUnderflowException;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.qortal.transform.Transformer;
|
||||
@@ -11,13 +11,26 @@ import com.google.common.primitives.Ints;
|
||||
|
||||
public class ArbitraryDataMessage extends Message {
|
||||
|
||||
private static final int SIGNATURE_LENGTH = Transformer.SIGNATURE_LENGTH;
|
||||
|
||||
private byte[] signature;
|
||||
private byte[] data;
|
||||
|
||||
public ArbitraryDataMessage(byte[] signature, byte[] data) {
|
||||
this(-1, signature, data);
|
||||
super(MessageType.ARBITRARY_DATA);
|
||||
|
||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
|
||||
|
||||
try {
|
||||
bytes.write(signature);
|
||||
|
||||
bytes.write(Ints.toByteArray(data.length));
|
||||
|
||||
bytes.write(data);
|
||||
} catch (IOException e) {
|
||||
throw new AssertionError("IOException shouldn't occur with ByteArrayOutputStream");
|
||||
}
|
||||
|
||||
this.dataBytes = bytes.toByteArray();
|
||||
this.checksumBytes = Message.generateChecksum(this.dataBytes);
|
||||
}
|
||||
|
||||
private ArbitraryDataMessage(int id, byte[] signature, byte[] data) {
|
||||
@@ -35,14 +48,14 @@ public class ArbitraryDataMessage extends Message {
|
||||
return this.data;
|
||||
}
|
||||
|
||||
public static Message fromByteBuffer(int id, ByteBuffer byteBuffer) throws UnsupportedEncodingException {
|
||||
byte[] signature = new byte[SIGNATURE_LENGTH];
|
||||
public static Message fromByteBuffer(int id, ByteBuffer byteBuffer) {
|
||||
byte[] signature = new byte[Transformer.SIGNATURE_LENGTH];
|
||||
byteBuffer.get(signature);
|
||||
|
||||
int dataLength = byteBuffer.getInt();
|
||||
|
||||
if (byteBuffer.remaining() != dataLength)
|
||||
return null;
|
||||
if (byteBuffer.remaining() < dataLength)
|
||||
throw new BufferUnderflowException();
|
||||
|
||||
byte[] data = new byte[dataLength];
|
||||
byteBuffer.get(data);
|
||||
@@ -50,24 +63,4 @@ public class ArbitraryDataMessage extends Message {
|
||||
return new ArbitraryDataMessage(id, signature, data);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected byte[] toData() {
|
||||
if (this.data == null)
|
||||
return null;
|
||||
|
||||
try {
|
||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
|
||||
|
||||
bytes.write(this.signature);
|
||||
|
||||
bytes.write(Ints.toByteArray(this.data.length));
|
||||
|
||||
bytes.write(this.data);
|
||||
|
||||
return bytes.toByteArray();
|
||||
} catch (IOException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -7,28 +7,40 @@ import org.qortal.transform.Transformer;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.nio.BufferUnderflowException;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public class ArbitraryMetadataMessage extends Message {
|
||||
|
||||
private static final int SIGNATURE_LENGTH = Transformer.SIGNATURE_LENGTH;
|
||||
private byte[] signature;
|
||||
private ArbitraryDataFile arbitraryMetadataFile;
|
||||
|
||||
private final byte[] signature;
|
||||
private final ArbitraryDataFile arbitraryMetadataFile;
|
||||
|
||||
public ArbitraryMetadataMessage(byte[] signature, ArbitraryDataFile arbitraryDataFile) {
|
||||
public ArbitraryMetadataMessage(byte[] signature, ArbitraryDataFile arbitraryMetadataFile) {
|
||||
super(MessageType.ARBITRARY_METADATA);
|
||||
|
||||
this.signature = signature;
|
||||
this.arbitraryMetadataFile = arbitraryDataFile;
|
||||
byte[] data = arbitraryMetadataFile.getBytes();
|
||||
|
||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
|
||||
|
||||
try {
|
||||
bytes.write(signature);
|
||||
|
||||
bytes.write(Ints.toByteArray(data.length));
|
||||
|
||||
bytes.write(data);
|
||||
} catch (IOException e) {
|
||||
throw new AssertionError("IOException shouldn't occur with ByteArrayOutputStream");
|
||||
}
|
||||
|
||||
this.dataBytes = bytes.toByteArray();
|
||||
this.checksumBytes = Message.generateChecksum(this.dataBytes);
|
||||
}
|
||||
|
||||
public ArbitraryMetadataMessage(int id, byte[] signature, ArbitraryDataFile arbitraryDataFile) {
|
||||
private ArbitraryMetadataMessage(int id, byte[] signature, ArbitraryDataFile arbitraryMetadataFile) {
|
||||
super(id, MessageType.ARBITRARY_METADATA);
|
||||
|
||||
this.signature = signature;
|
||||
this.arbitraryMetadataFile = arbitraryDataFile;
|
||||
this.arbitraryMetadataFile = arbitraryMetadataFile;
|
||||
}
|
||||
|
||||
public byte[] getSignature() {
|
||||
@@ -39,14 +51,14 @@ public class ArbitraryMetadataMessage extends Message {
|
||||
return this.arbitraryMetadataFile;
|
||||
}
|
||||
|
||||
public static Message fromByteBuffer(int id, ByteBuffer byteBuffer) throws UnsupportedEncodingException {
|
||||
byte[] signature = new byte[SIGNATURE_LENGTH];
|
||||
public static Message fromByteBuffer(int id, ByteBuffer byteBuffer) throws MessageException {
|
||||
byte[] signature = new byte[Transformer.SIGNATURE_LENGTH];
|
||||
byteBuffer.get(signature);
|
||||
|
||||
int dataLength = byteBuffer.getInt();
|
||||
|
||||
if (byteBuffer.remaining() != dataLength)
|
||||
return null;
|
||||
if (byteBuffer.remaining() < dataLength)
|
||||
throw new BufferUnderflowException();
|
||||
|
||||
byte[] data = new byte[dataLength];
|
||||
byteBuffer.get(data);
|
||||
@@ -54,42 +66,9 @@ public class ArbitraryMetadataMessage extends Message {
|
||||
try {
|
||||
ArbitraryDataFile arbitraryMetadataFile = new ArbitraryDataFile(data, signature);
|
||||
return new ArbitraryMetadataMessage(id, signature, arbitraryMetadataFile);
|
||||
} catch (DataException e) {
|
||||
throw new MessageException("Unable to process arbitrary metadata message: " + e.getMessage(), e);
|
||||
}
|
||||
catch (DataException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected byte[] toData() {
|
||||
if (this.arbitraryMetadataFile == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
byte[] data = this.arbitraryMetadataFile.getBytes();
|
||||
if (data == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
|
||||
|
||||
bytes.write(signature);
|
||||
|
||||
bytes.write(Ints.toByteArray(data.length));
|
||||
|
||||
bytes.write(data);
|
||||
|
||||
return bytes.toByteArray();
|
||||
} catch (IOException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public ArbitraryMetadataMessage cloneWithNewId(int newId) {
|
||||
ArbitraryMetadataMessage clone = new ArbitraryMetadataMessage(this.signature, this.arbitraryMetadataFile);
|
||||
clone.setId(newId);
|
||||
return clone;
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -8,21 +8,37 @@ import org.qortal.utils.Serialization;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.nio.BufferUnderflowException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class ArbitrarySignaturesMessage extends Message {
|
||||
|
||||
private static final int SIGNATURE_LENGTH = Transformer.SIGNATURE_LENGTH;
|
||||
|
||||
private String peerAddress;
|
||||
private int requestHops;
|
||||
private List<byte[]> signatures;
|
||||
|
||||
public ArbitrarySignaturesMessage(String peerAddress, int requestHops, List<byte[]> signatures) {
|
||||
this(-1, peerAddress, requestHops, signatures);
|
||||
super(MessageType.ARBITRARY_SIGNATURES);
|
||||
|
||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
|
||||
|
||||
try {
|
||||
Serialization.serializeSizedStringV2(bytes, peerAddress);
|
||||
|
||||
bytes.write(Ints.toByteArray(requestHops));
|
||||
|
||||
bytes.write(Ints.toByteArray(signatures.size()));
|
||||
|
||||
for (byte[] signature : signatures)
|
||||
bytes.write(signature);
|
||||
} catch (IOException e) {
|
||||
throw new AssertionError("IOException shouldn't occur with ByteArrayOutputStream");
|
||||
}
|
||||
|
||||
this.dataBytes = bytes.toByteArray();
|
||||
this.checksumBytes = Message.generateChecksum(this.dataBytes);
|
||||
}
|
||||
|
||||
private ArbitrarySignaturesMessage(int id, String peerAddress, int requestHops, List<byte[]> signatures) {
|
||||
@@ -41,27 +57,24 @@ public class ArbitrarySignaturesMessage extends Message {
|
||||
return this.signatures;
|
||||
}
|
||||
|
||||
public int getRequestHops() {
|
||||
return this.requestHops;
|
||||
}
|
||||
|
||||
public void setRequestHops(int requestHops) {
|
||||
this.requestHops = requestHops;
|
||||
}
|
||||
|
||||
public static Message fromByteBuffer(int id, ByteBuffer bytes) throws UnsupportedEncodingException, TransformationException {
|
||||
String peerAddress = Serialization.deserializeSizedStringV2(bytes, PeerData.MAX_PEER_ADDRESS_SIZE);
|
||||
public static Message fromByteBuffer(int id, ByteBuffer bytes) throws MessageException {
|
||||
String peerAddress;
|
||||
try {
|
||||
peerAddress = Serialization.deserializeSizedStringV2(bytes, PeerData.MAX_PEER_ADDRESS_SIZE);
|
||||
} catch (TransformationException e) {
|
||||
throw new MessageException(e.getMessage(), e);
|
||||
}
|
||||
|
||||
int requestHops = bytes.getInt();
|
||||
|
||||
int signatureCount = bytes.getInt();
|
||||
|
||||
if (bytes.remaining() != signatureCount * SIGNATURE_LENGTH)
|
||||
return null;
|
||||
if (bytes.remaining() < signatureCount * Transformer.SIGNATURE_LENGTH)
|
||||
throw new BufferUnderflowException();
|
||||
|
||||
List<byte[]> signatures = new ArrayList<>();
|
||||
for (int i = 0; i < signatureCount; ++i) {
|
||||
byte[] signature = new byte[SIGNATURE_LENGTH];
|
||||
byte[] signature = new byte[Transformer.SIGNATURE_LENGTH];
|
||||
bytes.get(signature);
|
||||
signatures.add(signature);
|
||||
}
|
||||
@@ -69,24 +82,4 @@ public class ArbitrarySignaturesMessage extends Message {
|
||||
return new ArbitrarySignaturesMessage(id, peerAddress, requestHops, signatures);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected byte[] toData() {
|
||||
try {
|
||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
|
||||
|
||||
Serialization.serializeSizedStringV2(bytes, this.peerAddress);
|
||||
|
||||
bytes.write(Ints.toByteArray(this.requestHops));
|
||||
|
||||
bytes.write(Ints.toByteArray(this.signatures.size()));
|
||||
|
||||
for (byte[] signature : this.signatures)
|
||||
bytes.write(signature);
|
||||
|
||||
return bytes.toByteArray();
|
||||
} catch (IOException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -1,14 +1,10 @@
|
||||
package org.qortal.network.message;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.List;
|
||||
|
||||
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;
|
||||
@@ -16,27 +12,15 @@ import org.qortal.transform.TransformationException;
|
||||
import org.qortal.transform.block.BlockTransformer;
|
||||
import org.qortal.utils.Triple;
|
||||
|
||||
import com.google.common.primitives.Ints;
|
||||
|
||||
public class BlockMessage extends Message {
|
||||
|
||||
private static final Logger LOGGER = LogManager.getLogger(BlockMessage.class);
|
||||
|
||||
private Block block = null;
|
||||
private final BlockData blockData;
|
||||
private final List<TransactionData> transactions;
|
||||
private final List<ATStateData> atStates;
|
||||
|
||||
private BlockData blockData = null;
|
||||
private List<TransactionData> transactions = null;
|
||||
private List<ATStateData> atStates = null;
|
||||
|
||||
private int height;
|
||||
|
||||
public BlockMessage(Block block) {
|
||||
super(MessageType.BLOCK);
|
||||
|
||||
this.block = block;
|
||||
this.blockData = block.getBlockData();
|
||||
this.height = block.getBlockData().getHeight();
|
||||
}
|
||||
// No public constructor as we're an incoming-only message type.
|
||||
|
||||
private BlockMessage(int id, BlockData blockData, List<TransactionData> transactions, List<ATStateData> atStates) {
|
||||
super(id, MessageType.BLOCK);
|
||||
@@ -44,8 +28,6 @@ public class BlockMessage extends Message {
|
||||
this.blockData = blockData;
|
||||
this.transactions = transactions;
|
||||
this.atStates = atStates;
|
||||
|
||||
this.height = blockData.getHeight();
|
||||
}
|
||||
|
||||
public BlockData getBlockData() {
|
||||
@@ -60,7 +42,7 @@ public class BlockMessage extends Message {
|
||||
return this.atStates;
|
||||
}
|
||||
|
||||
public static Message fromByteBuffer(int id, ByteBuffer byteBuffer) throws UnsupportedEncodingException {
|
||||
public static Message fromByteBuffer(int id, ByteBuffer byteBuffer) throws MessageException {
|
||||
try {
|
||||
int height = byteBuffer.getInt();
|
||||
|
||||
@@ -72,32 +54,8 @@ public class BlockMessage extends Message {
|
||||
return new BlockMessage(id, blockData, blockInfo.getB(), blockInfo.getC());
|
||||
} catch (TransformationException e) {
|
||||
LOGGER.info(String.format("Received garbled BLOCK message: %s", e.getMessage()));
|
||||
return null;
|
||||
throw new MessageException(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected byte[] toData() {
|
||||
if (this.block == null)
|
||||
return null;
|
||||
|
||||
try {
|
||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
|
||||
|
||||
bytes.write(Ints.toByteArray(this.height));
|
||||
|
||||
bytes.write(BlockTransformer.toBytes(this.block));
|
||||
|
||||
return bytes.toByteArray();
|
||||
} catch (TransformationException | IOException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public BlockMessage cloneWithNewId(int newId) {
|
||||
BlockMessage clone = new BlockMessage(this.block);
|
||||
clone.setId(newId);
|
||||
return clone;
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -2,7 +2,7 @@ package org.qortal.network.message;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.nio.BufferUnderflowException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@@ -20,7 +20,25 @@ public class BlockSummariesMessage extends Message {
|
||||
private List<BlockSummaryData> blockSummaries;
|
||||
|
||||
public BlockSummariesMessage(List<BlockSummaryData> blockSummaries) {
|
||||
this(-1, blockSummaries);
|
||||
super(MessageType.BLOCK_SUMMARIES);
|
||||
|
||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
|
||||
|
||||
try {
|
||||
bytes.write(Ints.toByteArray(blockSummaries.size()));
|
||||
|
||||
for (BlockSummaryData blockSummary : blockSummaries) {
|
||||
bytes.write(Ints.toByteArray(blockSummary.getHeight()));
|
||||
bytes.write(blockSummary.getSignature());
|
||||
bytes.write(blockSummary.getMinterPublicKey());
|
||||
bytes.write(Ints.toByteArray(blockSummary.getOnlineAccountsCount()));
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new AssertionError("IOException shouldn't occur with ByteArrayOutputStream");
|
||||
}
|
||||
|
||||
this.dataBytes = bytes.toByteArray();
|
||||
this.checksumBytes = Message.generateChecksum(this.dataBytes);
|
||||
}
|
||||
|
||||
private BlockSummariesMessage(int id, List<BlockSummaryData> blockSummaries) {
|
||||
@@ -33,11 +51,11 @@ public class BlockSummariesMessage extends Message {
|
||||
return this.blockSummaries;
|
||||
}
|
||||
|
||||
public static Message fromByteBuffer(int id, ByteBuffer bytes) throws UnsupportedEncodingException {
|
||||
public static Message fromByteBuffer(int id, ByteBuffer bytes) {
|
||||
int count = bytes.getInt();
|
||||
|
||||
if (bytes.remaining() != count * BLOCK_SUMMARY_LENGTH)
|
||||
return null;
|
||||
if (bytes.remaining() < count * BLOCK_SUMMARY_LENGTH)
|
||||
throw new BufferUnderflowException();
|
||||
|
||||
List<BlockSummaryData> blockSummaries = new ArrayList<>();
|
||||
for (int i = 0; i < count; ++i) {
|
||||
@@ -58,24 +76,4 @@ public class BlockSummariesMessage extends Message {
|
||||
return new BlockSummariesMessage(id, blockSummaries);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected byte[] toData() {
|
||||
try {
|
||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
|
||||
|
||||
bytes.write(Ints.toByteArray(this.blockSummaries.size()));
|
||||
|
||||
for (BlockSummaryData blockSummary : this.blockSummaries) {
|
||||
bytes.write(Ints.toByteArray(blockSummary.getHeight()));
|
||||
bytes.write(blockSummary.getSignature());
|
||||
bytes.write(blockSummary.getMinterPublicKey());
|
||||
bytes.write(Ints.toByteArray(blockSummary.getOnlineAccountsCount()));
|
||||
}
|
||||
|
||||
return bytes.toByteArray();
|
||||
} catch (IOException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -2,7 +2,6 @@ package org.qortal.network.message;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.qortal.block.Block;
|
||||
@@ -12,59 +11,34 @@ import org.qortal.transform.block.BlockTransformer;
|
||||
import com.google.common.primitives.Ints;
|
||||
|
||||
// This is an OUTGOING-only Message which more readily lends itself to being cached
|
||||
public class CachedBlockMessage extends Message {
|
||||
public class CachedBlockMessage extends Message implements Cloneable {
|
||||
|
||||
private Block block = null;
|
||||
private byte[] cachedBytes = null;
|
||||
|
||||
public CachedBlockMessage(Block block) {
|
||||
public CachedBlockMessage(Block block) throws TransformationException {
|
||||
super(MessageType.BLOCK);
|
||||
|
||||
this.block = block;
|
||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
|
||||
|
||||
try {
|
||||
bytes.write(Ints.toByteArray(block.getBlockData().getHeight()));
|
||||
|
||||
bytes.write(BlockTransformer.toBytes(block));
|
||||
} catch (IOException e) {
|
||||
throw new AssertionError("IOException shouldn't occur with ByteArrayOutputStream");
|
||||
}
|
||||
|
||||
this.dataBytes = bytes.toByteArray();
|
||||
this.checksumBytes = Message.generateChecksum(this.dataBytes);
|
||||
}
|
||||
|
||||
public CachedBlockMessage(byte[] cachedBytes) {
|
||||
super(MessageType.BLOCK);
|
||||
|
||||
this.block = null;
|
||||
this.cachedBytes = cachedBytes;
|
||||
this.dataBytes = cachedBytes;
|
||||
this.checksumBytes = Message.generateChecksum(this.dataBytes);
|
||||
}
|
||||
|
||||
public static Message fromByteBuffer(int id, ByteBuffer byteBuffer) throws UnsupportedEncodingException {
|
||||
|
||||
public static Message fromByteBuffer(int id, ByteBuffer byteBuffer) {
|
||||
throw new UnsupportedOperationException("CachedBlockMessage is for outgoing messages only");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected byte[] toData() {
|
||||
// Already serialized?
|
||||
if (this.cachedBytes != null)
|
||||
return cachedBytes;
|
||||
|
||||
if (this.block == null)
|
||||
return null;
|
||||
|
||||
try {
|
||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
|
||||
|
||||
bytes.write(Ints.toByteArray(this.block.getBlockData().getHeight()));
|
||||
|
||||
bytes.write(BlockTransformer.toBytes(this.block));
|
||||
|
||||
this.cachedBytes = bytes.toByteArray();
|
||||
// We no longer need source Block
|
||||
// and Block contains repository handle which is highly likely to be invalid after this call
|
||||
this.block = null;
|
||||
|
||||
return this.cachedBytes;
|
||||
} catch (TransformationException | IOException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public CachedBlockMessage cloneWithNewId(int newId) {
|
||||
CachedBlockMessage clone = new CachedBlockMessage(this.cachedBytes);
|
||||
clone.setId(newId);
|
||||
return clone;
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -10,8 +10,25 @@ public class ChallengeMessage extends Message {
|
||||
|
||||
public static final int CHALLENGE_LENGTH = 32;
|
||||
|
||||
private final byte[] publicKey;
|
||||
private final byte[] challenge;
|
||||
private byte[] publicKey;
|
||||
private byte[] challenge;
|
||||
|
||||
public ChallengeMessage(byte[] publicKey, byte[] challenge) {
|
||||
super(MessageType.CHALLENGE);
|
||||
|
||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream(publicKey.length + challenge.length);
|
||||
|
||||
try {
|
||||
bytes.write(publicKey);
|
||||
|
||||
bytes.write(challenge);
|
||||
} catch (IOException e) {
|
||||
throw new AssertionError("IOException shouldn't occur with ByteArrayOutputStream");
|
||||
}
|
||||
|
||||
this.dataBytes = bytes.toByteArray();
|
||||
this.checksumBytes = Message.generateChecksum(this.dataBytes);
|
||||
}
|
||||
|
||||
private ChallengeMessage(int id, byte[] publicKey, byte[] challenge) {
|
||||
super(id, MessageType.CHALLENGE);
|
||||
@@ -20,10 +37,6 @@ public class ChallengeMessage extends Message {
|
||||
this.challenge = challenge;
|
||||
}
|
||||
|
||||
public ChallengeMessage(byte[] publicKey, byte[] challenge) {
|
||||
this(-1, publicKey, challenge);
|
||||
}
|
||||
|
||||
public byte[] getPublicKey() {
|
||||
return this.publicKey;
|
||||
}
|
||||
@@ -42,15 +55,4 @@ public class ChallengeMessage extends Message {
|
||||
return new ChallengeMessage(id, publicKey, challenge);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected byte[] toData() throws IOException {
|
||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
|
||||
|
||||
bytes.write(this.publicKey);
|
||||
|
||||
bytes.write(this.challenge);
|
||||
|
||||
return bytes.toByteArray();
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -5,33 +5,54 @@ import com.google.common.primitives.Longs;
|
||||
import org.qortal.data.network.PeerData;
|
||||
import org.qortal.transform.TransformationException;
|
||||
import org.qortal.transform.Transformer;
|
||||
import org.qortal.transform.transaction.TransactionTransformer;
|
||||
import org.qortal.utils.Serialization;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static org.qortal.transform.Transformer.INT_LENGTH;
|
||||
import static org.qortal.transform.Transformer.LONG_LENGTH;
|
||||
|
||||
public class GetArbitraryDataFileListMessage extends Message {
|
||||
|
||||
private static final int SIGNATURE_LENGTH = Transformer.SIGNATURE_LENGTH;
|
||||
private static final int HASH_LENGTH = TransactionTransformer.SHA256_LENGTH;
|
||||
private static final int MAX_PEER_ADDRESS_LENGTH = PeerData.MAX_PEER_ADDRESS_SIZE;
|
||||
|
||||
private final byte[] signature;
|
||||
private byte[] signature;
|
||||
private List<byte[]> hashes;
|
||||
private final long requestTime;
|
||||
private long requestTime;
|
||||
private int requestHops;
|
||||
private String requestingPeer;
|
||||
|
||||
public GetArbitraryDataFileListMessage(byte[] signature, List<byte[]> hashes, long requestTime, int requestHops, String requestingPeer) {
|
||||
this(-1, signature, hashes, requestTime, requestHops, requestingPeer);
|
||||
super(MessageType.GET_ARBITRARY_DATA_FILE_LIST);
|
||||
|
||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
|
||||
|
||||
try {
|
||||
bytes.write(signature);
|
||||
|
||||
bytes.write(Longs.toByteArray(requestTime));
|
||||
|
||||
bytes.write(Ints.toByteArray(requestHops));
|
||||
|
||||
if (hashes != null) {
|
||||
bytes.write(Ints.toByteArray(hashes.size()));
|
||||
|
||||
for (byte[] hash : hashes) {
|
||||
bytes.write(hash);
|
||||
}
|
||||
}
|
||||
else {
|
||||
bytes.write(Ints.toByteArray(0));
|
||||
}
|
||||
|
||||
if (requestingPeer != null) {
|
||||
Serialization.serializeSizedStringV2(bytes, requestingPeer);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new AssertionError("IOException shouldn't occur with ByteArrayOutputStream");
|
||||
}
|
||||
|
||||
this.dataBytes = bytes.toByteArray();
|
||||
this.checksumBytes = Message.generateChecksum(this.dataBytes);
|
||||
}
|
||||
|
||||
private GetArbitraryDataFileListMessage(int id, byte[] signature, List<byte[]> hashes, long requestTime, int requestHops, String requestingPeer) {
|
||||
@@ -52,8 +73,20 @@ public class GetArbitraryDataFileListMessage extends Message {
|
||||
return this.hashes;
|
||||
}
|
||||
|
||||
public static Message fromByteBuffer(int id, ByteBuffer bytes) throws UnsupportedEncodingException, TransformationException {
|
||||
byte[] signature = new byte[SIGNATURE_LENGTH];
|
||||
public long getRequestTime() {
|
||||
return this.requestTime;
|
||||
}
|
||||
|
||||
public int getRequestHops() {
|
||||
return this.requestHops;
|
||||
}
|
||||
|
||||
public String getRequestingPeer() {
|
||||
return this.requestingPeer;
|
||||
}
|
||||
|
||||
public static Message fromByteBuffer(int id, ByteBuffer bytes) throws MessageException {
|
||||
byte[] signature = new byte[Transformer.SIGNATURE_LENGTH];
|
||||
|
||||
bytes.get(signature);
|
||||
|
||||
@@ -67,7 +100,7 @@ public class GetArbitraryDataFileListMessage extends Message {
|
||||
|
||||
hashes = new ArrayList<>();
|
||||
for (int i = 0; i < hashCount; ++i) {
|
||||
byte[] hash = new byte[HASH_LENGTH];
|
||||
byte[] hash = new byte[Transformer.SHA256_LENGTH];
|
||||
bytes.get(hash);
|
||||
hashes.add(hash);
|
||||
}
|
||||
@@ -75,57 +108,14 @@ public class GetArbitraryDataFileListMessage extends Message {
|
||||
|
||||
String requestingPeer = null;
|
||||
if (bytes.hasRemaining()) {
|
||||
requestingPeer = Serialization.deserializeSizedStringV2(bytes, MAX_PEER_ADDRESS_LENGTH);
|
||||
try {
|
||||
requestingPeer = Serialization.deserializeSizedStringV2(bytes, PeerData.MAX_PEER_ADDRESS_SIZE);
|
||||
} catch (TransformationException e) {
|
||||
throw new MessageException(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
return new GetArbitraryDataFileListMessage(id, signature, hashes, requestTime, requestHops, requestingPeer);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected byte[] toData() {
|
||||
try {
|
||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
|
||||
|
||||
bytes.write(this.signature);
|
||||
|
||||
bytes.write(Longs.toByteArray(this.requestTime));
|
||||
|
||||
bytes.write(Ints.toByteArray(this.requestHops));
|
||||
|
||||
if (this.hashes != null) {
|
||||
bytes.write(Ints.toByteArray(this.hashes.size()));
|
||||
|
||||
for (byte[] hash : this.hashes) {
|
||||
bytes.write(hash);
|
||||
}
|
||||
}
|
||||
else {
|
||||
bytes.write(Ints.toByteArray(0));
|
||||
}
|
||||
|
||||
if (this.requestingPeer != null) {
|
||||
Serialization.serializeSizedStringV2(bytes, this.requestingPeer);
|
||||
}
|
||||
|
||||
return bytes.toByteArray();
|
||||
} catch (IOException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public long getRequestTime() {
|
||||
return this.requestTime;
|
||||
}
|
||||
|
||||
public int getRequestHops() {
|
||||
return this.requestHops;
|
||||
}
|
||||
public void setRequestHops(int requestHops) {
|
||||
this.requestHops = requestHops;
|
||||
}
|
||||
|
||||
public String getRequestingPeer() {
|
||||
return this.requestingPeer;
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -1,23 +1,31 @@
|
||||
package org.qortal.network.message;
|
||||
|
||||
import org.qortal.transform.Transformer;
|
||||
import org.qortal.transform.transaction.TransactionTransformer;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public class GetArbitraryDataFileMessage extends Message {
|
||||
|
||||
private static final int SIGNATURE_LENGTH = Transformer.SIGNATURE_LENGTH;
|
||||
private static final int HASH_LENGTH = TransactionTransformer.SHA256_LENGTH;
|
||||
|
||||
private final byte[] signature;
|
||||
private final byte[] hash;
|
||||
private byte[] signature;
|
||||
private byte[] hash;
|
||||
|
||||
public GetArbitraryDataFileMessage(byte[] signature, byte[] hash) {
|
||||
this(-1, signature, hash);
|
||||
super(MessageType.GET_ARBITRARY_DATA_FILE);
|
||||
|
||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream(signature.length + hash.length);
|
||||
|
||||
try {
|
||||
bytes.write(signature);
|
||||
|
||||
bytes.write(hash);
|
||||
} catch (IOException e) {
|
||||
throw new AssertionError("IOException shouldn't occur with ByteArrayOutputStream");
|
||||
}
|
||||
|
||||
this.dataBytes = bytes.toByteArray();
|
||||
this.checksumBytes = Message.generateChecksum(this.dataBytes);
|
||||
}
|
||||
|
||||
private GetArbitraryDataFileMessage(int id, byte[] signature, byte[] hash) {
|
||||
@@ -35,32 +43,14 @@ public class GetArbitraryDataFileMessage extends Message {
|
||||
return this.hash;
|
||||
}
|
||||
|
||||
public static Message fromByteBuffer(int id, ByteBuffer bytes) throws UnsupportedEncodingException {
|
||||
if (bytes.remaining() != HASH_LENGTH + SIGNATURE_LENGTH)
|
||||
return null;
|
||||
|
||||
byte[] signature = new byte[SIGNATURE_LENGTH];
|
||||
public static Message fromByteBuffer(int id, ByteBuffer bytes) {
|
||||
byte[] signature = new byte[Transformer.SIGNATURE_LENGTH];
|
||||
bytes.get(signature);
|
||||
|
||||
byte[] hash = new byte[HASH_LENGTH];
|
||||
byte[] hash = new byte[Transformer.SHA256_LENGTH];
|
||||
bytes.get(hash);
|
||||
|
||||
return new GetArbitraryDataFileMessage(id, signature, hash);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected byte[] toData() {
|
||||
try {
|
||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
|
||||
|
||||
bytes.write(this.signature);
|
||||
|
||||
bytes.write(this.hash);
|
||||
|
||||
return bytes.toByteArray();
|
||||
} catch (IOException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -1,20 +1,19 @@
|
||||
package org.qortal.network.message;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.qortal.transform.Transformer;
|
||||
|
||||
public class GetArbitraryDataMessage extends Message {
|
||||
|
||||
private static final int SIGNATURE_LENGTH = Transformer.SIGNATURE_LENGTH;
|
||||
|
||||
private byte[] signature;
|
||||
|
||||
public GetArbitraryDataMessage(byte[] signature) {
|
||||
this(-1, signature);
|
||||
super(MessageType.GET_ARBITRARY_DATA);
|
||||
|
||||
this.dataBytes = Arrays.copyOf(signature, signature.length);
|
||||
this.checksumBytes = Message.generateChecksum(this.dataBytes);
|
||||
}
|
||||
|
||||
private GetArbitraryDataMessage(int id, byte[] signature) {
|
||||
@@ -27,28 +26,12 @@ public class GetArbitraryDataMessage extends Message {
|
||||
return this.signature;
|
||||
}
|
||||
|
||||
public static Message fromByteBuffer(int id, ByteBuffer bytes) throws UnsupportedEncodingException {
|
||||
if (bytes.remaining() != SIGNATURE_LENGTH)
|
||||
return null;
|
||||
|
||||
byte[] signature = new byte[SIGNATURE_LENGTH];
|
||||
public static Message fromByteBuffer(int id, ByteBuffer bytes) {
|
||||
byte[] signature = new byte[Transformer.SIGNATURE_LENGTH];
|
||||
|
||||
bytes.get(signature);
|
||||
|
||||
return new GetArbitraryDataMessage(id, signature);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected byte[] toData() {
|
||||
try {
|
||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
|
||||
|
||||
bytes.write(this.signature);
|
||||
|
||||
return bytes.toByteArray();
|
||||
} catch (IOException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -6,22 +6,31 @@ import org.qortal.transform.Transformer;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import static org.qortal.transform.Transformer.INT_LENGTH;
|
||||
import static org.qortal.transform.Transformer.LONG_LENGTH;
|
||||
|
||||
public class GetArbitraryMetadataMessage extends Message {
|
||||
|
||||
private static final int SIGNATURE_LENGTH = Transformer.SIGNATURE_LENGTH;
|
||||
|
||||
private final byte[] signature;
|
||||
private final long requestTime;
|
||||
private byte[] signature;
|
||||
private long requestTime;
|
||||
private int requestHops;
|
||||
|
||||
public GetArbitraryMetadataMessage(byte[] signature, long requestTime, int requestHops) {
|
||||
this(-1, signature, requestTime, requestHops);
|
||||
super(MessageType.GET_ARBITRARY_METADATA);
|
||||
|
||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
|
||||
|
||||
try {
|
||||
bytes.write(signature);
|
||||
|
||||
bytes.write(Longs.toByteArray(requestTime));
|
||||
|
||||
bytes.write(Ints.toByteArray(requestHops));
|
||||
} catch (IOException e) {
|
||||
throw new AssertionError("IOException shouldn't occur with ByteArrayOutputStream");
|
||||
}
|
||||
|
||||
this.dataBytes = bytes.toByteArray();
|
||||
this.checksumBytes = Message.generateChecksum(this.dataBytes);
|
||||
}
|
||||
|
||||
private GetArbitraryMetadataMessage(int id, byte[] signature, long requestTime, int requestHops) {
|
||||
@@ -36,12 +45,16 @@ public class GetArbitraryMetadataMessage extends Message {
|
||||
return this.signature;
|
||||
}
|
||||
|
||||
public static Message fromByteBuffer(int id, ByteBuffer bytes) throws UnsupportedEncodingException {
|
||||
if (bytes.remaining() != SIGNATURE_LENGTH + LONG_LENGTH + INT_LENGTH)
|
||||
return null;
|
||||
public long getRequestTime() {
|
||||
return this.requestTime;
|
||||
}
|
||||
|
||||
byte[] signature = new byte[SIGNATURE_LENGTH];
|
||||
public int getRequestHops() {
|
||||
return this.requestHops;
|
||||
}
|
||||
|
||||
public static Message fromByteBuffer(int id, ByteBuffer bytes) {
|
||||
byte[] signature = new byte[Transformer.SIGNATURE_LENGTH];
|
||||
bytes.get(signature);
|
||||
|
||||
long requestTime = bytes.getLong();
|
||||
@@ -51,33 +64,4 @@ public class GetArbitraryMetadataMessage extends Message {
|
||||
return new GetArbitraryMetadataMessage(id, signature, requestTime, requestHops);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected byte[] toData() {
|
||||
try {
|
||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
|
||||
|
||||
bytes.write(this.signature);
|
||||
|
||||
bytes.write(Longs.toByteArray(this.requestTime));
|
||||
|
||||
bytes.write(Ints.toByteArray(this.requestHops));
|
||||
|
||||
return bytes.toByteArray();
|
||||
} catch (IOException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public long getRequestTime() {
|
||||
return this.requestTime;
|
||||
}
|
||||
|
||||
public int getRequestHops() {
|
||||
return this.requestHops;
|
||||
}
|
||||
|
||||
public void setRequestHops(int requestHops) {
|
||||
this.requestHops = requestHops;
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -1,20 +1,19 @@
|
||||
package org.qortal.network.message;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.qortal.transform.block.BlockTransformer;
|
||||
|
||||
public class GetBlockMessage extends Message {
|
||||
|
||||
private static final int BLOCK_SIGNATURE_LENGTH = BlockTransformer.BLOCK_SIGNATURE_LENGTH;
|
||||
|
||||
private byte[] signature;
|
||||
|
||||
public GetBlockMessage(byte[] signature) {
|
||||
this(-1, signature);
|
||||
super(MessageType.GET_BLOCK);
|
||||
|
||||
this.dataBytes = Arrays.copyOf(signature, signature.length);
|
||||
this.checksumBytes = Message.generateChecksum(this.dataBytes);
|
||||
}
|
||||
|
||||
private GetBlockMessage(int id, byte[] signature) {
|
||||
@@ -27,28 +26,11 @@ public class GetBlockMessage extends Message {
|
||||
return this.signature;
|
||||
}
|
||||
|
||||
public static Message fromByteBuffer(int id, ByteBuffer bytes) throws UnsupportedEncodingException {
|
||||
if (bytes.remaining() != BLOCK_SIGNATURE_LENGTH)
|
||||
return null;
|
||||
|
||||
byte[] signature = new byte[BLOCK_SIGNATURE_LENGTH];
|
||||
|
||||
public static Message fromByteBuffer(int id, ByteBuffer bytes) {
|
||||
byte[] signature = new byte[BlockTransformer.BLOCK_SIGNATURE_LENGTH];
|
||||
bytes.get(signature);
|
||||
|
||||
return new GetBlockMessage(id, signature);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected byte[] toData() {
|
||||
try {
|
||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
|
||||
|
||||
bytes.write(this.signature);
|
||||
|
||||
return bytes.toByteArray();
|
||||
} catch (IOException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -2,23 +2,32 @@ package org.qortal.network.message;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.qortal.transform.Transformer;
|
||||
import org.qortal.transform.block.BlockTransformer;
|
||||
|
||||
import com.google.common.primitives.Ints;
|
||||
|
||||
public class GetBlockSummariesMessage extends Message {
|
||||
|
||||
private static final int BLOCK_SIGNATURE_LENGTH = BlockTransformer.BLOCK_SIGNATURE_LENGTH;
|
||||
|
||||
private byte[] parentSignature;
|
||||
private int numberRequested;
|
||||
|
||||
public GetBlockSummariesMessage(byte[] parentSignature, int numberRequested) {
|
||||
this(-1, parentSignature, numberRequested);
|
||||
super(MessageType.GET_BLOCK_SUMMARIES);
|
||||
|
||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
|
||||
|
||||
try {
|
||||
bytes.write(parentSignature);
|
||||
|
||||
bytes.write(Ints.toByteArray(numberRequested));
|
||||
} catch (IOException e) {
|
||||
throw new AssertionError("IOException shouldn't occur with ByteArrayOutputStream");
|
||||
}
|
||||
|
||||
this.dataBytes = bytes.toByteArray();
|
||||
this.checksumBytes = Message.generateChecksum(this.dataBytes);
|
||||
}
|
||||
|
||||
private GetBlockSummariesMessage(int id, byte[] parentSignature, int numberRequested) {
|
||||
@@ -36,11 +45,8 @@ public class GetBlockSummariesMessage extends Message {
|
||||
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];
|
||||
public static Message fromByteBuffer(int id, ByteBuffer bytes) {
|
||||
byte[] parentSignature = new byte[BlockTransformer.BLOCK_SIGNATURE_LENGTH];
|
||||
bytes.get(parentSignature);
|
||||
|
||||
int numberRequested = bytes.getInt();
|
||||
@@ -48,19 +54,4 @@ public class GetBlockSummariesMessage extends Message {
|
||||
return new GetBlockSummariesMessage(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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -2,7 +2,6 @@ package org.qortal.network.message;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@@ -20,7 +19,24 @@ public class GetOnlineAccountsMessage extends Message {
|
||||
private List<OnlineAccountData> onlineAccounts;
|
||||
|
||||
public GetOnlineAccountsMessage(List<OnlineAccountData> onlineAccounts) {
|
||||
this(-1, onlineAccounts);
|
||||
super(MessageType.GET_ONLINE_ACCOUNTS);
|
||||
|
||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
|
||||
|
||||
try {
|
||||
bytes.write(Ints.toByteArray(onlineAccounts.size()));
|
||||
|
||||
for (OnlineAccountData onlineAccountData : onlineAccounts) {
|
||||
bytes.write(Longs.toByteArray(onlineAccountData.getTimestamp()));
|
||||
|
||||
bytes.write(onlineAccountData.getPublicKey());
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new AssertionError("IOException shouldn't occur with ByteArrayOutputStream");
|
||||
}
|
||||
|
||||
this.dataBytes = bytes.toByteArray();
|
||||
this.checksumBytes = Message.generateChecksum(this.dataBytes);
|
||||
}
|
||||
|
||||
private GetOnlineAccountsMessage(int id, List<OnlineAccountData> onlineAccounts) {
|
||||
@@ -33,7 +49,7 @@ public class GetOnlineAccountsMessage extends Message {
|
||||
return this.onlineAccounts;
|
||||
}
|
||||
|
||||
public static Message fromByteBuffer(int id, ByteBuffer bytes) throws UnsupportedEncodingException {
|
||||
public static Message fromByteBuffer(int id, ByteBuffer bytes) {
|
||||
final int accountCount = bytes.getInt();
|
||||
|
||||
List<OnlineAccountData> onlineAccounts = new ArrayList<>(accountCount);
|
||||
@@ -50,24 +66,4 @@ public class GetOnlineAccountsMessage extends Message {
|
||||
return new GetOnlineAccountsMessage(id, onlineAccounts);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected byte[] toData() {
|
||||
try {
|
||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
|
||||
|
||||
bytes.write(Ints.toByteArray(this.onlineAccounts.size()));
|
||||
|
||||
for (int i = 0; i < this.onlineAccounts.size(); ++i) {
|
||||
OnlineAccountData onlineAccountData = this.onlineAccounts.get(i);
|
||||
bytes.write(Longs.toByteArray(onlineAccountData.getTimestamp()));
|
||||
|
||||
bytes.write(onlineAccountData.getPublicKey());
|
||||
}
|
||||
|
||||
return bytes.toByteArray();
|
||||
} catch (IOException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -7,7 +7,6 @@ import org.qortal.transform.Transformer;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
@@ -24,11 +23,51 @@ import java.util.Map;
|
||||
* Also V2 only builds online accounts message once!
|
||||
*/
|
||||
public class GetOnlineAccountsV2Message extends Message {
|
||||
|
||||
private List<OnlineAccountData> onlineAccounts;
|
||||
private byte[] cachedData;
|
||||
|
||||
public GetOnlineAccountsV2Message(List<OnlineAccountData> onlineAccounts) {
|
||||
this(-1, onlineAccounts);
|
||||
super(MessageType.GET_ONLINE_ACCOUNTS_V2);
|
||||
|
||||
// If we don't have ANY online accounts then it's an easier construction...
|
||||
if (onlineAccounts.isEmpty()) {
|
||||
// Always supply a number of accounts
|
||||
this.dataBytes = Ints.toByteArray(0);
|
||||
this.checksumBytes = Message.generateChecksum(this.dataBytes);
|
||||
return;
|
||||
}
|
||||
|
||||
// How many of each timestamp
|
||||
Map<Long, Integer> countByTimestamp = new HashMap<>();
|
||||
|
||||
for (OnlineAccountData onlineAccountData : onlineAccounts) {
|
||||
Long timestamp = onlineAccountData.getTimestamp();
|
||||
countByTimestamp.compute(timestamp, (k, v) -> v == null ? 1 : ++v);
|
||||
}
|
||||
|
||||
// We should know exactly how many bytes to allocate now
|
||||
int byteSize = countByTimestamp.size() * (Transformer.INT_LENGTH + Transformer.TIMESTAMP_LENGTH)
|
||||
+ onlineAccounts.size() * Transformer.PUBLIC_KEY_LENGTH;
|
||||
|
||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream(byteSize);
|
||||
|
||||
try {
|
||||
for (long timestamp : countByTimestamp.keySet()) {
|
||||
bytes.write(Ints.toByteArray(countByTimestamp.get(timestamp)));
|
||||
|
||||
bytes.write(Longs.toByteArray(timestamp));
|
||||
|
||||
for (OnlineAccountData onlineAccountData : onlineAccounts) {
|
||||
if (onlineAccountData.getTimestamp() == timestamp)
|
||||
bytes.write(onlineAccountData.getPublicKey());
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new AssertionError("IOException shouldn't occur with ByteArrayOutputStream");
|
||||
}
|
||||
|
||||
this.dataBytes = bytes.toByteArray();
|
||||
this.checksumBytes = Message.generateChecksum(this.dataBytes);
|
||||
}
|
||||
|
||||
private GetOnlineAccountsV2Message(int id, List<OnlineAccountData> onlineAccounts) {
|
||||
@@ -41,7 +80,7 @@ public class GetOnlineAccountsV2Message extends Message {
|
||||
return this.onlineAccounts;
|
||||
}
|
||||
|
||||
public static Message fromByteBuffer(int id, ByteBuffer bytes) throws UnsupportedEncodingException {
|
||||
public static Message fromByteBuffer(int id, ByteBuffer bytes) {
|
||||
int accountCount = bytes.getInt();
|
||||
|
||||
List<OnlineAccountData> onlineAccounts = new ArrayList<>(accountCount);
|
||||
@@ -67,51 +106,4 @@ public class GetOnlineAccountsV2Message extends Message {
|
||||
return new GetOnlineAccountsV2Message(id, onlineAccounts);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected synchronized byte[] toData() {
|
||||
if (this.cachedData != null)
|
||||
return this.cachedData;
|
||||
|
||||
// Shortcut in case we have no online accounts
|
||||
if (this.onlineAccounts.isEmpty()) {
|
||||
this.cachedData = Ints.toByteArray(0);
|
||||
return this.cachedData;
|
||||
}
|
||||
|
||||
// How many of each timestamp
|
||||
Map<Long, Integer> countByTimestamp = new HashMap<>();
|
||||
|
||||
for (int i = 0; i < this.onlineAccounts.size(); ++i) {
|
||||
OnlineAccountData onlineAccountData = this.onlineAccounts.get(i);
|
||||
Long timestamp = onlineAccountData.getTimestamp();
|
||||
countByTimestamp.compute(timestamp, (k, v) -> v == null ? 1 : ++v);
|
||||
}
|
||||
|
||||
// We should know exactly how many bytes to allocate now
|
||||
int byteSize = countByTimestamp.size() * (Transformer.INT_LENGTH + Transformer.TIMESTAMP_LENGTH)
|
||||
+ this.onlineAccounts.size() * Transformer.PUBLIC_KEY_LENGTH;
|
||||
|
||||
try {
|
||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream(byteSize);
|
||||
|
||||
for (long timestamp : countByTimestamp.keySet()) {
|
||||
bytes.write(Ints.toByteArray(countByTimestamp.get(timestamp)));
|
||||
|
||||
bytes.write(Longs.toByteArray(timestamp));
|
||||
|
||||
for (int i = 0; i < this.onlineAccounts.size(); ++i) {
|
||||
OnlineAccountData onlineAccountData = this.onlineAccounts.get(i);
|
||||
|
||||
if (onlineAccountData.getTimestamp() == timestamp)
|
||||
bytes.write(onlineAccountData.getPublicKey());
|
||||
}
|
||||
}
|
||||
|
||||
this.cachedData = bytes.toByteArray();
|
||||
return this.cachedData;
|
||||
} catch (IOException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -1,25 +1,21 @@
|
||||
package org.qortal.network.message;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public class GetPeersMessage extends Message {
|
||||
|
||||
public GetPeersMessage() {
|
||||
this(-1);
|
||||
super(MessageType.GET_PEERS);
|
||||
|
||||
this.dataBytes = EMPTY_DATA_BYTES;
|
||||
}
|
||||
|
||||
private GetPeersMessage(int id) {
|
||||
super(id, MessageType.GET_PEERS);
|
||||
}
|
||||
|
||||
public static Message fromByteBuffer(int id, ByteBuffer bytes) throws UnsupportedEncodingException {
|
||||
public static Message fromByteBuffer(int id, ByteBuffer bytes) {
|
||||
return new GetPeersMessage(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected byte[] toData() {
|
||||
return new byte[0];
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -2,24 +2,32 @@ package org.qortal.network.message;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.qortal.transform.Transformer;
|
||||
import org.qortal.transform.block.BlockTransformer;
|
||||
|
||||
import com.google.common.primitives.Ints;
|
||||
|
||||
public class GetSignaturesV2Message extends Message {
|
||||
|
||||
private static final int BLOCK_SIGNATURE_LENGTH = BlockTransformer.BLOCK_SIGNATURE_LENGTH;
|
||||
private static final int NUMBER_REQUESTED_LENGTH = Transformer.INT_LENGTH;
|
||||
|
||||
private byte[] parentSignature;
|
||||
private int numberRequested;
|
||||
|
||||
public GetSignaturesV2Message(byte[] parentSignature, int numberRequested) {
|
||||
this(-1, parentSignature, numberRequested);
|
||||
super(MessageType.GET_SIGNATURES_V2);
|
||||
|
||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
|
||||
|
||||
try {
|
||||
bytes.write(parentSignature);
|
||||
|
||||
bytes.write(Ints.toByteArray(numberRequested));
|
||||
} catch (IOException e) {
|
||||
throw new AssertionError("IOException shouldn't occur with ByteArrayOutputStream");
|
||||
}
|
||||
|
||||
this.dataBytes = bytes.toByteArray();
|
||||
this.checksumBytes = Message.generateChecksum(this.dataBytes);
|
||||
}
|
||||
|
||||
private GetSignaturesV2Message(int id, byte[] parentSignature, int numberRequested) {
|
||||
@@ -37,11 +45,8 @@ public class GetSignaturesV2Message extends Message {
|
||||
return this.numberRequested;
|
||||
}
|
||||
|
||||
public static Message fromByteBuffer(int id, ByteBuffer bytes) throws UnsupportedEncodingException {
|
||||
if (bytes.remaining() != BLOCK_SIGNATURE_LENGTH + NUMBER_REQUESTED_LENGTH)
|
||||
return null;
|
||||
|
||||
byte[] parentSignature = new byte[BLOCK_SIGNATURE_LENGTH];
|
||||
public static Message fromByteBuffer(int id, ByteBuffer bytes) {
|
||||
byte[] parentSignature = new byte[BlockTransformer.BLOCK_SIGNATURE_LENGTH];
|
||||
bytes.get(parentSignature);
|
||||
|
||||
int numberRequested = bytes.getInt();
|
||||
@@ -49,19 +54,4 @@ public class GetSignaturesV2Message extends Message {
|
||||
return new GetSignaturesV2Message(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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -7,7 +7,6 @@ import org.qortal.transform.Transformer;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
@@ -21,10 +20,48 @@ import java.util.Map;
|
||||
*/
|
||||
public class GetTradePresencesMessage extends Message {
|
||||
private List<TradePresenceData> tradePresences;
|
||||
private byte[] cachedData;
|
||||
|
||||
public GetTradePresencesMessage(List<TradePresenceData> tradePresences) {
|
||||
this(-1, tradePresences);
|
||||
super(MessageType.GET_TRADE_PRESENCES);
|
||||
|
||||
// Shortcut in case we have no trade presences
|
||||
if (tradePresences.isEmpty()) {
|
||||
this.dataBytes = Ints.toByteArray(0);
|
||||
this.checksumBytes = Message.generateChecksum(this.dataBytes);
|
||||
return;
|
||||
}
|
||||
|
||||
// How many of each timestamp
|
||||
Map<Long, Integer> countByTimestamp = new HashMap<>();
|
||||
|
||||
for (TradePresenceData tradePresenceData : tradePresences) {
|
||||
Long timestamp = tradePresenceData.getTimestamp();
|
||||
countByTimestamp.compute(timestamp, (k, v) -> v == null ? 1 : ++v);
|
||||
}
|
||||
|
||||
// We should know exactly how many bytes to allocate now
|
||||
int byteSize = countByTimestamp.size() * (Transformer.INT_LENGTH + Transformer.TIMESTAMP_LENGTH)
|
||||
+ tradePresences.size() * Transformer.PUBLIC_KEY_LENGTH;
|
||||
|
||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream(byteSize);
|
||||
|
||||
try {
|
||||
for (long timestamp : countByTimestamp.keySet()) {
|
||||
bytes.write(Ints.toByteArray(countByTimestamp.get(timestamp)));
|
||||
|
||||
bytes.write(Longs.toByteArray(timestamp));
|
||||
|
||||
for (TradePresenceData tradePresenceData : tradePresences) {
|
||||
if (tradePresenceData.getTimestamp() == timestamp)
|
||||
bytes.write(tradePresenceData.getPublicKey());
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new AssertionError("IOException shouldn't occur with ByteArrayOutputStream");
|
||||
}
|
||||
|
||||
this.dataBytes = bytes.toByteArray();
|
||||
this.checksumBytes = Message.generateChecksum(this.dataBytes);
|
||||
}
|
||||
|
||||
private GetTradePresencesMessage(int id, List<TradePresenceData> tradePresences) {
|
||||
@@ -37,7 +74,7 @@ public class GetTradePresencesMessage extends Message {
|
||||
return this.tradePresences;
|
||||
}
|
||||
|
||||
public static Message fromByteBuffer(int id, ByteBuffer bytes) throws UnsupportedEncodingException {
|
||||
public static Message fromByteBuffer(int id, ByteBuffer bytes) {
|
||||
int groupedEntriesCount = bytes.getInt();
|
||||
|
||||
List<TradePresenceData> tradePresences = new ArrayList<>(groupedEntriesCount);
|
||||
@@ -63,48 +100,4 @@ public class GetTradePresencesMessage extends Message {
|
||||
return new GetTradePresencesMessage(id, tradePresences);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected synchronized byte[] toData() {
|
||||
if (this.cachedData != null)
|
||||
return this.cachedData;
|
||||
|
||||
// Shortcut in case we have no trade presences
|
||||
if (this.tradePresences.isEmpty()) {
|
||||
this.cachedData = Ints.toByteArray(0);
|
||||
return this.cachedData;
|
||||
}
|
||||
|
||||
// How many of each timestamp
|
||||
Map<Long, Integer> countByTimestamp = new HashMap<>();
|
||||
|
||||
for (TradePresenceData tradePresenceData : this.tradePresences) {
|
||||
Long timestamp = tradePresenceData.getTimestamp();
|
||||
countByTimestamp.compute(timestamp, (k, v) -> v == null ? 1 : ++v);
|
||||
}
|
||||
|
||||
// We should know exactly how many bytes to allocate now
|
||||
int byteSize = countByTimestamp.size() * (Transformer.INT_LENGTH + Transformer.TIMESTAMP_LENGTH)
|
||||
+ this.tradePresences.size() * Transformer.PUBLIC_KEY_LENGTH;
|
||||
|
||||
try {
|
||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream(byteSize);
|
||||
|
||||
for (long timestamp : countByTimestamp.keySet()) {
|
||||
bytes.write(Ints.toByteArray(countByTimestamp.get(timestamp)));
|
||||
|
||||
bytes.write(Longs.toByteArray(timestamp));
|
||||
|
||||
for (TradePresenceData tradePresenceData : this.tradePresences) {
|
||||
if (tradePresenceData.getTimestamp() == timestamp)
|
||||
bytes.write(tradePresenceData.getPublicKey());
|
||||
}
|
||||
}
|
||||
|
||||
this.cachedData = bytes.toByteArray();
|
||||
return this.cachedData;
|
||||
} catch (IOException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -1,20 +1,19 @@
|
||||
package org.qortal.network.message;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.qortal.transform.Transformer;
|
||||
|
||||
public class GetTransactionMessage extends Message {
|
||||
|
||||
private static final int TRANSACTION_SIGNATURE_LENGTH = Transformer.SIGNATURE_LENGTH;
|
||||
|
||||
private byte[] signature;
|
||||
|
||||
public GetTransactionMessage(byte[] signature) {
|
||||
this(-1, signature);
|
||||
super(MessageType.GET_TRANSACTION);
|
||||
|
||||
this.dataBytes = Arrays.copyOf(signature, signature.length);
|
||||
this.checksumBytes = Message.generateChecksum(this.dataBytes);
|
||||
}
|
||||
|
||||
private GetTransactionMessage(int id, byte[] signature) {
|
||||
@@ -27,28 +26,12 @@ public class GetTransactionMessage extends Message {
|
||||
return this.signature;
|
||||
}
|
||||
|
||||
public static Message fromByteBuffer(int id, ByteBuffer bytes) throws UnsupportedEncodingException {
|
||||
if (bytes.remaining() != TRANSACTION_SIGNATURE_LENGTH)
|
||||
return null;
|
||||
|
||||
byte[] signature = new byte[TRANSACTION_SIGNATURE_LENGTH];
|
||||
public static Message fromByteBuffer(int id, ByteBuffer bytes) {
|
||||
byte[] signature = new byte[Transformer.SIGNATURE_LENGTH];
|
||||
|
||||
bytes.get(signature);
|
||||
|
||||
return new GetTransactionMessage(id, signature);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected byte[] toData() {
|
||||
try {
|
||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
|
||||
|
||||
bytes.write(this.signature);
|
||||
|
||||
return bytes.toByteArray();
|
||||
} catch (IOException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -1,25 +1,21 @@
|
||||
package org.qortal.network.message;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public class GetUnconfirmedTransactionsMessage extends Message {
|
||||
|
||||
public GetUnconfirmedTransactionsMessage() {
|
||||
this(-1);
|
||||
super(MessageType.GET_UNCONFIRMED_TRANSACTIONS);
|
||||
|
||||
this.dataBytes = EMPTY_DATA_BYTES;
|
||||
}
|
||||
|
||||
private GetUnconfirmedTransactionsMessage(int id) {
|
||||
super(id, MessageType.GET_UNCONFIRMED_TRANSACTIONS);
|
||||
}
|
||||
|
||||
public static Message fromByteBuffer(int id, ByteBuffer bytes) throws UnsupportedEncodingException {
|
||||
public static Message fromByteBuffer(int id, ByteBuffer bytes) {
|
||||
return new GetUnconfirmedTransactionsMessage(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected byte[] toData() {
|
||||
return new byte[0];
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -3,7 +3,6 @@ package org.qortal.network.message;
|
||||
import static java.util.Arrays.stream;
|
||||
import static java.util.stream.Collectors.toMap;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Map;
|
||||
|
||||
@@ -22,7 +21,7 @@ public class GoodbyeMessage extends Message {
|
||||
private static final Map<Integer, Reason> map = stream(Reason.values())
|
||||
.collect(toMap(reason -> reason.value, reason -> reason));
|
||||
|
||||
private Reason(int value) {
|
||||
Reason(int value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@@ -31,7 +30,14 @@ public class GoodbyeMessage extends Message {
|
||||
}
|
||||
}
|
||||
|
||||
private final Reason reason;
|
||||
private Reason reason;
|
||||
|
||||
public GoodbyeMessage(Reason reason) {
|
||||
super(MessageType.GOODBYE);
|
||||
|
||||
this.dataBytes = Ints.toByteArray(reason.value);
|
||||
this.checksumBytes = Message.generateChecksum(this.dataBytes);
|
||||
}
|
||||
|
||||
private GoodbyeMessage(int id, Reason reason) {
|
||||
super(id, MessageType.GOODBYE);
|
||||
@@ -39,27 +45,18 @@ public class GoodbyeMessage extends Message {
|
||||
this.reason = reason;
|
||||
}
|
||||
|
||||
public GoodbyeMessage(Reason reason) {
|
||||
this(-1, reason);
|
||||
}
|
||||
|
||||
public Reason getReason() {
|
||||
return this.reason;
|
||||
}
|
||||
|
||||
public static Message fromByteBuffer(int id, ByteBuffer byteBuffer) {
|
||||
public static Message fromByteBuffer(int id, ByteBuffer byteBuffer) throws MessageException {
|
||||
int reasonValue = byteBuffer.getInt();
|
||||
|
||||
Reason reason = Reason.valueOf(reasonValue);
|
||||
if (reason == null)
|
||||
return null;
|
||||
throw new MessageException("Invalid reason " + reasonValue + " in GOODBYE message");
|
||||
|
||||
return new GoodbyeMessage(id, reason);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected byte[] toData() throws IOException {
|
||||
return Ints.toByteArray(this.reason.value);
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -2,7 +2,6 @@ package org.qortal.network.message;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.qortal.transform.Transformer;
|
||||
@@ -19,7 +18,24 @@ public class HeightV2Message extends Message {
|
||||
private byte[] minterPublicKey;
|
||||
|
||||
public HeightV2Message(int height, byte[] signature, long timestamp, byte[] minterPublicKey) {
|
||||
this(-1, height, signature, timestamp, minterPublicKey);
|
||||
super(MessageType.HEIGHT_V2);
|
||||
|
||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
|
||||
|
||||
try {
|
||||
bytes.write(Ints.toByteArray(height));
|
||||
|
||||
bytes.write(signature);
|
||||
|
||||
bytes.write(Longs.toByteArray(timestamp));
|
||||
|
||||
bytes.write(minterPublicKey);
|
||||
} catch (IOException e) {
|
||||
throw new AssertionError("IOException shouldn't occur with ByteArrayOutputStream");
|
||||
}
|
||||
|
||||
this.dataBytes = bytes.toByteArray();
|
||||
this.checksumBytes = Message.generateChecksum(this.dataBytes);
|
||||
}
|
||||
|
||||
private HeightV2Message(int id, int height, byte[] signature, long timestamp, byte[] minterPublicKey) {
|
||||
@@ -47,7 +63,7 @@ public class HeightV2Message extends Message {
|
||||
return this.minterPublicKey;
|
||||
}
|
||||
|
||||
public static Message fromByteBuffer(int id, ByteBuffer bytes) throws UnsupportedEncodingException {
|
||||
public static Message fromByteBuffer(int id, ByteBuffer bytes) {
|
||||
int height = bytes.getInt();
|
||||
|
||||
byte[] signature = new byte[BlockTransformer.BLOCK_SIGNATURE_LENGTH];
|
||||
@@ -61,23 +77,4 @@ public class HeightV2Message extends Message {
|
||||
return new HeightV2Message(id, height, signature, timestamp, minterPublicKey);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected byte[] toData() {
|
||||
try {
|
||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
|
||||
|
||||
bytes.write(Ints.toByteArray(this.height));
|
||||
|
||||
bytes.write(this.signature);
|
||||
|
||||
bytes.write(Longs.toByteArray(this.timestamp));
|
||||
|
||||
bytes.write(this.minterPublicKey);
|
||||
|
||||
return bytes.toByteArray();
|
||||
} catch (IOException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -11,9 +11,28 @@ import com.google.common.primitives.Longs;
|
||||
|
||||
public class HelloMessage extends Message {
|
||||
|
||||
private final long timestamp;
|
||||
private final String versionString;
|
||||
private final String senderPeerAddress;
|
||||
private long timestamp;
|
||||
private String versionString;
|
||||
private String senderPeerAddress;
|
||||
|
||||
public HelloMessage(long timestamp, String versionString, String senderPeerAddress) {
|
||||
super(MessageType.HELLO);
|
||||
|
||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
|
||||
|
||||
try {
|
||||
bytes.write(Longs.toByteArray(timestamp));
|
||||
|
||||
Serialization.serializeSizedString(bytes, versionString);
|
||||
|
||||
Serialization.serializeSizedString(bytes, senderPeerAddress);
|
||||
} catch (IOException e) {
|
||||
throw new AssertionError("IOException shouldn't occur with ByteArrayOutputStream");
|
||||
}
|
||||
|
||||
this.dataBytes = bytes.toByteArray();
|
||||
this.checksumBytes = Message.generateChecksum(this.dataBytes);
|
||||
}
|
||||
|
||||
private HelloMessage(int id, long timestamp, String versionString, String senderPeerAddress) {
|
||||
super(id, MessageType.HELLO);
|
||||
@@ -23,10 +42,6 @@ public class HelloMessage extends Message {
|
||||
this.senderPeerAddress = senderPeerAddress;
|
||||
}
|
||||
|
||||
public HelloMessage(long timestamp, String versionString, String senderPeerAddress) {
|
||||
this(-1, timestamp, versionString, senderPeerAddress);
|
||||
}
|
||||
|
||||
public long getTimestamp() {
|
||||
return this.timestamp;
|
||||
}
|
||||
@@ -39,31 +54,23 @@ public class HelloMessage extends Message {
|
||||
return this.senderPeerAddress;
|
||||
}
|
||||
|
||||
public static Message fromByteBuffer(int id, ByteBuffer byteBuffer) throws TransformationException {
|
||||
public static Message fromByteBuffer(int id, ByteBuffer byteBuffer) throws MessageException {
|
||||
long timestamp = byteBuffer.getLong();
|
||||
|
||||
String versionString = Serialization.deserializeSizedString(byteBuffer, 255);
|
||||
|
||||
// Sender peer address added in v3.0, so is an optional field. Older versions won't send it.
|
||||
String versionString;
|
||||
String senderPeerAddress = null;
|
||||
if (byteBuffer.hasRemaining()) {
|
||||
senderPeerAddress = Serialization.deserializeSizedString(byteBuffer, 255);
|
||||
try {
|
||||
versionString = Serialization.deserializeSizedString(byteBuffer, 255);
|
||||
|
||||
// Sender peer address added in v3.0, so is an optional field. Older versions won't send it.
|
||||
if (byteBuffer.hasRemaining()) {
|
||||
senderPeerAddress = Serialization.deserializeSizedString(byteBuffer, 255);
|
||||
}
|
||||
} catch (TransformationException e) {
|
||||
throw new MessageException(e.getMessage(), e);
|
||||
}
|
||||
|
||||
return new HelloMessage(id, timestamp, versionString, senderPeerAddress);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected byte[] toData() throws IOException {
|
||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
|
||||
|
||||
bytes.write(Longs.toByteArray(this.timestamp));
|
||||
|
||||
Serialization.serializeSizedString(bytes, this.versionString);
|
||||
|
||||
Serialization.serializeSizedString(bytes, this.senderPeerAddress);
|
||||
|
||||
return bytes.toByteArray();
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -1,161 +1,67 @@
|
||||
package org.qortal.network.message;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.qortal.crypto.Crypto;
|
||||
import org.qortal.network.Network;
|
||||
import org.qortal.transform.TransformationException;
|
||||
|
||||
import com.google.common.primitives.Ints;
|
||||
|
||||
import static java.util.Arrays.stream;
|
||||
import static java.util.stream.Collectors.toMap;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.nio.BufferUnderflowException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Network message for sending over network, or unpacked data received from network.
|
||||
* <p></p>
|
||||
* <p>
|
||||
* For messages received from network, subclass's {@code fromByteBuffer()} method is used
|
||||
* to construct a subclassed instance. Original bytes from network are not retained.
|
||||
* Access to deserialized data should be via subclass's getters. Ideally there should be NO setters!
|
||||
* </p>
|
||||
* <p></p>
|
||||
* <p>
|
||||
* Each subclass's <b>public</b> constructor is for building a message to send <b>only</b>.
|
||||
* The constructor will serialize into byte form but <b>not</b> store the passed args.
|
||||
* Serialized bytes are saved into superclass (Message) {@code dataBytes} and, if not empty,
|
||||
* a checksum is created and saved into {@code checksumBytes}.
|
||||
* Therefore: <i>do not use subclass's getters after using constructor!</i>
|
||||
* </p>
|
||||
* <p></p>
|
||||
* <p>
|
||||
* For subclasses where outgoing versions might be usefully cached, they can implement Clonable
|
||||
* as long if they are safe to use {@link Object#clone()}.
|
||||
* </p>
|
||||
*/
|
||||
public abstract class Message {
|
||||
|
||||
// MAGIC(4) + TYPE(4) + HAS-ID(1) + ID?(4) + DATA-SIZE(4) + CHECKSUM?(4) + DATA?(*)
|
||||
private static final int MAGIC_LENGTH = 4;
|
||||
private static final int TYPE_LENGTH = 4;
|
||||
private static final int HAS_ID_LENGTH = 1;
|
||||
private static final int ID_LENGTH = 4;
|
||||
private static final int DATA_SIZE_LENGTH = 4;
|
||||
private static final int CHECKSUM_LENGTH = 4;
|
||||
|
||||
private static final int MAX_DATA_SIZE = 10 * 1024 * 1024; // 10MB
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
public static class MessageException extends Exception {
|
||||
public MessageException() {
|
||||
}
|
||||
protected static final byte[] EMPTY_DATA_BYTES = new byte[0];
|
||||
|
||||
public MessageException(String message) {
|
||||
super(message);
|
||||
}
|
||||
protected int id;
|
||||
protected final MessageType type;
|
||||
|
||||
public MessageException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public MessageException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
}
|
||||
|
||||
public enum MessageType {
|
||||
// Handshaking
|
||||
HELLO(0),
|
||||
GOODBYE(1),
|
||||
CHALLENGE(2),
|
||||
RESPONSE(3),
|
||||
|
||||
// Status / notifications
|
||||
HEIGHT_V2(10),
|
||||
PING(11),
|
||||
PONG(12),
|
||||
|
||||
// Requesting data
|
||||
PEERS_V2(20),
|
||||
GET_PEERS(21),
|
||||
|
||||
TRANSACTION(30),
|
||||
GET_TRANSACTION(31),
|
||||
|
||||
TRANSACTION_SIGNATURES(40),
|
||||
GET_UNCONFIRMED_TRANSACTIONS(41),
|
||||
|
||||
BLOCK(50),
|
||||
GET_BLOCK(51),
|
||||
|
||||
SIGNATURES(60),
|
||||
GET_SIGNATURES_V2(61),
|
||||
|
||||
BLOCK_SUMMARIES(70),
|
||||
GET_BLOCK_SUMMARIES(71),
|
||||
|
||||
ONLINE_ACCOUNTS(80),
|
||||
GET_ONLINE_ACCOUNTS(81),
|
||||
ONLINE_ACCOUNTS_V2(82),
|
||||
GET_ONLINE_ACCOUNTS_V2(83),
|
||||
|
||||
ARBITRARY_DATA(90),
|
||||
GET_ARBITRARY_DATA(91),
|
||||
|
||||
BLOCKS(100),
|
||||
GET_BLOCKS(101),
|
||||
|
||||
ARBITRARY_DATA_FILE(110),
|
||||
GET_ARBITRARY_DATA_FILE(111),
|
||||
|
||||
ARBITRARY_DATA_FILE_LIST(120),
|
||||
GET_ARBITRARY_DATA_FILE_LIST(121),
|
||||
|
||||
ARBITRARY_SIGNATURES(130),
|
||||
|
||||
TRADE_PRESENCES(140),
|
||||
GET_TRADE_PRESENCES(141),
|
||||
|
||||
ARBITRARY_METADATA(150),
|
||||
GET_ARBITRARY_METADATA(151);
|
||||
|
||||
public final int value;
|
||||
public final Method fromByteBufferMethod;
|
||||
|
||||
private static final Map<Integer, MessageType> map = stream(MessageType.values())
|
||||
.collect(toMap(messageType -> messageType.value, messageType -> messageType));
|
||||
|
||||
private MessageType(int value) {
|
||||
this.value = value;
|
||||
|
||||
String[] classNameParts = this.name().toLowerCase().split("_");
|
||||
|
||||
for (int i = 0; i < classNameParts.length; ++i)
|
||||
classNameParts[i] = classNameParts[i].substring(0, 1).toUpperCase().concat(classNameParts[i].substring(1));
|
||||
|
||||
String className = String.join("", classNameParts);
|
||||
|
||||
Method method;
|
||||
try {
|
||||
Class<?> subclass = Class.forName(String.join("", Message.class.getPackage().getName(), ".", className, "Message"));
|
||||
|
||||
method = subclass.getDeclaredMethod("fromByteBuffer", int.class, ByteBuffer.class);
|
||||
} catch (ClassNotFoundException | NoSuchMethodException | SecurityException e) {
|
||||
method = null;
|
||||
}
|
||||
|
||||
this.fromByteBufferMethod = method;
|
||||
}
|
||||
|
||||
public static MessageType valueOf(int value) {
|
||||
return map.get(value);
|
||||
}
|
||||
|
||||
public Message fromByteBuffer(int id, ByteBuffer byteBuffer) throws MessageException {
|
||||
if (this.fromByteBufferMethod == null)
|
||||
throw new MessageException("Unsupported message type [" + value + "] during conversion from bytes");
|
||||
|
||||
try {
|
||||
return (Message) this.fromByteBufferMethod.invoke(null, id, byteBuffer);
|
||||
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
|
||||
if (e.getCause() instanceof BufferUnderflowException)
|
||||
throw new MessageException("Byte data too short for " + name() + " message");
|
||||
|
||||
throw new MessageException("Internal error with " + name() + " message during conversion from bytes");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int id;
|
||||
private MessageType type;
|
||||
/** Serialized outgoing message data. Expected to be written to by subclass. */
|
||||
protected byte[] dataBytes;
|
||||
/** Serialized outgoing message checksum. Expected to be written to by subclass. */
|
||||
protected byte[] checksumBytes;
|
||||
|
||||
/** Typically called by subclass when constructing message from received network data. */
|
||||
protected Message(int id, MessageType type) {
|
||||
this.id = id;
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
/** Typically called by subclass when constructing outgoing message. */
|
||||
protected Message(MessageType type) {
|
||||
this(-1, type);
|
||||
}
|
||||
@@ -179,9 +85,9 @@ public abstract class Message {
|
||||
/**
|
||||
* Attempt to read a message from byte buffer.
|
||||
*
|
||||
* @param readOnlyBuffer
|
||||
* @param readOnlyBuffer ByteBuffer containing bytes read from network
|
||||
* @return null if no complete message can be read
|
||||
* @throws MessageException
|
||||
* @throws MessageException if message could not be decoded or is invalid
|
||||
*/
|
||||
public static Message fromByteBuffer(ByteBuffer readOnlyBuffer) throws MessageException {
|
||||
try {
|
||||
@@ -256,9 +162,27 @@ public abstract class Message {
|
||||
return Arrays.copyOfRange(Crypto.digest(dataBuffer), 0, CHECKSUM_LENGTH);
|
||||
}
|
||||
|
||||
public void checkValidOutgoing() throws MessageException {
|
||||
// We expect subclass to have initialized these
|
||||
if (this.dataBytes == null)
|
||||
throw new MessageException("Missing data payload");
|
||||
if (this.dataBytes.length > 0 && this.checksumBytes == null)
|
||||
throw new MessageException("Missing data checksum");
|
||||
}
|
||||
|
||||
public byte[] toBytes() throws MessageException {
|
||||
checkValidOutgoing();
|
||||
|
||||
// We can calculate exact length
|
||||
int messageLength = MAGIC_LENGTH + TYPE_LENGTH + HAS_ID_LENGTH;
|
||||
messageLength += this.hasId() ? ID_LENGTH : 0;
|
||||
messageLength += DATA_SIZE_LENGTH + this.dataBytes.length > 0 ? CHECKSUM_LENGTH + this.dataBytes.length : 0;
|
||||
|
||||
if (messageLength > MAX_DATA_SIZE)
|
||||
throw new MessageException(String.format("About to send message with length %d larger than allowed %d", messageLength, MAX_DATA_SIZE));
|
||||
|
||||
try {
|
||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream(256);
|
||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream(messageLength);
|
||||
|
||||
// Magic
|
||||
bytes.write(Network.getInstance().getMessageMagic());
|
||||
@@ -273,26 +197,30 @@ public abstract class Message {
|
||||
bytes.write(0);
|
||||
}
|
||||
|
||||
byte[] data = this.toData();
|
||||
if (data == null)
|
||||
throw new MessageException("Missing data payload");
|
||||
bytes.write(Ints.toByteArray(this.dataBytes.length));
|
||||
|
||||
bytes.write(Ints.toByteArray(data.length));
|
||||
|
||||
if (data.length > 0) {
|
||||
bytes.write(generateChecksum(data));
|
||||
bytes.write(data);
|
||||
if (this.dataBytes.length > 0) {
|
||||
bytes.write(this.checksumBytes);
|
||||
bytes.write(this.dataBytes);
|
||||
}
|
||||
|
||||
if (bytes.size() > MAX_DATA_SIZE)
|
||||
throw new MessageException(String.format("About to send message with length %d larger than allowed %d", bytes.size(), MAX_DATA_SIZE));
|
||||
|
||||
return bytes.toByteArray();
|
||||
} catch (IOException | TransformationException e) {
|
||||
} catch (IOException e) {
|
||||
throw new MessageException("Failed to serialize message", e);
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract byte[] toData() throws IOException, TransformationException;
|
||||
public static <M extends Message> M cloneWithNewId(M message, int newId) {
|
||||
M clone;
|
||||
|
||||
try {
|
||||
clone = (M) message.clone();
|
||||
} catch (CloneNotSupportedException e) {
|
||||
throw new UnsupportedOperationException("Message sub-class not cloneable");
|
||||
}
|
||||
|
||||
clone.setId(newId);
|
||||
return clone;
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -0,0 +1,19 @@
|
||||
package org.qortal.network.message;
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
public class MessageException extends Exception {
|
||||
public MessageException() {
|
||||
}
|
||||
|
||||
public MessageException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public MessageException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public MessageException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
}
|
@@ -0,0 +1,8 @@
|
||||
package org.qortal.network.message;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
@FunctionalInterface
|
||||
public interface MessageProducer {
|
||||
Message fromByteBuffer(int id, ByteBuffer byteBuffer) throws MessageException;
|
||||
}
|
96
src/main/java/org/qortal/network/message/MessageType.java
Normal file
96
src/main/java/org/qortal/network/message/MessageType.java
Normal file
@@ -0,0 +1,96 @@
|
||||
package org.qortal.network.message;
|
||||
|
||||
import java.nio.BufferUnderflowException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Map;
|
||||
|
||||
import static java.util.Arrays.stream;
|
||||
import static java.util.stream.Collectors.toMap;
|
||||
|
||||
public enum MessageType {
|
||||
// Handshaking
|
||||
HELLO(0, HelloMessage::fromByteBuffer),
|
||||
GOODBYE(1, GoodbyeMessage::fromByteBuffer),
|
||||
CHALLENGE(2, ChallengeMessage::fromByteBuffer),
|
||||
RESPONSE(3, ResponseMessage::fromByteBuffer),
|
||||
|
||||
// Status / notifications
|
||||
HEIGHT_V2(10, HeightV2Message::fromByteBuffer),
|
||||
PING(11, PingMessage::fromByteBuffer),
|
||||
PONG(12, PongMessage::fromByteBuffer),
|
||||
|
||||
// Requesting data
|
||||
PEERS_V2(20, PeersV2Message::fromByteBuffer),
|
||||
GET_PEERS(21, GetPeersMessage::fromByteBuffer),
|
||||
|
||||
TRANSACTION(30, TransactionMessage::fromByteBuffer),
|
||||
GET_TRANSACTION(31, GetTransactionMessage::fromByteBuffer),
|
||||
|
||||
TRANSACTION_SIGNATURES(40, TransactionSignaturesMessage::fromByteBuffer),
|
||||
GET_UNCONFIRMED_TRANSACTIONS(41, GetUnconfirmedTransactionsMessage::fromByteBuffer),
|
||||
|
||||
BLOCK(50, BlockMessage::fromByteBuffer),
|
||||
GET_BLOCK(51, GetBlockMessage::fromByteBuffer),
|
||||
|
||||
SIGNATURES(60, SignaturesMessage::fromByteBuffer),
|
||||
GET_SIGNATURES_V2(61, GetSignaturesV2Message::fromByteBuffer),
|
||||
|
||||
BLOCK_SUMMARIES(70, BlockSummariesMessage::fromByteBuffer),
|
||||
GET_BLOCK_SUMMARIES(71, GetBlockSummariesMessage::fromByteBuffer),
|
||||
|
||||
ONLINE_ACCOUNTS(80, OnlineAccountsMessage::fromByteBuffer),
|
||||
GET_ONLINE_ACCOUNTS(81, GetOnlineAccountsMessage::fromByteBuffer),
|
||||
ONLINE_ACCOUNTS_V2(82, OnlineAccountsV2Message::fromByteBuffer),
|
||||
GET_ONLINE_ACCOUNTS_V2(83, GetOnlineAccountsV2Message::fromByteBuffer),
|
||||
|
||||
ARBITRARY_DATA(90, ArbitraryDataMessage::fromByteBuffer),
|
||||
GET_ARBITRARY_DATA(91, GetArbitraryDataMessage::fromByteBuffer),
|
||||
|
||||
BLOCKS(100, null), // unsupported
|
||||
GET_BLOCKS(101, null), // unsupported
|
||||
|
||||
ARBITRARY_DATA_FILE(110, ArbitraryDataFileMessage::fromByteBuffer),
|
||||
GET_ARBITRARY_DATA_FILE(111, GetArbitraryDataFileMessage::fromByteBuffer),
|
||||
|
||||
ARBITRARY_DATA_FILE_LIST(120, ArbitraryDataFileListMessage::fromByteBuffer),
|
||||
GET_ARBITRARY_DATA_FILE_LIST(121, GetArbitraryDataFileListMessage::fromByteBuffer),
|
||||
|
||||
ARBITRARY_SIGNATURES(130, ArbitrarySignaturesMessage::fromByteBuffer),
|
||||
|
||||
TRADE_PRESENCES(140, TradePresencesMessage::fromByteBuffer),
|
||||
GET_TRADE_PRESENCES(141, GetTradePresencesMessage::fromByteBuffer),
|
||||
|
||||
ARBITRARY_METADATA(150, ArbitraryMetadataMessage::fromByteBuffer),
|
||||
GET_ARBITRARY_METADATA(151, GetArbitraryMetadataMessage::fromByteBuffer);
|
||||
|
||||
public final int value;
|
||||
public final MessageProducer fromByteBufferMethod;
|
||||
|
||||
private static final Map<Integer, MessageType> map = stream(MessageType.values())
|
||||
.collect(toMap(messageType -> messageType.value, messageType -> messageType));
|
||||
|
||||
MessageType(int value, MessageProducer fromByteBufferMethod) {
|
||||
this.value = value;
|
||||
this.fromByteBufferMethod = fromByteBufferMethod;
|
||||
}
|
||||
|
||||
public static MessageType valueOf(int value) {
|
||||
return map.get(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to read a message from byte buffer.
|
||||
*
|
||||
* @param id message ID or -1
|
||||
* @param byteBuffer ByteBuffer source for message
|
||||
* @return null if no complete message can be read
|
||||
* @throws MessageException if message could not be decoded or is invalid
|
||||
* @throws BufferUnderflowException if not enough bytes in buffer to read message
|
||||
*/
|
||||
public Message fromByteBuffer(int id, ByteBuffer byteBuffer) throws MessageException {
|
||||
if (this.fromByteBufferMethod == null)
|
||||
throw new MessageException("Message type " + this.name() + " unsupported");
|
||||
|
||||
return this.fromByteBufferMethod.fromByteBuffer(id, byteBuffer);
|
||||
}
|
||||
}
|
@@ -2,7 +2,6 @@ package org.qortal.network.message;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@@ -20,7 +19,26 @@ public class OnlineAccountsMessage extends Message {
|
||||
private List<OnlineAccountData> onlineAccounts;
|
||||
|
||||
public OnlineAccountsMessage(List<OnlineAccountData> onlineAccounts) {
|
||||
this(-1, onlineAccounts);
|
||||
super(MessageType.ONLINE_ACCOUNTS);
|
||||
|
||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
|
||||
|
||||
try {
|
||||
bytes.write(Ints.toByteArray(onlineAccounts.size()));
|
||||
|
||||
for (OnlineAccountData onlineAccountData : onlineAccounts) {
|
||||
bytes.write(Longs.toByteArray(onlineAccountData.getTimestamp()));
|
||||
|
||||
bytes.write(onlineAccountData.getSignature());
|
||||
|
||||
bytes.write(onlineAccountData.getPublicKey());
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new AssertionError("IOException shouldn't occur with ByteArrayOutputStream");
|
||||
}
|
||||
|
||||
this.dataBytes = bytes.toByteArray();
|
||||
this.checksumBytes = Message.generateChecksum(this.dataBytes);
|
||||
}
|
||||
|
||||
private OnlineAccountsMessage(int id, List<OnlineAccountData> onlineAccounts) {
|
||||
@@ -33,7 +51,7 @@ public class OnlineAccountsMessage extends Message {
|
||||
return this.onlineAccounts;
|
||||
}
|
||||
|
||||
public static Message fromByteBuffer(int id, ByteBuffer bytes) throws UnsupportedEncodingException {
|
||||
public static Message fromByteBuffer(int id, ByteBuffer bytes) {
|
||||
final int accountCount = bytes.getInt();
|
||||
|
||||
List<OnlineAccountData> onlineAccounts = new ArrayList<>(accountCount);
|
||||
@@ -54,27 +72,4 @@ public class OnlineAccountsMessage extends Message {
|
||||
return new OnlineAccountsMessage(id, onlineAccounts);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected byte[] toData() {
|
||||
try {
|
||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
|
||||
|
||||
bytes.write(Ints.toByteArray(this.onlineAccounts.size()));
|
||||
|
||||
for (int i = 0; i < this.onlineAccounts.size(); ++i) {
|
||||
OnlineAccountData onlineAccountData = this.onlineAccounts.get(i);
|
||||
|
||||
bytes.write(Longs.toByteArray(onlineAccountData.getTimestamp()));
|
||||
|
||||
bytes.write(onlineAccountData.getSignature());
|
||||
|
||||
bytes.write(onlineAccountData.getPublicKey());
|
||||
}
|
||||
|
||||
return bytes.toByteArray();
|
||||
} catch (IOException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -7,13 +7,11 @@ import org.qortal.transform.Transformer;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* For sending online accounts info to remote peer.
|
||||
@@ -25,11 +23,52 @@ import java.util.stream.Collectors;
|
||||
* Also V2 only builds online accounts message once!
|
||||
*/
|
||||
public class OnlineAccountsV2Message extends Message {
|
||||
|
||||
private List<OnlineAccountData> onlineAccounts;
|
||||
private byte[] cachedData;
|
||||
|
||||
public OnlineAccountsV2Message(List<OnlineAccountData> onlineAccounts) {
|
||||
this(-1, onlineAccounts);
|
||||
super(MessageType.ONLINE_ACCOUNTS_V2);
|
||||
|
||||
// Shortcut in case we have no online accounts
|
||||
if (onlineAccounts.isEmpty()) {
|
||||
this.dataBytes = Ints.toByteArray(0);
|
||||
this.checksumBytes = Message.generateChecksum(this.dataBytes);
|
||||
return;
|
||||
}
|
||||
|
||||
// How many of each timestamp
|
||||
Map<Long, Integer> countByTimestamp = new HashMap<>();
|
||||
|
||||
for (OnlineAccountData onlineAccountData : onlineAccounts) {
|
||||
Long timestamp = onlineAccountData.getTimestamp();
|
||||
countByTimestamp.compute(timestamp, (k, v) -> v == null ? 1 : ++v);
|
||||
}
|
||||
|
||||
// We should know exactly how many bytes to allocate now
|
||||
int byteSize = countByTimestamp.size() * (Transformer.INT_LENGTH + Transformer.TIMESTAMP_LENGTH)
|
||||
+ onlineAccounts.size() * (Transformer.SIGNATURE_LENGTH + Transformer.PUBLIC_KEY_LENGTH);
|
||||
|
||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream(byteSize);
|
||||
|
||||
try {
|
||||
for (long timestamp : countByTimestamp.keySet()) {
|
||||
bytes.write(Ints.toByteArray(countByTimestamp.get(timestamp)));
|
||||
|
||||
bytes.write(Longs.toByteArray(timestamp));
|
||||
|
||||
for (OnlineAccountData onlineAccountData : onlineAccounts) {
|
||||
if (onlineAccountData.getTimestamp() == timestamp) {
|
||||
bytes.write(onlineAccountData.getSignature());
|
||||
bytes.write(onlineAccountData.getPublicKey());
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new AssertionError("IOException shouldn't occur with ByteArrayOutputStream");
|
||||
}
|
||||
|
||||
this.dataBytes = bytes.toByteArray();
|
||||
this.checksumBytes = Message.generateChecksum(this.dataBytes);
|
||||
}
|
||||
|
||||
private OnlineAccountsV2Message(int id, List<OnlineAccountData> onlineAccounts) {
|
||||
@@ -42,7 +81,7 @@ public class OnlineAccountsV2Message extends Message {
|
||||
return this.onlineAccounts;
|
||||
}
|
||||
|
||||
public static Message fromByteBuffer(int id, ByteBuffer bytes) throws UnsupportedEncodingException {
|
||||
public static Message fromByteBuffer(int id, ByteBuffer bytes) throws MessageException {
|
||||
int accountCount = bytes.getInt();
|
||||
|
||||
List<OnlineAccountData> onlineAccounts = new ArrayList<>(accountCount);
|
||||
@@ -71,54 +110,4 @@ public class OnlineAccountsV2Message extends Message {
|
||||
return new OnlineAccountsV2Message(id, onlineAccounts);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected synchronized byte[] toData() {
|
||||
if (this.cachedData != null)
|
||||
return this.cachedData;
|
||||
|
||||
// Shortcut in case we have no online accounts
|
||||
if (this.onlineAccounts.isEmpty()) {
|
||||
this.cachedData = Ints.toByteArray(0);
|
||||
return this.cachedData;
|
||||
}
|
||||
|
||||
// How many of each timestamp
|
||||
Map<Long, Integer> countByTimestamp = new HashMap<>();
|
||||
|
||||
for (int i = 0; i < this.onlineAccounts.size(); ++i) {
|
||||
OnlineAccountData onlineAccountData = this.onlineAccounts.get(i);
|
||||
Long timestamp = onlineAccountData.getTimestamp();
|
||||
countByTimestamp.compute(timestamp, (k, v) -> v == null ? 1 : ++v);
|
||||
}
|
||||
|
||||
// We should know exactly how many bytes to allocate now
|
||||
int byteSize = countByTimestamp.size() * (Transformer.INT_LENGTH + Transformer.TIMESTAMP_LENGTH)
|
||||
+ this.onlineAccounts.size() * (Transformer.SIGNATURE_LENGTH + Transformer.PUBLIC_KEY_LENGTH);
|
||||
|
||||
try {
|
||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream(byteSize);
|
||||
|
||||
for (long timestamp : countByTimestamp.keySet()) {
|
||||
bytes.write(Ints.toByteArray(countByTimestamp.get(timestamp)));
|
||||
|
||||
bytes.write(Longs.toByteArray(timestamp));
|
||||
|
||||
for (int i = 0; i < this.onlineAccounts.size(); ++i) {
|
||||
OnlineAccountData onlineAccountData = this.onlineAccounts.get(i);
|
||||
|
||||
if (onlineAccountData.getTimestamp() == timestamp) {
|
||||
bytes.write(onlineAccountData.getSignature());
|
||||
|
||||
bytes.write(onlineAccountData.getPublicKey());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.cachedData = bytes.toByteArray();
|
||||
return this.cachedData;
|
||||
} catch (IOException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -2,7 +2,6 @@ package org.qortal.network.message;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
@@ -19,7 +18,35 @@ public class PeersV2Message extends Message {
|
||||
private List<PeerAddress> peerAddresses;
|
||||
|
||||
public PeersV2Message(List<PeerAddress> peerAddresses) {
|
||||
this(-1, peerAddresses);
|
||||
super(MessageType.PEERS_V2);
|
||||
|
||||
List<byte[]> addresses = new ArrayList<>();
|
||||
|
||||
// First entry represents sending node but contains only port number with empty address.
|
||||
addresses.add(("0.0.0.0:" + Settings.getInstance().getListenPort()).getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
for (PeerAddress peerAddress : peerAddresses)
|
||||
addresses.add(peerAddress.toString().getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
// We can't send addresses that are longer than 255 bytes as length itself is encoded in one byte.
|
||||
addresses.removeIf(addressString -> addressString.length > 255);
|
||||
|
||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
|
||||
|
||||
try {
|
||||
// Number of entries
|
||||
bytes.write(Ints.toByteArray(addresses.size()));
|
||||
|
||||
for (byte[] address : addresses) {
|
||||
bytes.write(address.length);
|
||||
bytes.write(address);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new AssertionError("IOException shouldn't occur with ByteArrayOutputStream");
|
||||
}
|
||||
|
||||
this.dataBytes = bytes.toByteArray();
|
||||
this.checksumBytes = Message.generateChecksum(this.dataBytes);
|
||||
}
|
||||
|
||||
private PeersV2Message(int id, List<PeerAddress> peerAddresses) {
|
||||
@@ -32,7 +59,7 @@ public class PeersV2Message extends Message {
|
||||
return this.peerAddresses;
|
||||
}
|
||||
|
||||
public static Message fromByteBuffer(int id, ByteBuffer byteBuffer) throws UnsupportedEncodingException {
|
||||
public static Message fromByteBuffer(int id, ByteBuffer byteBuffer) throws MessageException {
|
||||
// Read entry count
|
||||
int count = byteBuffer.getInt();
|
||||
|
||||
@@ -49,43 +76,11 @@ public class PeersV2Message extends Message {
|
||||
PeerAddress peerAddress = PeerAddress.fromString(addressString);
|
||||
peerAddresses.add(peerAddress);
|
||||
} catch (IllegalArgumentException e) {
|
||||
// Not valid - ignore
|
||||
throw new MessageException("Invalid peer address in received PEERS_V2 message");
|
||||
}
|
||||
}
|
||||
|
||||
return new PeersV2Message(id, peerAddresses);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected byte[] toData() {
|
||||
try {
|
||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
|
||||
|
||||
List<byte[]> addresses = new ArrayList<>();
|
||||
|
||||
// First entry represents sending node but contains only port number with empty address.
|
||||
addresses.add(("0.0.0.0:" + Settings.getInstance().getListenPort()).getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
for (PeerAddress peerAddress : this.peerAddresses)
|
||||
addresses.add(peerAddress.toString().getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
// We can't send addresses that are longer than 255 bytes as length itself is encoded in one byte.
|
||||
addresses.removeIf(addressString -> addressString.length > 255);
|
||||
|
||||
// Serialize
|
||||
|
||||
// Number of entries
|
||||
bytes.write(Ints.toByteArray(addresses.size()));
|
||||
|
||||
for (byte[] address : addresses) {
|
||||
bytes.write(address.length);
|
||||
bytes.write(address);
|
||||
}
|
||||
|
||||
return bytes.toByteArray();
|
||||
} catch (IOException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -1,25 +1,21 @@
|
||||
package org.qortal.network.message;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public class PingMessage extends Message {
|
||||
|
||||
public PingMessage() {
|
||||
this(-1);
|
||||
super(MessageType.PING);
|
||||
|
||||
this.dataBytes = EMPTY_DATA_BYTES;
|
||||
}
|
||||
|
||||
private PingMessage(int id) {
|
||||
super(id, MessageType.PING);
|
||||
}
|
||||
|
||||
public static Message fromByteBuffer(int id, ByteBuffer bytes) throws UnsupportedEncodingException {
|
||||
public static Message fromByteBuffer(int id, ByteBuffer bytes) {
|
||||
return new PingMessage(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected byte[] toData() {
|
||||
return new byte[0];
|
||||
}
|
||||
|
||||
}
|
||||
|
21
src/main/java/org/qortal/network/message/PongMessage.java
Normal file
21
src/main/java/org/qortal/network/message/PongMessage.java
Normal file
@@ -0,0 +1,21 @@
|
||||
package org.qortal.network.message;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public class PongMessage extends Message {
|
||||
|
||||
public PongMessage() {
|
||||
super(MessageType.PONG);
|
||||
|
||||
this.dataBytes = EMPTY_DATA_BYTES;
|
||||
}
|
||||
|
||||
private PongMessage(int id) {
|
||||
super(id, MessageType.PONG);
|
||||
}
|
||||
|
||||
public static Message fromByteBuffer(int id, ByteBuffer bytes) {
|
||||
return new PongMessage(id);
|
||||
}
|
||||
|
||||
}
|
@@ -10,8 +10,25 @@ public class ResponseMessage extends Message {
|
||||
|
||||
public static final int DATA_LENGTH = 32;
|
||||
|
||||
private final int nonce;
|
||||
private final byte[] data;
|
||||
private int nonce;
|
||||
private byte[] data;
|
||||
|
||||
public ResponseMessage(int nonce, byte[] data) {
|
||||
super(MessageType.RESPONSE);
|
||||
|
||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream(4 + DATA_LENGTH);
|
||||
|
||||
try {
|
||||
bytes.write(Ints.toByteArray(nonce));
|
||||
|
||||
bytes.write(data);
|
||||
} catch (IOException e) {
|
||||
throw new AssertionError("IOException shouldn't occur with ByteArrayOutputStream");
|
||||
}
|
||||
|
||||
this.dataBytes = bytes.toByteArray();
|
||||
this.checksumBytes = Message.generateChecksum(this.dataBytes);
|
||||
}
|
||||
|
||||
private ResponseMessage(int id, int nonce, byte[] data) {
|
||||
super(id, MessageType.RESPONSE);
|
||||
@@ -20,10 +37,6 @@ public class ResponseMessage extends Message {
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public ResponseMessage(int nonce, byte[] data) {
|
||||
this(-1, nonce, data);
|
||||
}
|
||||
|
||||
public int getNonce() {
|
||||
return this.nonce;
|
||||
}
|
||||
@@ -41,15 +54,4 @@ public class ResponseMessage extends Message {
|
||||
return new ResponseMessage(id, nonce, data);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected byte[] toData() throws IOException {
|
||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream(4 + DATA_LENGTH);
|
||||
|
||||
bytes.write(Ints.toByteArray(this.nonce));
|
||||
|
||||
bytes.write(data);
|
||||
|
||||
return bytes.toByteArray();
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -2,7 +2,7 @@ package org.qortal.network.message;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.nio.BufferUnderflowException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@@ -13,12 +13,24 @@ import com.google.common.primitives.Ints;
|
||||
|
||||
public class SignaturesMessage extends Message {
|
||||
|
||||
private static final int BLOCK_SIGNATURE_LENGTH = BlockTransformer.BLOCK_SIGNATURE_LENGTH;
|
||||
|
||||
private List<byte[]> signatures;
|
||||
|
||||
public SignaturesMessage(List<byte[]> signatures) {
|
||||
this(-1, signatures);
|
||||
super(MessageType.SIGNATURES);
|
||||
|
||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
|
||||
|
||||
try {
|
||||
bytes.write(Ints.toByteArray(signatures.size()));
|
||||
|
||||
for (byte[] signature : signatures)
|
||||
bytes.write(signature);
|
||||
} catch (IOException e) {
|
||||
throw new AssertionError("IOException shouldn't occur with ByteArrayOutputStream");
|
||||
}
|
||||
|
||||
this.dataBytes = bytes.toByteArray();
|
||||
this.checksumBytes = Message.generateChecksum(this.dataBytes);
|
||||
}
|
||||
|
||||
private SignaturesMessage(int id, List<byte[]> signatures) {
|
||||
@@ -31,15 +43,15 @@ public class SignaturesMessage extends Message {
|
||||
return this.signatures;
|
||||
}
|
||||
|
||||
public static Message fromByteBuffer(int id, ByteBuffer bytes) throws UnsupportedEncodingException {
|
||||
public static Message fromByteBuffer(int id, ByteBuffer bytes) {
|
||||
int count = bytes.getInt();
|
||||
|
||||
if (bytes.remaining() != count * BLOCK_SIGNATURE_LENGTH)
|
||||
return null;
|
||||
if (bytes.remaining() < count * BlockTransformer.BLOCK_SIGNATURE_LENGTH)
|
||||
throw new BufferUnderflowException();
|
||||
|
||||
List<byte[]> signatures = new ArrayList<>();
|
||||
for (int i = 0; i < count; ++i) {
|
||||
byte[] signature = new byte[BLOCK_SIGNATURE_LENGTH];
|
||||
byte[] signature = new byte[BlockTransformer.BLOCK_SIGNATURE_LENGTH];
|
||||
bytes.get(signature);
|
||||
signatures.add(signature);
|
||||
}
|
||||
@@ -47,20 +59,4 @@ public class SignaturesMessage extends Message {
|
||||
return new SignaturesMessage(id, signatures);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected byte[] toData() {
|
||||
try {
|
||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
|
||||
|
||||
bytes.write(Ints.toByteArray(this.signatures.size()));
|
||||
|
||||
for (byte[] signature : this.signatures)
|
||||
bytes.write(signature);
|
||||
|
||||
return bytes.toByteArray();
|
||||
} catch (IOException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -8,7 +8,6 @@ import org.qortal.utils.Base58;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
@@ -21,11 +20,55 @@ import java.util.Map;
|
||||
* Groups of: number of entries, timestamp, then pubkey + sig + AT address for each entry.
|
||||
*/
|
||||
public class TradePresencesMessage extends Message {
|
||||
|
||||
private List<TradePresenceData> tradePresences;
|
||||
private byte[] cachedData;
|
||||
|
||||
public TradePresencesMessage(List<TradePresenceData> tradePresences) {
|
||||
this(-1, tradePresences);
|
||||
super(MessageType.TRADE_PRESENCES);
|
||||
|
||||
// Shortcut in case we have no trade presences
|
||||
if (tradePresences.isEmpty()) {
|
||||
this.dataBytes = Ints.toByteArray(0);
|
||||
this.checksumBytes = Message.generateChecksum(this.dataBytes);
|
||||
return;
|
||||
}
|
||||
|
||||
// How many of each timestamp
|
||||
Map<Long, Integer> countByTimestamp = new HashMap<>();
|
||||
|
||||
for (TradePresenceData tradePresenceData : tradePresences) {
|
||||
Long timestamp = tradePresenceData.getTimestamp();
|
||||
countByTimestamp.compute(timestamp, (k, v) -> v == null ? 1 : ++v);
|
||||
}
|
||||
|
||||
// We should know exactly how many bytes to allocate now
|
||||
int byteSize = countByTimestamp.size() * (Transformer.INT_LENGTH + Transformer.TIMESTAMP_LENGTH)
|
||||
+ tradePresences.size() * (Transformer.PUBLIC_KEY_LENGTH + Transformer.SIGNATURE_LENGTH + Transformer.ADDRESS_LENGTH);
|
||||
|
||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream(byteSize);
|
||||
|
||||
try {
|
||||
for (long timestamp : countByTimestamp.keySet()) {
|
||||
bytes.write(Ints.toByteArray(countByTimestamp.get(timestamp)));
|
||||
|
||||
bytes.write(Longs.toByteArray(timestamp));
|
||||
|
||||
for (TradePresenceData tradePresenceData : tradePresences) {
|
||||
if (tradePresenceData.getTimestamp() == timestamp) {
|
||||
bytes.write(tradePresenceData.getPublicKey());
|
||||
|
||||
bytes.write(tradePresenceData.getSignature());
|
||||
|
||||
bytes.write(Base58.decode(tradePresenceData.getAtAddress()));
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new AssertionError("IOException shouldn't occur with ByteArrayOutputStream");
|
||||
}
|
||||
|
||||
this.dataBytes = bytes.toByteArray();
|
||||
this.checksumBytes = Message.generateChecksum(this.dataBytes);
|
||||
}
|
||||
|
||||
private TradePresencesMessage(int id, List<TradePresenceData> tradePresences) {
|
||||
@@ -38,7 +81,7 @@ public class TradePresencesMessage extends Message {
|
||||
return this.tradePresences;
|
||||
}
|
||||
|
||||
public static Message fromByteBuffer(int id, ByteBuffer bytes) throws UnsupportedEncodingException {
|
||||
public static Message fromByteBuffer(int id, ByteBuffer bytes) {
|
||||
int groupedEntriesCount = bytes.getInt();
|
||||
|
||||
List<TradePresenceData> tradePresences = new ArrayList<>(groupedEntriesCount);
|
||||
@@ -71,53 +114,4 @@ public class TradePresencesMessage extends Message {
|
||||
return new TradePresencesMessage(id, tradePresences);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected synchronized byte[] toData() {
|
||||
if (this.cachedData != null)
|
||||
return this.cachedData;
|
||||
|
||||
// Shortcut in case we have no trade presences
|
||||
if (this.tradePresences.isEmpty()) {
|
||||
this.cachedData = Ints.toByteArray(0);
|
||||
return this.cachedData;
|
||||
}
|
||||
|
||||
// How many of each timestamp
|
||||
Map<Long, Integer> countByTimestamp = new HashMap<>();
|
||||
|
||||
for (TradePresenceData tradePresenceData : this.tradePresences) {
|
||||
Long timestamp = tradePresenceData.getTimestamp();
|
||||
countByTimestamp.compute(timestamp, (k, v) -> v == null ? 1 : ++v);
|
||||
}
|
||||
|
||||
// We should know exactly how many bytes to allocate now
|
||||
int byteSize = countByTimestamp.size() * (Transformer.INT_LENGTH + Transformer.TIMESTAMP_LENGTH)
|
||||
+ this.tradePresences.size() * (Transformer.PUBLIC_KEY_LENGTH + Transformer.SIGNATURE_LENGTH + Transformer.ADDRESS_LENGTH);
|
||||
|
||||
try {
|
||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream(byteSize);
|
||||
|
||||
for (long timestamp : countByTimestamp.keySet()) {
|
||||
bytes.write(Ints.toByteArray(countByTimestamp.get(timestamp)));
|
||||
|
||||
bytes.write(Longs.toByteArray(timestamp));
|
||||
|
||||
for (TradePresenceData tradePresenceData : this.tradePresences) {
|
||||
if (tradePresenceData.getTimestamp() == timestamp) {
|
||||
bytes.write(tradePresenceData.getPublicKey());
|
||||
|
||||
bytes.write(tradePresenceData.getSignature());
|
||||
|
||||
bytes.write(Base58.decode(tradePresenceData.getAtAddress()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.cachedData = bytes.toByteArray();
|
||||
return this.cachedData;
|
||||
} catch (IOException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -1,6 +1,5 @@
|
||||
package org.qortal.network.message;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.qortal.data.transaction.TransactionData;
|
||||
@@ -11,8 +10,11 @@ public class TransactionMessage extends Message {
|
||||
|
||||
private TransactionData transactionData;
|
||||
|
||||
public TransactionMessage(TransactionData transactionData) {
|
||||
this(-1, transactionData);
|
||||
public TransactionMessage(TransactionData transactionData) throws TransformationException {
|
||||
super(MessageType.TRANSACTION);
|
||||
|
||||
this.dataBytes = TransactionTransformer.toBytes(transactionData);
|
||||
this.checksumBytes = Message.generateChecksum(this.dataBytes);
|
||||
}
|
||||
|
||||
private TransactionMessage(int id, TransactionData transactionData) {
|
||||
@@ -25,26 +27,16 @@ public class TransactionMessage extends Message {
|
||||
return this.transactionData;
|
||||
}
|
||||
|
||||
public static Message fromByteBuffer(int id, ByteBuffer byteBuffer) throws UnsupportedEncodingException {
|
||||
try {
|
||||
TransactionData transactionData = TransactionTransformer.fromByteBuffer(byteBuffer);
|
||||
|
||||
return new TransactionMessage(id, transactionData);
|
||||
} catch (TransformationException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected byte[] toData() {
|
||||
if (this.transactionData == null)
|
||||
return null;
|
||||
public static Message fromByteBuffer(int id, ByteBuffer byteBuffer) throws MessageException {
|
||||
TransactionData transactionData;
|
||||
|
||||
try {
|
||||
return TransactionTransformer.toBytes(this.transactionData);
|
||||
transactionData = TransactionTransformer.fromByteBuffer(byteBuffer);
|
||||
} catch (TransformationException e) {
|
||||
return null;
|
||||
throw new MessageException(e.getMessage(), e);
|
||||
}
|
||||
|
||||
return new TransactionMessage(id, transactionData);
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -2,7 +2,7 @@ package org.qortal.network.message;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.nio.BufferUnderflowException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@@ -13,12 +13,24 @@ import com.google.common.primitives.Ints;
|
||||
|
||||
public class TransactionSignaturesMessage extends Message {
|
||||
|
||||
private static final int SIGNATURE_LENGTH = Transformer.SIGNATURE_LENGTH;
|
||||
|
||||
private List<byte[]> signatures;
|
||||
|
||||
public TransactionSignaturesMessage(List<byte[]> signatures) {
|
||||
this(-1, signatures);
|
||||
super(MessageType.TRANSACTION_SIGNATURES);
|
||||
|
||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
|
||||
|
||||
try {
|
||||
bytes.write(Ints.toByteArray(signatures.size()));
|
||||
|
||||
for (byte[] signature : signatures)
|
||||
bytes.write(signature);
|
||||
} catch (IOException e) {
|
||||
throw new AssertionError("IOException shouldn't occur with ByteArrayOutputStream");
|
||||
}
|
||||
|
||||
this.dataBytes = bytes.toByteArray();
|
||||
this.checksumBytes = Message.generateChecksum(this.dataBytes);
|
||||
}
|
||||
|
||||
private TransactionSignaturesMessage(int id, List<byte[]> signatures) {
|
||||
@@ -31,15 +43,15 @@ public class TransactionSignaturesMessage extends Message {
|
||||
return this.signatures;
|
||||
}
|
||||
|
||||
public static Message fromByteBuffer(int id, ByteBuffer bytes) throws UnsupportedEncodingException {
|
||||
public static Message fromByteBuffer(int id, ByteBuffer bytes) {
|
||||
int count = bytes.getInt();
|
||||
|
||||
if (bytes.remaining() != count * SIGNATURE_LENGTH)
|
||||
return null;
|
||||
if (bytes.remaining() < count * Transformer.SIGNATURE_LENGTH)
|
||||
throw new BufferUnderflowException();
|
||||
|
||||
List<byte[]> signatures = new ArrayList<>();
|
||||
for (int i = 0; i < count; ++i) {
|
||||
byte[] signature = new byte[SIGNATURE_LENGTH];
|
||||
byte[] signature = new byte[Transformer.SIGNATURE_LENGTH];
|
||||
bytes.get(signature);
|
||||
signatures.add(signature);
|
||||
}
|
||||
@@ -47,20 +59,4 @@ public class TransactionSignaturesMessage extends Message {
|
||||
return new TransactionSignaturesMessage(id, signatures);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected byte[] toData() {
|
||||
try {
|
||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
|
||||
|
||||
bytes.write(Ints.toByteArray(this.signatures.size()));
|
||||
|
||||
for (byte[] signature : this.signatures)
|
||||
bytes.write(signature);
|
||||
|
||||
return bytes.toByteArray();
|
||||
} catch (IOException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
22
src/main/java/org/qortal/network/task/BroadcastTask.java
Normal file
22
src/main/java/org/qortal/network/task/BroadcastTask.java
Normal file
@@ -0,0 +1,22 @@
|
||||
package org.qortal.network.task;
|
||||
|
||||
import org.qortal.controller.Controller;
|
||||
import org.qortal.network.Network;
|
||||
import org.qortal.network.Peer;
|
||||
import org.qortal.network.message.Message;
|
||||
import org.qortal.utils.ExecuteProduceConsume.Task;
|
||||
|
||||
public class BroadcastTask implements Task {
|
||||
public BroadcastTask() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "BroadcastTask";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void perform() throws InterruptedException {
|
||||
Controller.getInstance().doNetworkBroadcast();
|
||||
}
|
||||
}
|
143
src/main/java/org/qortal/network/task/ChannelAcceptTask.java
Normal file
143
src/main/java/org/qortal/network/task/ChannelAcceptTask.java
Normal file
@@ -0,0 +1,143 @@
|
||||
package org.qortal.network.task;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.qortal.controller.arbitrary.ArbitraryDataFileManager;
|
||||
import org.qortal.network.Network;
|
||||
import org.qortal.network.Peer;
|
||||
import org.qortal.network.PeerAddress;
|
||||
import org.qortal.settings.Settings;
|
||||
import org.qortal.utils.ExecuteProduceConsume.Task;
|
||||
import org.qortal.utils.NTP;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.channels.SelectionKey;
|
||||
import java.nio.channels.ServerSocketChannel;
|
||||
import java.nio.channels.SocketChannel;
|
||||
import java.util.List;
|
||||
|
||||
public class ChannelAcceptTask implements Task {
|
||||
private static final Logger LOGGER = LogManager.getLogger(ChannelAcceptTask.class);
|
||||
|
||||
private final ServerSocketChannel serverSocketChannel;
|
||||
|
||||
public ChannelAcceptTask(ServerSocketChannel serverSocketChannel) {
|
||||
this.serverSocketChannel = serverSocketChannel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "ChannelAcceptTask";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void perform() throws InterruptedException {
|
||||
Network network = Network.getInstance();
|
||||
SocketChannel socketChannel;
|
||||
|
||||
try {
|
||||
if (network.getImmutableConnectedPeers().size() >= network.getMaxPeers()) {
|
||||
// We have enough peers
|
||||
LOGGER.debug("Ignoring pending incoming connections because the server is full");
|
||||
return;
|
||||
}
|
||||
|
||||
socketChannel = serverSocketChannel.accept();
|
||||
|
||||
network.setInterestOps(serverSocketChannel, SelectionKey.OP_ACCEPT);
|
||||
} catch (IOException e) {
|
||||
return;
|
||||
}
|
||||
|
||||
// No connection actually accepted?
|
||||
if (socketChannel == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
PeerAddress address = PeerAddress.fromSocket(socketChannel.socket());
|
||||
List<String> fixedNetwork = Settings.getInstance().getFixedNetwork();
|
||||
if (fixedNetwork != null && !fixedNetwork.isEmpty() && network.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;
|
||||
}
|
||||
|
||||
// We allow up to a maximum of maxPeers connected peers, of which...
|
||||
// - maxDataPeers must be prearranged data connections (these are intentionally short-lived)
|
||||
// - the remainder can be any regular peers
|
||||
|
||||
// Firstly, determine the maximum limits
|
||||
int maxPeers = Settings.getInstance().getMaxPeers();
|
||||
int maxDataPeers = Settings.getInstance().getMaxDataPeers();
|
||||
int maxRegularPeers = maxPeers - maxDataPeers;
|
||||
|
||||
// Next, obtain the current state
|
||||
int connectedDataPeerCount = Network.getInstance().getImmutableConnectedDataPeers().size();
|
||||
int connectedRegularPeerCount = Network.getInstance().getImmutableConnectedNonDataPeers().size();
|
||||
|
||||
// Check if the incoming connection should be considered a data or regular peer
|
||||
boolean isDataPeer = ArbitraryDataFileManager.getInstance().isPeerRequestingData(address.getHost());
|
||||
|
||||
// Finally, decide if we have any capacity for this incoming peer
|
||||
boolean connectionLimitReached;
|
||||
if (isDataPeer) {
|
||||
connectionLimitReached = (connectedDataPeerCount >= maxDataPeers);
|
||||
}
|
||||
else {
|
||||
connectionLimitReached = (connectedRegularPeerCount >= maxRegularPeers);
|
||||
}
|
||||
|
||||
// Extra maxPeers check just to be safe
|
||||
if (Network.getInstance().getImmutableConnectedPeers().size() >= maxPeers) {
|
||||
connectionLimitReached = true;
|
||||
}
|
||||
|
||||
if (connectionLimitReached) {
|
||||
try {
|
||||
// We have enough peers
|
||||
LOGGER.debug("Connection discarded from peer {} because the server is full", 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", address);
|
||||
socketChannel.close();
|
||||
return;
|
||||
}
|
||||
|
||||
LOGGER.debug("Connection accepted from peer {}", address);
|
||||
|
||||
newPeer = new Peer(socketChannel);
|
||||
if (isDataPeer) {
|
||||
newPeer.setMaxConnectionAge(Settings.getInstance().getMaxDataPeerConnectionTime() * 1000L);
|
||||
}
|
||||
newPeer.setIsDataPeer(isDataPeer);
|
||||
network.addConnectedPeer(newPeer);
|
||||
|
||||
} 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?
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
network.onPeerReady(newPeer);
|
||||
}
|
||||
}
|
49
src/main/java/org/qortal/network/task/ChannelReadTask.java
Normal file
49
src/main/java/org/qortal/network/task/ChannelReadTask.java
Normal file
@@ -0,0 +1,49 @@
|
||||
package org.qortal.network.task;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import org.qortal.network.Network;
|
||||
import org.qortal.network.Peer;
|
||||
import org.qortal.utils.ExecuteProduceConsume.Task;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.channels.SelectionKey;
|
||||
import java.nio.channels.SocketChannel;
|
||||
|
||||
public class ChannelReadTask implements Task {
|
||||
private static final Logger LOGGER = LogManager.getLogger(ChannelReadTask.class);
|
||||
|
||||
private final SocketChannel socketChannel;
|
||||
private final Peer peer;
|
||||
private final String name;
|
||||
|
||||
public ChannelReadTask(SocketChannel socketChannel, Peer peer) {
|
||||
this.socketChannel = socketChannel;
|
||||
this.peer = peer;
|
||||
this.name = "ChannelReadTask::" + peer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void perform() throws InterruptedException {
|
||||
try {
|
||||
peer.readChannel();
|
||||
|
||||
Network.getInstance().setInterestOps(socketChannel, SelectionKey.OP_READ);
|
||||
} catch (IOException e) {
|
||||
if (e.getMessage() != null && e.getMessage().toLowerCase().contains("connection reset")) {
|
||||
peer.disconnect("Connection reset");
|
||||
return;
|
||||
}
|
||||
|
||||
LOGGER.trace("[{}] Network thread {} encountered I/O error: {}", peer.getPeerConnectionId(),
|
||||
Thread.currentThread().getId(), e.getMessage(), e);
|
||||
peer.disconnect("I/O error");
|
||||
}
|
||||
}
|
||||
}
|
52
src/main/java/org/qortal/network/task/ChannelWriteTask.java
Normal file
52
src/main/java/org/qortal/network/task/ChannelWriteTask.java
Normal file
@@ -0,0 +1,52 @@
|
||||
package org.qortal.network.task;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.qortal.network.Network;
|
||||
import org.qortal.network.Peer;
|
||||
import org.qortal.utils.ExecuteProduceConsume.Task;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.channels.SelectionKey;
|
||||
import java.nio.channels.SocketChannel;
|
||||
|
||||
public class ChannelWriteTask implements Task {
|
||||
private static final Logger LOGGER = LogManager.getLogger(ChannelWriteTask.class);
|
||||
|
||||
private final SocketChannel socketChannel;
|
||||
private final Peer peer;
|
||||
private final String name;
|
||||
|
||||
public ChannelWriteTask(SocketChannel socketChannel, Peer peer) {
|
||||
this.socketChannel = socketChannel;
|
||||
this.peer = peer;
|
||||
this.name = "ChannelWriteTask::" + peer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void perform() throws InterruptedException {
|
||||
try {
|
||||
boolean isSocketClogged = peer.writeChannel();
|
||||
|
||||
// Tell Network that we've finished
|
||||
Network.getInstance().notifyChannelNotWriting(socketChannel);
|
||||
|
||||
if (isSocketClogged)
|
||||
Network.getInstance().setInterestOps(this.socketChannel, SelectionKey.OP_WRITE);
|
||||
} catch (IOException e) {
|
||||
if (e.getMessage() != null && e.getMessage().toLowerCase().contains("connection reset")) {
|
||||
peer.disconnect("Connection reset");
|
||||
return;
|
||||
}
|
||||
|
||||
LOGGER.trace("[{}] Network thread {} encountered I/O error: {}", peer.getPeerConnectionId(),
|
||||
Thread.currentThread().getId(), e.getMessage(), e);
|
||||
peer.disconnect("I/O error");
|
||||
}
|
||||
}
|
||||
}
|
28
src/main/java/org/qortal/network/task/MessageTask.java
Normal file
28
src/main/java/org/qortal/network/task/MessageTask.java
Normal file
@@ -0,0 +1,28 @@
|
||||
package org.qortal.network.task;
|
||||
|
||||
import org.qortal.network.Network;
|
||||
import org.qortal.network.Peer;
|
||||
import org.qortal.network.message.Message;
|
||||
import org.qortal.utils.ExecuteProduceConsume.Task;
|
||||
|
||||
public class MessageTask implements Task {
|
||||
private final Peer peer;
|
||||
private final Message nextMessage;
|
||||
private final String name;
|
||||
|
||||
public MessageTask(Peer peer, Message nextMessage) {
|
||||
this.peer = peer;
|
||||
this.nextMessage = nextMessage;
|
||||
this.name = "MessageTask::" + peer + "::" + nextMessage.getType();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void perform() throws InterruptedException {
|
||||
Network.getInstance().onMessage(peer, nextMessage);
|
||||
}
|
||||
}
|
33
src/main/java/org/qortal/network/task/PeerConnectTask.java
Normal file
33
src/main/java/org/qortal/network/task/PeerConnectTask.java
Normal file
@@ -0,0 +1,33 @@
|
||||
package org.qortal.network.task;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.qortal.network.Network;
|
||||
import org.qortal.network.Peer;
|
||||
import org.qortal.network.message.Message;
|
||||
import org.qortal.network.message.MessageType;
|
||||
import org.qortal.network.message.PingMessage;
|
||||
import org.qortal.utils.ExecuteProduceConsume.Task;
|
||||
import org.qortal.utils.NTP;
|
||||
|
||||
public class PeerConnectTask implements Task {
|
||||
private static final Logger LOGGER = LogManager.getLogger(PeerConnectTask.class);
|
||||
|
||||
private final Peer peer;
|
||||
private final String name;
|
||||
|
||||
public PeerConnectTask(Peer peer) {
|
||||
this.peer = peer;
|
||||
this.name = "PeerConnectTask::" + peer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void perform() throws InterruptedException {
|
||||
Network.getInstance().connectPeer(peer);
|
||||
}
|
||||
}
|
44
src/main/java/org/qortal/network/task/PingTask.java
Normal file
44
src/main/java/org/qortal/network/task/PingTask.java
Normal file
@@ -0,0 +1,44 @@
|
||||
package org.qortal.network.task;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.qortal.network.Peer;
|
||||
import org.qortal.network.message.Message;
|
||||
import org.qortal.network.message.MessageType;
|
||||
import org.qortal.network.message.PingMessage;
|
||||
import org.qortal.utils.ExecuteProduceConsume.Task;
|
||||
import org.qortal.utils.NTP;
|
||||
|
||||
public class PingTask implements Task {
|
||||
private static final Logger LOGGER = LogManager.getLogger(PingTask.class);
|
||||
|
||||
private final Peer peer;
|
||||
private final Long now;
|
||||
private final String name;
|
||||
|
||||
public PingTask(Peer peer, Long now) {
|
||||
this.peer = peer;
|
||||
this.now = now;
|
||||
this.name = "PingTask::" + peer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void perform() throws InterruptedException {
|
||||
PingMessage pingMessage = new PingMessage();
|
||||
Message message = peer.getResponse(pingMessage);
|
||||
|
||||
if (message == null || message.getType() != MessageType.PING) {
|
||||
LOGGER.debug("[{}] Didn't receive reply from {} for PING ID {}",
|
||||
peer.getPeerConnectionId(), peer, pingMessage.getId());
|
||||
peer.disconnect("no ping received");
|
||||
return;
|
||||
}
|
||||
|
||||
peer.setLastPing(NTP.getTime() - now);
|
||||
}
|
||||
}
|
@@ -76,6 +76,9 @@ public interface AccountRepository {
|
||||
*/
|
||||
public void setBlocksMintedAdjustment(AccountData accountData) throws DataException;
|
||||
|
||||
/** Returns account's minted block count or null if account not found. */
|
||||
public Integer getMintedBlockCount(String address) throws DataException;
|
||||
|
||||
/**
|
||||
* Saves account's minted block count and public key if present, in repository.
|
||||
* <p>
|
||||
|
@@ -419,8 +419,8 @@ public class Bootstrap {
|
||||
downloaded += bytesRead;
|
||||
|
||||
if (fileSize > 0) {
|
||||
int progress = (int)((double)downloaded / (double)fileSize * 100);
|
||||
SplashFrame.getInstance().updateStatus(String.format("Downloading %s bootstrap... (%d%%)", type, progress));
|
||||
double progress = (double)downloaded / (double)fileSize * 100;
|
||||
SplashFrame.getInstance().updateStatus(String.format("Downloading %s bootstrap... (%.1f%%)", type, progress));
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -241,6 +241,20 @@ public class HSQLDBAccountRepository implements AccountRepository {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer getMintedBlockCount(String address) throws DataException {
|
||||
String sql = "SELECT blocks_minted FROM Accounts WHERE account = ?";
|
||||
|
||||
try (ResultSet resultSet = this.repository.checkedExecute(sql, address)) {
|
||||
if (resultSet == null)
|
||||
return null;
|
||||
|
||||
return resultSet.getInt(1);
|
||||
} catch (SQLException e) {
|
||||
throw new DataException("Unable to fetch account's minted block count from repository", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMintedBlockCount(AccountData accountData) throws DataException {
|
||||
HSQLDBSaver saveHelper = new HSQLDBSaver("Accounts");
|
||||
|
@@ -188,7 +188,9 @@ public class Settings {
|
||||
/** Target number of outbound connections to peers we should make. */
|
||||
private int minOutboundPeers = 16;
|
||||
/** Maximum number of peer connections we allow. */
|
||||
private int maxPeers = 32;
|
||||
private int maxPeers = 36;
|
||||
/** Number of slots to reserve for short-lived QDN data transfers */
|
||||
private int maxDataPeers = 4;
|
||||
/** Maximum number of threads for network engine. */
|
||||
private int maxNetworkThreadPoolSize = 32;
|
||||
/** Maximum number of threads for network proof-of-work compute, used during handshaking. */
|
||||
@@ -207,6 +209,8 @@ public class Settings {
|
||||
private int minPeerConnectionTime = 5 * 60; // seconds
|
||||
/** Maximum time (in seconds) that we should attempt to remain connected to a peer for */
|
||||
private int maxPeerConnectionTime = 60 * 60; // seconds
|
||||
/** Maximum time (in seconds) that a peer should remain connected when requesting QDN data */
|
||||
private int maxDataPeerConnectionTime = 2 * 60; // seconds
|
||||
|
||||
/** Whether to sync multiple blocks at once in normal operation */
|
||||
private boolean fastSyncEnabled = true;
|
||||
@@ -646,6 +650,10 @@ public class Settings {
|
||||
return this.maxPeers;
|
||||
}
|
||||
|
||||
public int getMaxDataPeers() {
|
||||
return this.maxDataPeers;
|
||||
}
|
||||
|
||||
public int getMaxNetworkThreadPoolSize() {
|
||||
return this.maxNetworkThreadPoolSize;
|
||||
}
|
||||
@@ -664,6 +672,10 @@ public class Settings {
|
||||
|
||||
public int getMaxPeerConnectionTime() { return this.maxPeerConnectionTime; }
|
||||
|
||||
public int getMaxDataPeerConnectionTime() {
|
||||
return this.maxDataPeerConnectionTime;
|
||||
}
|
||||
|
||||
public String getBlockchainConfig() {
|
||||
return this.blockchainConfig;
|
||||
}
|
||||
|
@@ -29,7 +29,7 @@ public class ChatTransaction extends Transaction {
|
||||
public static final int MAX_DATA_SIZE = 256;
|
||||
public static final int POW_BUFFER_SIZE = 8 * 1024 * 1024; // bytes
|
||||
public static final int POW_DIFFICULTY_WITH_QORT = 8; // leading zero bits
|
||||
public static final int POW_DIFFICULTY_NO_QORT = 14; // leading zero bits
|
||||
public static final int POW_DIFFICULTY_NO_QORT = 12; // leading zero bits
|
||||
|
||||
// Constructors
|
||||
|
||||
|
@@ -185,7 +185,7 @@ public class PresenceTransaction extends Transaction {
|
||||
String signerAddress = Crypto.toAddress(this.transactionData.getCreatorPublicKey());
|
||||
|
||||
for (ATData atData : atsData) {
|
||||
ByteArray atCodeHash = new ByteArray(atData.getCodeHash());
|
||||
ByteArray atCodeHash = ByteArray.wrap(atData.getCodeHash());
|
||||
Supplier<ACCT> acctSupplier = acctSuppliersByCodeHash.get(atCodeHash);
|
||||
if (acctSupplier == null)
|
||||
continue;
|
||||
|
@@ -39,11 +39,7 @@ public class RegisterNameTransaction extends Transaction {
|
||||
|
||||
@Override
|
||||
public long getUnitFee(Long timestamp) {
|
||||
// Use a higher unit fee after the fee increase timestamp
|
||||
if (timestamp > BlockChain.getInstance().getNameRegistrationUnitFeeTimestamp()) {
|
||||
return BlockChain.getInstance().getNameRegistrationUnitFee();
|
||||
}
|
||||
return BlockChain.getInstance().getUnitFee();
|
||||
return BlockChain.getInstance().getNameRegistrationUnitFeeAtTimestamp(timestamp);
|
||||
}
|
||||
|
||||
// Navigation
|
||||
|
@@ -118,10 +118,13 @@ public class UpdateNameTransaction extends Transaction {
|
||||
if (!owner.getAddress().equals(nameData.getOwner()))
|
||||
return ValidationResult.INVALID_NAME_OWNER;
|
||||
|
||||
// Check new name isn't already taken, unless it is the same name (this allows for case-adjusting renames)
|
||||
NameData newNameData = this.repository.getNameRepository().fromReducedName(this.updateNameTransactionData.getReducedNewName());
|
||||
if (newNameData != null && !newNameData.getName().equals(nameData.getName()))
|
||||
return ValidationResult.NAME_ALREADY_REGISTERED;
|
||||
// Additional checks if transaction intends to change name
|
||||
if (!this.updateNameTransactionData.getNewName().isEmpty()) {
|
||||
// Check new name isn't already taken, unless it is the same name (this allows for case-adjusting renames)
|
||||
NameData newNameData = this.repository.getNameRepository().fromReducedName(this.updateNameTransactionData.getReducedNewName());
|
||||
if (newNameData != null && !newNameData.getName().equals(nameData.getName()))
|
||||
return ValidationResult.NAME_ALREADY_REGISTERED;
|
||||
}
|
||||
|
||||
return ValidationResult.OK;
|
||||
}
|
||||
|
@@ -39,12 +39,12 @@ public class ArbitraryTransactionTransformer extends TransactionTransformer {
|
||||
private static final int IDENTIFIER_SIZE_LENGTH = INT_LENGTH;
|
||||
private static final int COMPRESSION_LENGTH = INT_LENGTH;
|
||||
private static final int METHOD_LENGTH = INT_LENGTH;
|
||||
private static final int SECRET_LENGTH = INT_LENGTH; // TODO: wtf?
|
||||
private static final int SECRET_SIZE_LENGTH = INT_LENGTH;
|
||||
|
||||
private static final int EXTRAS_LENGTH = SERVICE_LENGTH + DATA_TYPE_LENGTH + DATA_SIZE_LENGTH;
|
||||
|
||||
private static final int EXTRAS_V5_LENGTH = NONCE_LENGTH + NAME_SIZE_LENGTH + IDENTIFIER_SIZE_LENGTH +
|
||||
METHOD_LENGTH + SECRET_LENGTH + COMPRESSION_LENGTH + RAW_DATA_SIZE_LENGTH + METADATA_HASH_SIZE_LENGTH;
|
||||
METHOD_LENGTH + SECRET_SIZE_LENGTH + COMPRESSION_LENGTH + RAW_DATA_SIZE_LENGTH + METADATA_HASH_SIZE_LENGTH;
|
||||
|
||||
protected static final TransactionLayout layout;
|
||||
|
||||
|
@@ -8,12 +8,16 @@ public class ByteArray implements Comparable<ByteArray> {
|
||||
private int hash;
|
||||
public final byte[] value;
|
||||
|
||||
public ByteArray(byte[] value) {
|
||||
this.value = Objects.requireNonNull(value);
|
||||
private ByteArray(byte[] value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public static ByteArray of(byte[] value) {
|
||||
return new ByteArray(value);
|
||||
public static ByteArray wrap(byte[] value) {
|
||||
return new ByteArray(Objects.requireNonNull(value));
|
||||
}
|
||||
|
||||
public static ByteArray copyOf(byte[] value) {
|
||||
return new ByteArray(Arrays.copyOf(value, value.length));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -36,12 +40,7 @@ public class ByteArray implements Comparable<ByteArray> {
|
||||
byte[] val = this.value;
|
||||
|
||||
if (h == 0 && val.length > 0) {
|
||||
h = 1;
|
||||
|
||||
for (int i = 0; i < val.length; ++i)
|
||||
h = 31 * h + val[i];
|
||||
|
||||
this.hash = h;
|
||||
this.hash = h = Arrays.hashCode(val);
|
||||
}
|
||||
return h;
|
||||
}
|
||||
@@ -53,24 +52,7 @@ public class ByteArray implements Comparable<ByteArray> {
|
||||
}
|
||||
|
||||
public int compareToPrimitive(byte[] otherValue) {
|
||||
byte[] val = this.value;
|
||||
|
||||
if (val.length < otherValue.length)
|
||||
return -1;
|
||||
|
||||
if (val.length > otherValue.length)
|
||||
return 1;
|
||||
|
||||
for (int i = 0; i < val.length; ++i) {
|
||||
int a = val[i] & 0xFF;
|
||||
int b = otherValue[i] & 0xFF;
|
||||
if (a < b)
|
||||
return -1;
|
||||
if (a > b)
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
return Arrays.compareUnsigned(this.value, otherValue);
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
|
@@ -28,7 +28,6 @@ public abstract class ExecuteProduceConsume implements Runnable {
|
||||
|
||||
private final String className;
|
||||
private final Logger logger;
|
||||
private final boolean isLoggerTraceEnabled;
|
||||
|
||||
protected ExecutorService executor;
|
||||
|
||||
@@ -43,12 +42,12 @@ public abstract class ExecuteProduceConsume implements Runnable {
|
||||
private volatile int tasksConsumed = 0;
|
||||
private volatile int spawnFailures = 0;
|
||||
|
||||
/** Whether a new thread has already been spawned and is waiting to start. Used to prevent spawning multiple new threads. */
|
||||
private volatile boolean hasThreadPending = false;
|
||||
|
||||
public ExecuteProduceConsume(ExecutorService executor) {
|
||||
this.className = this.getClass().getSimpleName();
|
||||
this.logger = LogManager.getLogger(this.getClass());
|
||||
this.isLoggerTraceEnabled = this.logger.isTraceEnabled();
|
||||
|
||||
this.executor = executor;
|
||||
}
|
||||
@@ -98,15 +97,14 @@ public abstract class ExecuteProduceConsume implements Runnable {
|
||||
*/
|
||||
protected abstract Task produceTask(boolean canBlock) throws InterruptedException;
|
||||
|
||||
@FunctionalInterface
|
||||
public interface Task {
|
||||
public abstract void perform() throws InterruptedException;
|
||||
String getName();
|
||||
void perform() throws InterruptedException;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
if (this.isLoggerTraceEnabled)
|
||||
Thread.currentThread().setName(this.className + "-" + Thread.currentThread().getId());
|
||||
Thread.currentThread().setName(this.className + "-" + Thread.currentThread().getId());
|
||||
|
||||
boolean wasThreadPending;
|
||||
synchronized (this) {
|
||||
@@ -114,25 +112,19 @@ public abstract class ExecuteProduceConsume implements Runnable {
|
||||
if (this.activeThreadCount > this.greatestActiveThreadCount)
|
||||
this.greatestActiveThreadCount = this.activeThreadCount;
|
||||
|
||||
if (this.isLoggerTraceEnabled) {
|
||||
this.logger.trace(() -> String.format("[%d] started, hasThreadPending was: %b, activeThreadCount now: %d",
|
||||
Thread.currentThread().getId(), this.hasThreadPending, this.activeThreadCount));
|
||||
}
|
||||
this.logger.trace(() -> String.format("[%d] started, hasThreadPending was: %b, activeThreadCount now: %d",
|
||||
Thread.currentThread().getId(), this.hasThreadPending, this.activeThreadCount));
|
||||
|
||||
// Defer clearing hasThreadPending to prevent unnecessary threads waiting to produce...
|
||||
wasThreadPending = this.hasThreadPending;
|
||||
}
|
||||
|
||||
try {
|
||||
// It's possible this might need to become a class instance private volatile
|
||||
boolean canBlock = false;
|
||||
|
||||
while (!Thread.currentThread().isInterrupted()) {
|
||||
Task task = null;
|
||||
String taskType;
|
||||
|
||||
if (this.isLoggerTraceEnabled) {
|
||||
this.logger.trace(() -> String.format("[%d] waiting to produce...", Thread.currentThread().getId()));
|
||||
}
|
||||
this.logger.trace(() -> String.format("[%d] waiting to produce...", Thread.currentThread().getId()));
|
||||
|
||||
synchronized (this) {
|
||||
if (wasThreadPending) {
|
||||
@@ -141,13 +133,13 @@ public abstract class ExecuteProduceConsume implements Runnable {
|
||||
wasThreadPending = false;
|
||||
}
|
||||
|
||||
final boolean lambdaCanIdle = canBlock;
|
||||
if (this.isLoggerTraceEnabled) {
|
||||
this.logger.trace(() -> String.format("[%d] producing, activeThreadCount: %d, consumerCount: %d, canBlock is %b...",
|
||||
Thread.currentThread().getId(), this.activeThreadCount, this.consumerCount, lambdaCanIdle));
|
||||
}
|
||||
// If we're the only non-consuming thread - producer can afford to block this round
|
||||
boolean canBlock = this.activeThreadCount - this.consumerCount <= 1;
|
||||
|
||||
final long beforeProduce = isLoggerTraceEnabled ? System.currentTimeMillis() : 0;
|
||||
this.logger.trace(() -> String.format("[%d] producing... [activeThreadCount: %d, consumerCount: %d, canBlock: %b]",
|
||||
Thread.currentThread().getId(), this.activeThreadCount, this.consumerCount, canBlock));
|
||||
|
||||
final long beforeProduce = this.logger.isDebugEnabled() ? System.currentTimeMillis() : 0;
|
||||
|
||||
try {
|
||||
task = produceTask(canBlock);
|
||||
@@ -158,31 +150,36 @@ public abstract class ExecuteProduceConsume implements Runnable {
|
||||
this.logger.warn(() -> String.format("[%d] exception while trying to produce task", Thread.currentThread().getId()), e);
|
||||
}
|
||||
|
||||
if (this.isLoggerTraceEnabled) {
|
||||
this.logger.trace(() -> String.format("[%d] producing took %dms", Thread.currentThread().getId(), System.currentTimeMillis() - beforeProduce));
|
||||
if (this.logger.isDebugEnabled()) {
|
||||
final long productionPeriod = System.currentTimeMillis() - beforeProduce;
|
||||
taskType = task == null ? "no task" : task.getName();
|
||||
|
||||
this.logger.debug(() -> String.format("[%d] produced [%s] in %dms [canBlock: %b]",
|
||||
Thread.currentThread().getId(),
|
||||
taskType,
|
||||
productionPeriod,
|
||||
canBlock
|
||||
));
|
||||
} else {
|
||||
taskType = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (task == null)
|
||||
synchronized (this) {
|
||||
if (this.isLoggerTraceEnabled) {
|
||||
this.logger.trace(() -> String.format("[%d] no task, activeThreadCount: %d, consumerCount: %d",
|
||||
Thread.currentThread().getId(), this.activeThreadCount, this.consumerCount));
|
||||
}
|
||||
this.logger.trace(() -> String.format("[%d] no task, activeThreadCount: %d, consumerCount: %d",
|
||||
Thread.currentThread().getId(), this.activeThreadCount, this.consumerCount));
|
||||
|
||||
if (this.activeThreadCount > this.consumerCount + 1) {
|
||||
// If we have an excess of non-consuming threads then we can exit
|
||||
if (this.activeThreadCount - this.consumerCount > 1) {
|
||||
--this.activeThreadCount;
|
||||
if (this.isLoggerTraceEnabled) {
|
||||
this.logger.trace(() -> String.format("[%d] ending, activeThreadCount now: %d",
|
||||
Thread.currentThread().getId(), this.activeThreadCount));
|
||||
}
|
||||
|
||||
this.logger.trace(() -> String.format("[%d] ending, activeThreadCount now: %d",
|
||||
Thread.currentThread().getId(), this.activeThreadCount));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// We're the last surviving thread - producer can afford to block next round
|
||||
canBlock = true;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -192,16 +189,13 @@ public abstract class ExecuteProduceConsume implements Runnable {
|
||||
++this.tasksProduced;
|
||||
++this.consumerCount;
|
||||
|
||||
if (this.isLoggerTraceEnabled) {
|
||||
this.logger.trace(() -> String.format("[%d] hasThreadPending: %b, activeThreadCount: %d, consumerCount now: %d",
|
||||
Thread.currentThread().getId(), this.hasThreadPending, this.activeThreadCount, this.consumerCount));
|
||||
}
|
||||
this.logger.trace(() -> String.format("[%d] hasThreadPending: %b, activeThreadCount: %d, consumerCount now: %d",
|
||||
Thread.currentThread().getId(), this.hasThreadPending, this.activeThreadCount, this.consumerCount));
|
||||
|
||||
// If we have no thread pending and no excess of threads then we should spawn a fresh thread
|
||||
if (!this.hasThreadPending && this.activeThreadCount <= this.consumerCount + 1) {
|
||||
if (this.isLoggerTraceEnabled) {
|
||||
this.logger.trace(() -> String.format("[%d] spawning another thread", Thread.currentThread().getId()));
|
||||
}
|
||||
if (!this.hasThreadPending && this.activeThreadCount == this.consumerCount) {
|
||||
this.logger.trace(() -> String.format("[%d] spawning another thread", Thread.currentThread().getId()));
|
||||
|
||||
this.hasThreadPending = true;
|
||||
|
||||
try {
|
||||
@@ -209,21 +203,19 @@ public abstract class ExecuteProduceConsume implements Runnable {
|
||||
} catch (RejectedExecutionException e) {
|
||||
++this.spawnFailures;
|
||||
this.hasThreadPending = false;
|
||||
if (this.isLoggerTraceEnabled) {
|
||||
this.logger.trace(() -> String.format("[%d] failed to spawn another thread", Thread.currentThread().getId()));
|
||||
}
|
||||
|
||||
this.logger.trace(() -> String.format("[%d] failed to spawn another thread", Thread.currentThread().getId()));
|
||||
|
||||
this.onSpawnFailure();
|
||||
}
|
||||
} else {
|
||||
if (this.isLoggerTraceEnabled) {
|
||||
this.logger.trace(() -> String.format("[%d] NOT spawning another thread", Thread.currentThread().getId()));
|
||||
}
|
||||
this.logger.trace(() -> String.format("[%d] NOT spawning another thread", Thread.currentThread().getId()));
|
||||
}
|
||||
}
|
||||
|
||||
if (this.isLoggerTraceEnabled) {
|
||||
this.logger.trace(() -> String.format("[%d] performing task...", Thread.currentThread().getId()));
|
||||
}
|
||||
this.logger.trace(() -> String.format("[%d] consuming [%s] task...", Thread.currentThread().getId(), taskType));
|
||||
|
||||
final long beforePerform = this.logger.isDebugEnabled() ? System.currentTimeMillis() : 0;
|
||||
|
||||
try {
|
||||
task.perform(); // This can block for a while
|
||||
@@ -231,29 +223,25 @@ public abstract class ExecuteProduceConsume implements Runnable {
|
||||
// We're in shutdown situation so exit
|
||||
Thread.currentThread().interrupt();
|
||||
} catch (Exception e) {
|
||||
this.logger.warn(() -> String.format("[%d] exception while performing task", Thread.currentThread().getId()), e);
|
||||
this.logger.warn(() -> String.format("[%d] exception while consuming task", Thread.currentThread().getId()), e);
|
||||
}
|
||||
|
||||
if (this.isLoggerTraceEnabled) {
|
||||
this.logger.trace(() -> String.format("[%d] finished task", Thread.currentThread().getId()));
|
||||
if (this.logger.isDebugEnabled()) {
|
||||
final long productionPeriod = System.currentTimeMillis() - beforePerform;
|
||||
|
||||
this.logger.debug(() -> String.format("[%d] consumed [%s] task in %dms", Thread.currentThread().getId(), taskType, productionPeriod));
|
||||
}
|
||||
|
||||
synchronized (this) {
|
||||
++this.tasksConsumed;
|
||||
--this.consumerCount;
|
||||
|
||||
if (this.isLoggerTraceEnabled) {
|
||||
this.logger.trace(() -> String.format("[%d] consumerCount now: %d",
|
||||
Thread.currentThread().getId(), this.consumerCount));
|
||||
}
|
||||
|
||||
// Quicker, non-blocking produce next round
|
||||
canBlock = false;
|
||||
this.logger.trace(() -> String.format("[%d] consumerCount now: %d",
|
||||
Thread.currentThread().getId(), this.consumerCount));
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
if (this.isLoggerTraceEnabled)
|
||||
Thread.currentThread().setName(this.className);
|
||||
Thread.currentThread().setName(this.className);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -18,6 +18,9 @@ import java.util.TreeMap;
|
||||
|
||||
import com.google.common.base.CharMatcher;
|
||||
|
||||
import com.ibm.icu.text.CaseMap;
|
||||
import com.ibm.icu.text.Normalizer2;
|
||||
import com.ibm.icu.text.UnicodeSet;
|
||||
import net.codebox.homoglyph.HomoglyphBuilder;
|
||||
|
||||
public abstract class Unicode {
|
||||
@@ -31,6 +34,8 @@ public abstract class Unicode {
|
||||
public static final String ZERO_WIDTH_NO_BREAK_SPACE = "\ufeff";
|
||||
|
||||
public static final CharMatcher ZERO_WIDTH_CHAR_MATCHER = CharMatcher.anyOf(ZERO_WIDTH_SPACE + ZERO_WIDTH_NON_JOINER + ZERO_WIDTH_JOINER + WORD_JOINER + ZERO_WIDTH_NO_BREAK_SPACE);
|
||||
private static final UnicodeSet removableUniset = new UnicodeSet("[[:Mark:][:Other:]]").freeze();
|
||||
|
||||
|
||||
private static int[] homoglyphCodePoints;
|
||||
private static int[] reducedCodePoints;
|
||||
@@ -59,7 +64,7 @@ public abstract class Unicode {
|
||||
public static String normalize(String input) {
|
||||
String output;
|
||||
|
||||
// Normalize
|
||||
// Normalize using NFKC to recompose in canonical form
|
||||
output = Normalizer.normalize(input, Form.NFKC);
|
||||
|
||||
// Remove zero-width code-points, used for rendering
|
||||
@@ -91,8 +96,8 @@ public abstract class Unicode {
|
||||
public static String sanitize(String input) {
|
||||
String output;
|
||||
|
||||
// Normalize
|
||||
output = Normalizer.normalize(input, Form.NFKD);
|
||||
// Normalize using NFKD to decompose into individual combining code points
|
||||
output = Normalizer2.getNFKDInstance().normalize(input);
|
||||
|
||||
// Remove zero-width code-points, used for rendering
|
||||
output = removeZeroWidth(output);
|
||||
@@ -100,11 +105,11 @@ public abstract class Unicode {
|
||||
// Normalize whitespace
|
||||
output = CharMatcher.whitespace().trimAndCollapseFrom(output, ' ');
|
||||
|
||||
// Remove accents, combining marks
|
||||
output = output.replaceAll("[\\p{M}\\p{C}]", "");
|
||||
// Remove accents, combining marks - see https://www.unicode.org/reports/tr44/#GC_Values_Table
|
||||
output = removableUniset.stripFrom(output, true);
|
||||
|
||||
// Convert to lowercase
|
||||
output = output.toLowerCase(Locale.ROOT);
|
||||
output = CaseMap.toLower().apply(Locale.ROOT, output);
|
||||
|
||||
// Reduce homoglyphs
|
||||
output = reduceHomoglyphs(output);
|
||||
|
@@ -4,8 +4,10 @@
|
||||
"maxBlockSize": 2097152,
|
||||
"maxBytesPerUnitFee": 1024,
|
||||
"unitFee": "0.001",
|
||||
"nameRegistrationUnitFee": "5",
|
||||
"nameRegistrationUnitFeeTimestamp": 1645372800000,
|
||||
"nameRegistrationUnitFees": [
|
||||
{ "timestamp": 1645372800000, "fee": "5" },
|
||||
{ "timestamp": 1651420800000, "fee": "1.25" }
|
||||
],
|
||||
"useBrokenMD160ForAddresses": false,
|
||||
"requireGroupForApproval": false,
|
||||
"defaultGroupId": 0,
|
||||
|
@@ -81,4 +81,3 @@ ORDER_SIZE_TOO_SMALL = order amount too low
|
||||
FILE_NOT_FOUND = Datei nicht gefunden
|
||||
|
||||
NO_REPLY = peer did not reply with data
|
||||
|
||||
|
@@ -68,7 +68,7 @@ ORDER_UNKNOWN = unknown asset order ID
|
||||
GROUP_UNKNOWN = group unknown
|
||||
|
||||
### Foreign Blockchain ###
|
||||
FOREIGN_BLOCKCHAIN_NETWORK_ISSUE = foreign blokchain or ElectrumX network issue
|
||||
FOREIGN_BLOCKCHAIN_NETWORK_ISSUE = foreign blockchain or ElectrumX network issue
|
||||
|
||||
FOREIGN_BLOCKCHAIN_BALANCE_ISSUE = insufficient balance on foreign blockchain
|
||||
|
||||
@@ -80,8 +80,4 @@ ORDER_SIZE_TOO_SMALL = order amount too low
|
||||
### Data ###
|
||||
FILE_NOT_FOUND = file not found
|
||||
|
||||
ORDER_SIZE_TOO_SMALL = order size too small
|
||||
|
||||
FILE_NOT_FOUND = file not found
|
||||
|
||||
NO_REPLY = peer didn't reply within the allowed time
|
||||
|
83
src/main/resources/i18n/ApiError_es.properties
Normal file
83
src/main/resources/i18n/ApiError_es.properties
Normal file
@@ -0,0 +1,83 @@
|
||||
#Generated by ResourceBundle Editor (http://essiembre.github.io/eclipse-rbe/)
|
||||
# Keys are from api.ApiError enum
|
||||
|
||||
# "localeLang": "es",
|
||||
|
||||
### Common ###
|
||||
JSON = no se pudo analizar el mensaje JSON
|
||||
|
||||
INSUFFICIENT_BALANCE = saldo insuficiente
|
||||
|
||||
UNAUTHORIZED = Llamada API no autorizada
|
||||
|
||||
REPOSITORY_ISSUE = error de repositorio
|
||||
|
||||
NON_PRODUCTION = esta llamada API no está permitida para sistemas de producción
|
||||
|
||||
BLOCKCHAIN_NEEDS_SYNC = blockchain necesita sincronizarse primero
|
||||
|
||||
NO_TIME_SYNC = aún no hay sincronización de reloj
|
||||
|
||||
### Validation ###
|
||||
INVALID_SIGNATURE = firma no válida
|
||||
|
||||
INVALID_ADDRESS = dirección no válida
|
||||
|
||||
INVALID_PUBLIC_KEY = clave pública no válida
|
||||
|
||||
INVALID_DATA = datos no válidos
|
||||
|
||||
INVALID_NETWORK_ADDRESS = dirección de red no válida
|
||||
|
||||
ADDRESS_UNKNOWN = dirección de cuenta desconocida
|
||||
|
||||
INVALID_CRITERIA = criterio de búsqueda no válido
|
||||
|
||||
INVALID_REFERENCE = referencia no válida
|
||||
|
||||
TRANSFORMATION_ERROR = no se pudo transformar JSON en transacción
|
||||
|
||||
INVALID_PRIVATE_KEY = clave privada no válida
|
||||
|
||||
INVALID_HEIGHT = altura de bloque no válida
|
||||
|
||||
CANNOT_MINT = la cuenta no puede acuñar
|
||||
|
||||
### Blocks ###
|
||||
BLOCK_UNKNOWN = bloque desconocido
|
||||
|
||||
### Transactions ###
|
||||
TRANSACTION_UNKNOWN = transacción desconocida
|
||||
|
||||
PUBLIC_KEY_NOT_FOUND = clave pública no encontrada
|
||||
|
||||
# this one is special in that caller expected to pass two additional strings, hence the two %s
|
||||
TRANSACTION_INVALID = transacción no válida: %s (%s)
|
||||
|
||||
### Naming ###
|
||||
NAME_UNKNOWN = nombre desconocido
|
||||
|
||||
### Asset ###
|
||||
INVALID_ASSET_ID = ID de recurso no válido
|
||||
|
||||
INVALID_ORDER_ID = ID de pedido de activo no válido
|
||||
|
||||
ORDER_UNKNOWN = ID de pedido de activo desconocido
|
||||
|
||||
### Groups ###
|
||||
GROUP_UNKNOWN = grupo desconocido
|
||||
|
||||
### Foreign Blockchain ###
|
||||
FOREIGN_BLOCKCHAIN_NETWORK_ISSUE = problema de cadena de bloques extranjera o red ElectrumX
|
||||
|
||||
FOREIGN_BLOCKCHAIN_BALANCE_ISSUE = saldo insuficiente en blockchain extranjera
|
||||
|
||||
FOREIGN_BLOCKCHAIN_TOO_SOON = demasiado pronto para transmitir transacciones de blockchain extranjeras (LockTime/mediana de tiempo de bloqueo)
|
||||
|
||||
### Trade Portal ###
|
||||
ORDER_SIZE_TOO_SMALL = importe del pedido demasiado bajo
|
||||
|
||||
### Data ###
|
||||
FILE_NOT_FOUND = archivo no encontrado
|
||||
|
||||
NO_REPLY = el compañero no respondió dentro del tiempo permitido
|
@@ -1,10 +1,7 @@
|
||||
#Generated by ResourceBundle Editor (http://essiembre.github.io/eclipse-rbe/)
|
||||
# Keys are from api.ApiError enum
|
||||
|
||||
# Kielen muuttaminen suomeksi tapahtuu settings.json-tiedostossa
|
||||
#
|
||||
# "localeLang": "fi",
|
||||
# muista pilkku lopussa jos komento ei ole viimeisellä rivillä
|
||||
|
||||
### Common ###
|
||||
JSON = JSON-viestin jaottelu epäonnistui
|
||||
@@ -83,4 +80,4 @@ ORDER_SIZE_TOO_SMALL = order amount too low
|
||||
### Data ###
|
||||
FILE_NOT_FOUND = file not found
|
||||
|
||||
NO_REPLY = peer did not reply with data
|
||||
NO_REPLY = peer did not reply with data
|
||||
|
@@ -1,24 +1,46 @@
|
||||
### Commun ###
|
||||
#Generated by ResourceBundle Editor (http://essiembre.github.io/eclipse-rbe/)
|
||||
# Keys are from api.ApiError enum
|
||||
|
||||
# "localeLang": "fr",
|
||||
|
||||
### Commun ###
|
||||
JSON = échec de l'analyse du message JSON
|
||||
|
||||
INSUFFICIENT_BALANCE = balance insuffisante
|
||||
|
||||
UNAUTHORIZED = appel de l’API non autorisé
|
||||
|
||||
REPOSITORY_ISSUE = erreur de dépôt
|
||||
|
||||
NON_PRODUCTION = cet appel API n'est pas autorisé pour les systèmes en production
|
||||
|
||||
BLOCKCHAIN_NEEDS_SYNC = la blockchain doit d'abord être synchronisée
|
||||
|
||||
NO_TIME_SYNC = heure pas encore synchronisée
|
||||
|
||||
### Validation ###
|
||||
INVALID_SIGNATURE = signature invalide
|
||||
|
||||
INVALID_ADDRESS = adresse invalide
|
||||
|
||||
INVALID_PUBLIC_KEY = clé publique invalide
|
||||
|
||||
INVALID_DATA = données invalides
|
||||
|
||||
INVALID_NETWORK_ADDRESS = adresse réseau invalide
|
||||
|
||||
ADDRESS_UNKNOWN = adresse de compte inconnue
|
||||
|
||||
INVALID_CRITERIA = critère de recherche invalide
|
||||
|
||||
INVALID_REFERENCE = référence invalide
|
||||
|
||||
TRANSFORMATION_ERROR = ne peut pas transformer JSON en transaction
|
||||
|
||||
INVALID_PRIVATE_KEY = clé privée invalide
|
||||
|
||||
INVALID_HEIGHT = hauteur de bloc invalide
|
||||
|
||||
CANNOT_MINT = le compte ne peut pas mint
|
||||
|
||||
### Blocks ###
|
||||
@@ -26,6 +48,7 @@ BLOCK_UNKNOWN = bloc inconnu
|
||||
|
||||
### Transactions ###
|
||||
TRANSACTION_UNKNOWN = opération inconnue
|
||||
|
||||
PUBLIC_KEY_NOT_FOUND = clé publique introuvable
|
||||
|
||||
# celui-ci est spécial dans le sens où l'appelant doit passer deux chaînes supplémentaires, d'où les deux %s
|
||||
@@ -36,7 +59,9 @@ NAME_UNKNOWN = nom inconnu
|
||||
|
||||
### Asset ###
|
||||
INVALID_ASSET_ID = identifiant d'actif invalide
|
||||
|
||||
INVALID_ORDER_ID = identifiant de commande d'actif non valide
|
||||
|
||||
ORDER_UNKNOWN = identifiant d'ordre d'actif inconnu
|
||||
|
||||
### Groupes ###
|
||||
@@ -44,7 +69,9 @@ GROUP_UNKNOWN = groupe inconnu
|
||||
|
||||
### Blockchain étrangère ###
|
||||
FOREIGN_BLOCKCHAIN_NETWORK_ISSUE = Problème blokchain étrangère ou de réseau ElectrumX
|
||||
|
||||
FOREIGN_BLOCKCHAIN_BALANCE_ISSUE = solde insuffisant sur la blockchain étrangère
|
||||
|
||||
FOREIGN_BLOCKCHAIN_TOO_SOON = trop tôt pour diffuser la transaction sur la blockchain étrangère (temps de verrouillage/temps de bloc médian)
|
||||
|
||||
### Portail de trading ###
|
||||
@@ -52,4 +79,5 @@ ORDER_SIZE_TOO_SMALL = montant de commande trop bas
|
||||
|
||||
### Données ###
|
||||
FILE_NOT_FOUND = fichier introuvable
|
||||
NO_REPLY = le pair n'a pas renvoyé de données
|
||||
|
||||
NO_REPLY = le pair n'a pas renvoyé de données
|
||||
|
@@ -1,8 +1,5 @@
|
||||
#Generated by ResourceBundle Editor (http://essiembre.github.io/eclipse-rbe/)
|
||||
# Keys are from api.ApiError enum
|
||||
|
||||
# Magyar myelvre forditotta: Szkíta (Scythian). 2021 Augusztus 7.
|
||||
# Az alkalmazás nyelvének magyarra való változtatása a settings.json oldalon történik.
|
||||
# Keys are from api.ApiError enum # Magyar nyelvre forditotta: Szkíta (Scythian). 2021 Augusztus 7.
|
||||
|
||||
# "localeLang": "hu",
|
||||
|
||||
@@ -15,7 +12,7 @@ UNAUTHORIZED = nem engedélyezett API-hívás
|
||||
|
||||
REPOSITORY_ISSUE = adattári hiba
|
||||
|
||||
NON_PRODUCTION = ez az API-hívás nem engedélyezett korlátozott rendszereken
|
||||
NON_PRODUCTION = ez az API-hívás nem engedélyezett éles rendszereken
|
||||
|
||||
BLOCKCHAIN_NEEDS_SYNC = a blokkláncnak még szinkronizálnia kell
|
||||
|
||||
@@ -24,15 +21,15 @@ NO_TIME_SYNC = az óraszinkronizálás még nem történt meg
|
||||
### Validation ###
|
||||
INVALID_SIGNATURE = érvénytelen aláírás
|
||||
|
||||
INVALID_ADDRESS = érvénytelen fiók cím
|
||||
INVALID_ADDRESS = érvénytelen fiókcím
|
||||
|
||||
INVALID_PUBLIC_KEY = érvénytelen nyilvános kulcs
|
||||
|
||||
INVALID_DATA = érvénytelen adat
|
||||
|
||||
INVALID_NETWORK_ADDRESS = érvénytelen hálózat cím
|
||||
INVALID_NETWORK_ADDRESS = érvénytelen hálózatcím
|
||||
|
||||
ADDRESS_UNKNOWN = ismeretlen fiók cím
|
||||
ADDRESS_UNKNOWN = ismeretlen fiókcím
|
||||
|
||||
INVALID_CRITERIA = érvénytelen keresési feltétel
|
||||
|
||||
@@ -83,4 +80,4 @@ ORDER_SIZE_TOO_SMALL = rendelési összeg túl alacsony
|
||||
### Data ###
|
||||
FILE_NOT_FOUND = fájl nem található
|
||||
|
||||
NO_REPLY = a másik csomópont nem válaszolt
|
||||
NO_REPLY = a másik csomópont nem válaszolt
|
||||
|
@@ -1,26 +1,22 @@
|
||||
#Generated by ResourceBundle Editor (http://essiembre.github.io/eclipse-rbe/)
|
||||
# Keys are from api.ApiError enum
|
||||
# Italian translation by Pabs 2021
|
||||
# Keys are from api.ApiError enum # Italian translation by Pabs 2021
|
||||
|
||||
# La modifica della lingua dell'UI è fatta nel file Settings.json
|
||||
#
|
||||
# "localeLang": "it",
|
||||
# Si prega ricordare la virgola alla fine, se questo comando non è sull'ultima riga
|
||||
|
||||
### Common ###
|
||||
JSON = Impossibile analizzare il messaggio JSON
|
||||
|
||||
INSUFFICIENT_BALANCE = insufficient balance
|
||||
INSUFFICIENT_BALANCE = bilancio insufficiente
|
||||
|
||||
UNAUTHORIZED = Chiamata API non autorizzata
|
||||
|
||||
REPOSITORY_ISSUE = errore del repositorio
|
||||
REPOSITORY_ISSUE = errore del repository
|
||||
|
||||
NON_PRODUCTION = questa chiamata API non è consentita per i sistemi di produzione
|
||||
|
||||
BLOCKCHAIN_NEEDS_SYNC = blockchain deve prima sincronizzarsi
|
||||
BLOCKCHAIN_NEEDS_SYNC = la blockchain deve sincronizzarsi
|
||||
|
||||
NO_TIME_SYNC = nessuna sincronizzazione dell'orologio ancora
|
||||
NO_TIME_SYNC = nessuna sincronizzazione
|
||||
|
||||
### Validation ###
|
||||
INVALID_SIGNATURE = firma non valida
|
||||
@@ -39,7 +35,7 @@ INVALID_CRITERIA = criteri di ricerca non validi
|
||||
|
||||
INVALID_REFERENCE = riferimento non valido
|
||||
|
||||
TRANSFORMATION_ERROR = non è stato possibile trasformare JSON in transazione
|
||||
TRANSFORMATION_ERROR = non è stato possibile trasformare il JSON
|
||||
|
||||
INVALID_PRIVATE_KEY = chiave privata non valida
|
||||
|
||||
@@ -62,26 +58,26 @@ TRANSACTION_INVALID = transazione non valida: %s (%s)
|
||||
NAME_UNKNOWN = nome sconosciuto
|
||||
|
||||
### Asset ###
|
||||
INVALID_ASSET_ID = identificazione risorsa non valida
|
||||
INVALID_ASSET_ID = risorsa non valida
|
||||
|
||||
INVALID_ORDER_ID = identificazione di ordine di risorsa non valida
|
||||
INVALID_ORDER_ID = ordine di risorsa non valida
|
||||
|
||||
ORDER_UNKNOWN = identificazione di ordine di risorsa sconosciuta
|
||||
ORDER_UNKNOWN = ordine di risorsa sconosciuta
|
||||
|
||||
### Groups ###
|
||||
GROUP_UNKNOWN = gruppo sconosciuto
|
||||
|
||||
### Foreign Blockchain ###
|
||||
FOREIGN_BLOCKCHAIN_NETWORK_ISSUE = foreign blokchain or ElectrumX network issue
|
||||
FOREIGN_BLOCKCHAIN_NETWORK_ISSUE = problema nella blockchain esterna o nella rete ElectrumX
|
||||
|
||||
FOREIGN_BLOCKCHAIN_BALANCE_ISSUE = insufficient balance on foreign blockchain
|
||||
FOREIGN_BLOCKCHAIN_BALANCE_ISSUE = bilancio insufficiente nella blockchain esterna
|
||||
|
||||
FOREIGN_BLOCKCHAIN_TOO_SOON = too soon to broadcast foreign blockchain transaction (LockTime/median block time)
|
||||
FOREIGN_BLOCKCHAIN_TOO_SOON = troppo presto per distribuire la transazione (sospensione LockTime/median)
|
||||
|
||||
### Trade Portal ###
|
||||
ORDER_SIZE_TOO_SMALL = order amount too low
|
||||
ORDER_SIZE_TOO_SMALL = quantità d'ordine troppo bassa
|
||||
|
||||
### Data ###
|
||||
FILE_NOT_FOUND = file not found
|
||||
|
||||
NO_REPLY = peer did not reply with data
|
||||
NO_REPLY = il peer non ha fornito dati
|
||||
|
@@ -41,7 +41,7 @@ INVALID_PRIVATE_KEY = ongeldige private key
|
||||
|
||||
INVALID_HEIGHT = ongeldige blokhoogte
|
||||
|
||||
CANNOT_MINT = account kan niet munten
|
||||
CANNOT_MINT = account kan niet minten
|
||||
|
||||
### Blocks ###
|
||||
BLOCK_UNKNOWN = blok onbekend
|
||||
@@ -68,16 +68,16 @@ ORDER_UNKNOWN = onbekende asset order ID
|
||||
GROUP_UNKNOWN = onbekende groep
|
||||
|
||||
### Foreign Blockchain ###
|
||||
FOREIGN_BLOCKCHAIN_NETWORK_ISSUE = foreign blokchain or ElectrumX network issue
|
||||
FOREIGN_BLOCKCHAIN_NETWORK_ISSUE = blockchain of ElectrumX network probleem
|
||||
|
||||
FOREIGN_BLOCKCHAIN_BALANCE_ISSUE = insufficient balance on foreign blockchain
|
||||
FOREIGN_BLOCKCHAIN_BALANCE_ISSUE = onvoldoende saldo blockchain
|
||||
|
||||
FOREIGN_BLOCKCHAIN_TOO_SOON = too soon to broadcast foreign blockchain transaction (LockTime/median block time)
|
||||
FOREIGN_BLOCKCHAIN_TOO_SOON = nog niet gereed om de blockchain transactie uittevoeren (LockTime/median block time)
|
||||
|
||||
### Trade Portal ###
|
||||
ORDER_SIZE_TOO_SMALL = order amount too low
|
||||
ORDER_SIZE_TOO_SMALL = order bedrag te laag
|
||||
|
||||
### Data ###
|
||||
FILE_NOT_FOUND = file not found
|
||||
FILE_NOT_FOUND = file niet gevonden
|
||||
|
||||
NO_REPLY = peer did not reply with data
|
||||
NO_REPLY = peer reageerd niet met data
|
||||
|
@@ -1,83 +1,83 @@
|
||||
#Generated by ResourceBundle Editor (http://essiembre.github.io/eclipse-rbe/)
|
||||
# Keys are from api.ApiError enum
|
||||
|
||||
# "localeLang": "ru",
|
||||
|
||||
### Common ###
|
||||
JSON = не удалось разобрать сообщение json
|
||||
|
||||
INSUFFICIENT_BALANCE = insufficient balance
|
||||
|
||||
UNAUTHORIZED = вызов API не авторизован
|
||||
|
||||
REPOSITORY_ISSUE = ошибка репозитория
|
||||
|
||||
NON_PRODUCTION = этот вызов API не разрешен для производственных систем
|
||||
|
||||
BLOCKCHAIN_NEEDS_SYNC = блокчейн должен сначала синхронизироваться
|
||||
|
||||
NO_TIME_SYNC = no clock synchronization yet
|
||||
|
||||
### Validation ###
|
||||
INVALID_SIGNATURE = недействительная подпись
|
||||
|
||||
INVALID_ADDRESS = неизвестный адрес
|
||||
|
||||
INVALID_PUBLIC_KEY = недействительный открытый ключ
|
||||
|
||||
INVALID_DATA = неверные данные
|
||||
|
||||
INVALID_NETWORK_ADDRESS = неверный сетевой адрес
|
||||
|
||||
ADDRESS_UNKNOWN = неизвестная учетная запись
|
||||
|
||||
INVALID_CRITERIA = неверные критерии поиска
|
||||
|
||||
INVALID_REFERENCE = неверная ссылка
|
||||
|
||||
TRANSFORMATION_ERROR = не удалось преобразовать JSON в транзакцию
|
||||
|
||||
INVALID_PRIVATE_KEY = неверный приватный ключ
|
||||
|
||||
INVALID_HEIGHT = недопустимая высота блока
|
||||
|
||||
CANNOT_MINT = аккаунт не может чеканить
|
||||
|
||||
### Blocks ###
|
||||
BLOCK_UNKNOWN = неизвестный блок
|
||||
|
||||
### Transactions ###
|
||||
TRANSACTION_UNKNOWN = транзакция неизвестна
|
||||
|
||||
PUBLIC_KEY_NOT_FOUND = открытый ключ не найден
|
||||
|
||||
# this one is special in that caller expected to pass two additional strings, hence the two %s
|
||||
TRANSACTION_INVALID = транзакция недействительна: %s (%s)
|
||||
|
||||
### Naming ###
|
||||
NAME_UNKNOWN = имя неизвестно
|
||||
|
||||
### Asset ###
|
||||
INVALID_ASSET_ID = неверный идентификатор актива
|
||||
|
||||
INVALID_ORDER_ID = неверный идентификатор заказа актива
|
||||
|
||||
ORDER_UNKNOWN = неизвестный идентификатор заказа актива
|
||||
|
||||
### Groups ###
|
||||
GROUP_UNKNOWN = неизвестная группа
|
||||
|
||||
### Foreign Blockchain ###
|
||||
FOREIGN_BLOCKCHAIN_NETWORK_ISSUE = foreign blokchain or ElectrumX network issue
|
||||
|
||||
FOREIGN_BLOCKCHAIN_BALANCE_ISSUE = insufficient balance on foreign blockchain
|
||||
|
||||
FOREIGN_BLOCKCHAIN_TOO_SOON = too soon to broadcast foreign blockchain transaction (LockTime/median block time)
|
||||
|
||||
### Trade Portal ###
|
||||
ORDER_SIZE_TOO_SMALL = order amount too low
|
||||
|
||||
### Data ###
|
||||
FILE_NOT_FOUND = file not found
|
||||
|
||||
NO_REPLY = peer did not reply with data
|
||||
#Generated by ResourceBundle Editor (http://essiembre.github.io/eclipse-rbe/)
|
||||
# Keys are from api.ApiError enum
|
||||
|
||||
# "localeLang": "ru",
|
||||
|
||||
### Common ###
|
||||
JSON = не удалось разобрать сообщение json
|
||||
|
||||
INSUFFICIENT_BALANCE = недостаточный баланс
|
||||
|
||||
UNAUTHORIZED = вызов API не авторизован
|
||||
|
||||
REPOSITORY_ISSUE = ошибка репозитория
|
||||
|
||||
NON_PRODUCTION = этот вызов API не разрешен для производственных систем
|
||||
|
||||
BLOCKCHAIN_NEEDS_SYNC = блокчейн должен сначала синхронизироваться
|
||||
|
||||
NO_TIME_SYNC = пока нет синхронизации часов
|
||||
|
||||
### Validation ###
|
||||
INVALID_SIGNATURE = недействительная подпись
|
||||
|
||||
INVALID_ADDRESS = неизвестный адрес
|
||||
|
||||
INVALID_PUBLIC_KEY = недействительный открытый ключ
|
||||
|
||||
INVALID_DATA = неверные данные
|
||||
|
||||
INVALID_NETWORK_ADDRESS = неверный адрес сети
|
||||
|
||||
ADDRESS_UNKNOWN = неизвестная учетная запись
|
||||
|
||||
INVALID_CRITERIA = неверные критерии поиска
|
||||
|
||||
INVALID_REFERENCE = неверная ссылка
|
||||
|
||||
TRANSFORMATION_ERROR = не удалось преобразовать JSON в транзакцию
|
||||
|
||||
INVALID_PRIVATE_KEY = неверный приватный ключ
|
||||
|
||||
INVALID_HEIGHT = недопустимая высота блока
|
||||
|
||||
CANNOT_MINT = аккаунт не может чеканить
|
||||
|
||||
### Blocks ###
|
||||
BLOCK_UNKNOWN = неизвестный блок
|
||||
|
||||
### Transactions ###
|
||||
TRANSACTION_UNKNOWN = транзакция неизвестна
|
||||
|
||||
PUBLIC_KEY_NOT_FOUND = открытый ключ не найден
|
||||
|
||||
# this one is special in that caller expected to pass two additional strings, hence the two %s
|
||||
TRANSACTION_INVALID = транзакция недействительна: %s (%s)
|
||||
|
||||
### Naming ###
|
||||
NAME_UNKNOWN = имя неизвестно
|
||||
|
||||
### Asset ###
|
||||
INVALID_ASSET_ID = неверный идентификатор актива
|
||||
|
||||
INVALID_ORDER_ID = неверный идентификатор заказа актива
|
||||
|
||||
ORDER_UNKNOWN = неизвестный идентификатор заказа актива
|
||||
|
||||
### Groups ###
|
||||
GROUP_UNKNOWN = неизвестная группа
|
||||
|
||||
### Foreign Blockchain ###
|
||||
FOREIGN_BLOCKCHAIN_NETWORK_ISSUE = проблема с внешним блокчейном или сетью ElectrumX
|
||||
|
||||
FOREIGN_BLOCKCHAIN_BALANCE_ISSUE = недостаточный баланс на внешнем блокчейне
|
||||
|
||||
FOREIGN_BLOCKCHAIN_TOO_SOON = слишком рано для трансляции транзакции во внений блокчей (время блокировки/среднее время блока)
|
||||
|
||||
### Trade Portal ###
|
||||
ORDER_SIZE_TOO_SMALL = слишком маленькая сумма ордера
|
||||
|
||||
### Data ###
|
||||
FILE_NOT_FOUND = файл не найден
|
||||
|
||||
NO_REPLY = узел не ответил данными
|
||||
|
83
src/main/resources/i18n/ApiError_sv.properties
Normal file
83
src/main/resources/i18n/ApiError_sv.properties
Normal file
@@ -0,0 +1,83 @@
|
||||
#Generated by ResourceBundle Editor (http://essiembre.github.io/eclipse-rbe/)
|
||||
# Keys are from api.ApiError enum
|
||||
|
||||
# "localeLang": "sv",
|
||||
|
||||
### Common ###
|
||||
JSON = misslyckades att tolka JSON meddelande
|
||||
|
||||
INSUFFICIENT_BALANCE = otillräcklig balans
|
||||
|
||||
UNAUTHORIZED = API obehörigt anrop
|
||||
|
||||
REPOSITORY_ISSUE = fel i lagret
|
||||
|
||||
NON_PRODUCTION = detta API-anrop är inte tillåtet för produktionssystem
|
||||
|
||||
BLOCKCHAIN_NEEDS_SYNC = blockchain måste synkroniseras först
|
||||
|
||||
NO_TIME_SYNC = ingen klocksynkronisering ännu
|
||||
|
||||
### Validation ###
|
||||
INVALID_SIGNATURE = ogiltig signatur
|
||||
|
||||
INVALID_ADDRESS = ogiltig adress
|
||||
|
||||
INVALID_PUBLIC_KEY = ogiltig offentlig nyckel
|
||||
|
||||
INVALID_DATA = ogiltig data
|
||||
|
||||
INVALID_NETWORK_ADDRESS = ogiltig nätverksadress
|
||||
|
||||
ADDRESS_UNKNOWN = okänd kontoadress
|
||||
|
||||
INVALID_CRITERIA = ogiltiga sökkriterier
|
||||
|
||||
INVALID_REFERENCE = ogiltig referens
|
||||
|
||||
TRANSFORMATION_ERROR = kunde inte omvandla JSON till en transaktion
|
||||
|
||||
INVALID_PRIVATE_KEY = ogiltig privat nyckel
|
||||
|
||||
INVALID_HEIGHT = ogiltig blockhöjd
|
||||
|
||||
CANNOT_MINT = konto kan inte prägla QORT
|
||||
|
||||
### Blocks ###
|
||||
BLOCK_UNKNOWN = okänt block
|
||||
|
||||
### Transactions ###
|
||||
TRANSACTION_UNKNOWN = okänd transaktion
|
||||
|
||||
PUBLIC_KEY_NOT_FOUND = hittade inte en offentlig nyckel
|
||||
|
||||
# this one is special in that caller expected to pass two additional strings, hence the two %s
|
||||
TRANSACTION_INVALID = ogiltig transaktion: %s (%s)
|
||||
|
||||
### Naming ###
|
||||
NAME_UNKNOWN = okänt namn
|
||||
|
||||
### Asset ###
|
||||
INVALID_ASSET_ID = ogiltigt tillgångs-ID
|
||||
|
||||
INVALID_ORDER_ID = ogiltigt tillgångsbeställnings-ID
|
||||
|
||||
ORDER_UNKNOWN = okänt tillgångsbeställnings-ID
|
||||
|
||||
### Groups ###
|
||||
GROUP_UNKNOWN = okänd grupp
|
||||
|
||||
### Foreign Blockchain ###
|
||||
FOREIGN_BLOCKCHAIN_NETWORK_ISSUE = utländsk blockchain eller ElectrumX nätverksproblem
|
||||
|
||||
FOREIGN_BLOCKCHAIN_BALANCE_ISSUE = otillräcklig balans på utländsk blockchain
|
||||
|
||||
FOREIGN_BLOCKCHAIN_TOO_SOON = för tidigt för att sända utländsk blockkedjetransaktion (LockTime/medianblocktid)
|
||||
|
||||
### Trade Portal ###
|
||||
ORDER_SIZE_TOO_SMALL = beställningssumman för låg
|
||||
|
||||
### Data ###
|
||||
FILE_NOT_FOUND = filen hittades inte
|
||||
|
||||
NO_REPLY = noden svarade inte inom den tillåtna tiden
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user