Compare commits

...

29 Commits

Author SHA1 Message Date
CalDescent
5a1a814003 Bump version to 2.1.3-mempow.0 2021-12-31 20:53:04 +00:00
CalDescent
e59abe1b14 Include hashrate in logs for easy comparison between different hardware specs 2021-12-31 20:52:50 +00:00
CalDescent
4f8721e629 Fixed merge issue 2021-12-31 20:52:24 +00:00
CalDescent
59ffb65b8c Merge branch 'master' into online-accounts-rework
# Conflicts:
#	pom.xml
2021-12-31 20:25:23 +00:00
QuickMythril
cf603aa80e disable Open UI menu item
this doesn't work and should either be fixed or removed completely.
2021-12-25 14:23:49 -05:00
CalDescent
35a5dc6219 Updated AdvancedInstaller project for v2.1.3 2021-12-19 22:53:04 +00:00
CalDescent
ace3ca0ad9 Bump version to 2.1.3 2021-12-19 21:10:24 +00:00
CalDescent
a8a498ddea Disable the names integrity check on startup by default.
This isn't really needed and is risky leaving it enabled, as there may be other unsupported cases.
2021-12-19 20:54:19 +00:00
CalDescent
d16663f0a9 Handle missing case in names integrity check caused by an UPDATE_NAME transaction with a blank "newName" value.
This is a valid transaction but not one that the integrity check was handling properly. Should fix NullPointerException on node startup.
2021-12-19 20:23:57 +00:00
QuickMythril
9ce748452d Merge pull request #64 from Qortal/protoniuman-FR-patch-1
Protoniuman fr patch 1
2021-12-19 05:55:37 -05:00
Proto
9263d74b75 Update TransactionValidity_fr.properties 2021-12-19 11:16:20 +01:00
Proto
9601bddc84 Update SysTray_fr.properties 2021-12-19 11:05:34 +01:00
Proto
e281e19052 Add files via upload 2021-12-19 11:02:00 +01:00
Proto
0238b78f45 Update ApiError_fr.properties 2021-12-19 10:57:57 +01:00
QuickMythril
0ccee4326d Added French translations
credit: stchoupi
2021-12-18 15:57:49 -05:00
CalDescent
391c3fe4c9 Added same functionality to GET /blocks/signature/{signature}
Also renamed query string parameter to "includeOnlineSignatures" to make it clearer.
2021-12-15 16:37:59 +00:00
CalDescent
3a7da9f13b Don't return online accounts signatures from GET /blocks/byheight/{height} unless requested using the includesignatures=true query string parameter.
This should fix issue where it would take up to 30 seconds to return for a recent block, and would consume masses of CPU due to having to base58 encode the online accounts signatures. Base58 is very slow and made this API endpoint almost unusable for recent blocks, due to them having untrimmed online accounts signatures.
2021-12-15 16:33:08 +00:00
CalDescent
e7fd803d19 Updated AdvancedInstaller project for v2.1.2 2021-12-11 22:16:43 +00:00
CalDescent
7c5c010eeb Bump version to 2.1.0-PoW.0 2021-11-27 23:08:08 +00:00
CalDescent
250d1315f0 Merge branch 'online-accounts-rework' of github.com:Qortal/qortal into online-accounts-rework 2021-11-27 23:00:06 +00:00
CalDescent
6da671ba86 Started work to compute a PoW nonce in the online accounts manager.
This isn't used for anything yet, but it's enough to measure the performance implications of continually computing a nonce.
2021-11-27 22:59:34 +00:00
CalDescent
bf10c46d9f Moved some OnlineAccountManager logs from trace to debug, so that online accounts activity can be monitored more easily. It was too "all or nothing" before. 2021-11-27 22:55:32 +00:00
CalDescent
a2fbdbe295 Moved all online accounts code to a new class called OnlineAccountsManager
The Controller class was a bit crowded so it makes sense to move this to a dedicated controller. There haven't been any intentional logic changes here, to keep risk to a minimum.
2021-11-27 22:55:32 +00:00
CalDescent
b40e6cb933 Started work to compute a PoW nonce in the online accounts manager.
This isn't used for anything yet, but it's enough to measure the performance implications of continually computing a nonce.
2021-10-09 17:32:43 +01:00
CalDescent
03ca36c990 Merge branch 'online-accounts-rework' of github.com:Qortal/qortal into online-accounts-rework 2021-10-09 17:02:40 +01:00
CalDescent
e3505836f3 Moved some OnlineAccountManager logs from trace to debug, so that online accounts activity can be monitored more easily. It was too "all or nothing" before. 2021-10-09 17:02:30 +01:00
CalDescent
6548e4c07d Moved all online accounts code to a new class called OnlineAccountsManager
The Controller class was a bit crowded so it makes sense to move this to a dedicated controller. There haven't been any intentional logic changes here, to keep risk to a minimum.
2021-10-09 17:02:30 +01:00
CalDescent
083c534e61 Moved some OnlineAccountManager logs from trace to debug, so that online accounts activity can be monitored more easily. It was too "all or nothing" before. 2021-08-07 19:05:11 +01:00
CalDescent
84a4b15019 Moved all online accounts code to a new class called OnlineAccountsManager
The Controller class was a bit crowded so it makes sense to move this to a dedicated controller. There haven't been any intentional logic changes here, to keep risk to a minimum.
2021-08-07 18:09:47 +01:00
20 changed files with 736 additions and 358 deletions

View File

@@ -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:{817F888F-5FFB-40A4-B839-941BB9426D93} 1049:{402239D3-5D8E-4FFC-9BAC-1920DBCC6D34} 2052:{BB2EB961-8D52-43AD-9BDE-8B7CA4D5985B} 2057:{C1281311-FCA8-4B2B-AB06-7DD75CE0D302} " Type="16"/>
<ROW Property="ProductCode" Value="1033:{51EFA0B0-C304-4043-AF6D-2C17C783A998} 1049:{C4662BB2-A247-426E-A128-B7DBD12ECE78} 2052:{1AF44520-C8AB-4261-BCFE-EEA941439718} 2057:{C096EB6A-F43F-45EE-921C-D20F9B993E80} " Type="16"/>
<ROW Property="ProductLanguage" Value="2057"/>
<ROW Property="ProductName" Value="Qortal"/>
<ROW Property="ProductVersion" Value="2.1.1" Type="32"/>
<ROW Property="ProductVersion" Value="2.1.3" Type="32"/>
<ROW Property="RECONFIG_NTP" Value="true"/>
<ROW Property="REMOVE_BLOCKCHAIN" Value="YES" Type="4"/>
<ROW Property="REPAIR_BLOCKCHAIN" Value="YES" Type="4"/>
@@ -212,7 +212,7 @@
<ROW Component="ADDITIONAL_LICENSE_INFO_71" ComponentId="{12A3ADBE-BB7A-496C-8869-410681E6232F}" Directory_="jdk.zipfs_Dir" Attributes="0" KeyPath="ADDITIONAL_LICENSE_INFO_71" Type="0"/>
<ROW Component="ADDITIONAL_LICENSE_INFO_8" ComponentId="{D53AD95E-CF96-4999-80FC-5812277A7456}" Directory_="java.naming_Dir" Attributes="0" KeyPath="ADDITIONAL_LICENSE_INFO_8" Type="0"/>
<ROW Component="ADDITIONAL_LICENSE_INFO_9" ComponentId="{6B7EA9B0-5D17-47A8-B78C-FACE86D15E01}" Directory_="java.net.http_Dir" Attributes="0" KeyPath="ADDITIONAL_LICENSE_INFO_9" Type="0"/>
<ROW Component="AI_CustomARPName" ComponentId="{E9D8F0C9-6CF8-477D-9410-49FB5425AEB1}" Directory_="APPDIR" Attributes="260" KeyPath="DisplayName" Options="1"/>
<ROW Component="AI_CustomARPName" ComponentId="{9A243E53-8FC9-4854-B6E2-937493305BB4}" 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"/>

View File

@@ -3,7 +3,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>org.qortal</groupId>
<artifactId>qortal</artifactId>
<version>2.1.2</version>
<version>2.1.3-mempow.0</version>
<packaging>jar</packaging>
<properties>
<skipTests>true</skipTests>

View File

@@ -34,7 +34,7 @@ import org.qortal.api.Security;
import org.qortal.api.model.ApiOnlineAccount;
import org.qortal.api.model.RewardShareKeyRequest;
import org.qortal.asset.Asset;
import org.qortal.controller.Controller;
import org.qortal.controller.OnlineAccountsManager;
import org.qortal.crypto.Crypto;
import org.qortal.data.account.AccountData;
import org.qortal.data.account.RewardShareData;
@@ -160,7 +160,7 @@ public class AddressesResource {
)
@ApiErrors({ApiError.PUBLIC_KEY_NOT_FOUND, ApiError.REPOSITORY_ISSUE})
public List<ApiOnlineAccount> getOnlineAccounts() {
List<OnlineAccountData> onlineAccounts = Controller.getInstance().getOnlineAccounts();
List<OnlineAccountData> onlineAccounts = OnlineAccountsManager.getInstance().getOnlineAccounts();
// Map OnlineAccountData entries to OnlineAccount via reward-share data
try (final Repository repository = RepositoryManager.getRepository()) {
@@ -195,7 +195,7 @@ public class AddressesResource {
)
@ApiErrors({ApiError.PUBLIC_KEY_NOT_FOUND, ApiError.REPOSITORY_ISSUE})
public List<OnlineAccountLevel> getOnlineAccountsByLevel() {
List<OnlineAccountData> onlineAccounts = Controller.getInstance().getOnlineAccounts();
List<OnlineAccountData> onlineAccounts = OnlineAccountsManager.getInstance().getOnlineAccounts();
try (final Repository repository = RepositoryManager.getRepository()) {
List<OnlineAccountLevel> onlineAccountLevels = new ArrayList<>();

View File

@@ -75,7 +75,8 @@ public class BlocksResource {
@ApiErrors({
ApiError.INVALID_SIGNATURE, ApiError.BLOCK_UNKNOWN, ApiError.REPOSITORY_ISSUE
})
public BlockData getBlock(@PathParam("signature") String signature58) {
public BlockData getBlock(@PathParam("signature") String signature58,
@QueryParam("includeOnlineSignatures") Boolean includeOnlineSignatures) {
// Decode signature
byte[] signature;
try {
@@ -88,12 +89,18 @@ public class BlocksResource {
// Check the database first
BlockData blockData = repository.getBlockRepository().fromSignature(signature);
if (blockData != null) {
if (includeOnlineSignatures == null || includeOnlineSignatures == false) {
blockData.setOnlineAccountsSignatures(null);
}
return blockData;
}
// Not found, so try the block archive
blockData = repository.getBlockArchiveRepository().fromSignature(signature);
if (blockData != null) {
if (includeOnlineSignatures == null || includeOnlineSignatures == false) {
blockData.setOnlineAccountsSignatures(null);
}
return blockData;
}
@@ -423,17 +430,24 @@ public class BlocksResource {
@ApiErrors({
ApiError.BLOCK_UNKNOWN, ApiError.REPOSITORY_ISSUE
})
public BlockData getByHeight(@PathParam("height") int height) {
public BlockData getByHeight(@PathParam("height") int height,
@QueryParam("includeOnlineSignatures") Boolean includeOnlineSignatures) {
try (final Repository repository = RepositoryManager.getRepository()) {
// Firstly check the database
BlockData blockData = repository.getBlockRepository().fromHeight(height);
if (blockData != null) {
if (includeOnlineSignatures == null || includeOnlineSignatures == false) {
blockData.setOnlineAccountsSignatures(null);
}
return blockData;
}
// Not found, so try the archive
blockData = repository.getBlockArchiveRepository().fromHeight(height);
if (blockData != null) {
if (includeOnlineSignatures == null || includeOnlineSignatures == false) {
blockData.setOnlineAccountsSignatures(null);
}
return blockData;
}

View File

@@ -28,7 +28,7 @@ import org.qortal.asset.Asset;
import org.qortal.at.AT;
import org.qortal.block.BlockChain.BlockTimingByHeight;
import org.qortal.block.BlockChain.AccountLevelShareBin;
import org.qortal.controller.Controller;
import org.qortal.controller.OnlineAccountsManager;
import org.qortal.crypto.Crypto;
import org.qortal.data.account.AccountBalanceData;
import org.qortal.data.account.AccountData;
@@ -320,7 +320,7 @@ public class Block {
byte[] reference = parentBlockData.getSignature();
// Fetch our list of online accounts
List<OnlineAccountData> onlineAccounts = Controller.getInstance().getOnlineAccounts();
List<OnlineAccountData> onlineAccounts = OnlineAccountsManager.getInstance().getOnlineAccounts();
if (onlineAccounts.isEmpty()) {
LOGGER.error("No online accounts - not even our own?");
return null;
@@ -976,10 +976,10 @@ public class Block {
byte[] onlineTimestampBytes = Longs.toByteArray(onlineTimestamp);
// If this block is much older than current online timestamp, then there's no point checking current online accounts
List<OnlineAccountData> currentOnlineAccounts = onlineTimestamp < NTP.getTime() - Controller.ONLINE_TIMESTAMP_MODULUS
List<OnlineAccountData> currentOnlineAccounts = onlineTimestamp < NTP.getTime() - OnlineAccountsManager.ONLINE_TIMESTAMP_MODULUS
? null
: Controller.getInstance().getOnlineAccounts();
List<OnlineAccountData> latestBlocksOnlineAccounts = Controller.getInstance().getLatestBlocksOnlineAccounts();
: OnlineAccountsManager.getInstance().getOnlineAccounts();
List<OnlineAccountData> latestBlocksOnlineAccounts = OnlineAccountsManager.getInstance().getLatestBlocksOnlineAccounts();
// Extract online accounts' timestamp signatures from block data
List<byte[]> onlineAccountsSignatures = BlockTransformer.decodeTimestampSignatures(this.blockData.getOnlineAccountsSignatures());
@@ -1357,7 +1357,7 @@ public class Block {
postBlockTidy();
// Give Controller our cached, valid online accounts data (if any) to help reduce CPU load for next block
Controller.getInstance().pushLatestBlocksOnlineAccounts(this.cachedValidOnlineAccounts);
OnlineAccountsManager.getInstance().pushLatestBlocksOnlineAccounts(this.cachedValidOnlineAccounts);
// Log some debugging info relating to the block weight calculation
this.logDebugInfo();
@@ -1575,8 +1575,8 @@ public class Block {
postBlockTidy();
// Remove any cached, valid online accounts data from Controller
Controller.getInstance().popLatestBlocksOnlineAccounts();
// Remove any cached, valid online accounts data from OnlineAccountsManager
OnlineAccountsManager.getInstance().popLatestBlocksOnlineAccounts();
}
protected void orphanTransactionsFromBlock() throws DataException {

View File

@@ -109,7 +109,7 @@ public class BlockMinter extends Thread {
continue;
// No online accounts? (e.g. during startup)
if (Controller.getInstance().getOnlineAccounts().isEmpty())
if (OnlineAccountsManager.getInstance().getOnlineAccounts().isEmpty())
continue;
List<MintingAccountData> mintingAccountsData = repository.getAccountRepository().getMintingAccounts();
@@ -429,7 +429,7 @@ public class BlockMinter extends Thread {
throw new DataException("Ignoring attempt to mint testing block for non-test chain!");
// Ensure mintingAccount is 'online' so blocks can be minted
Controller.getInstance().ensureTestingAccountsOnline(mintingAndOnlineAccounts);
OnlineAccountsManager.getInstance().ensureTestingAccountsOnline(mintingAndOnlineAccounts);
PrivateKeyAccount mintingAccount = mintingAndOnlineAccounts[0];

View File

@@ -12,7 +12,6 @@ import java.security.Security;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@@ -41,9 +40,6 @@ import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.jsse.provider.BouncyCastleJsseProvider;
import org.qortal.account.Account;
import org.qortal.account.PrivateKeyAccount;
import org.qortal.account.PublicKeyAccount;
import org.qortal.api.ApiService;
import org.qortal.block.Block;
import org.qortal.block.BlockChain;
@@ -53,11 +49,8 @@ import org.qortal.controller.repository.PruneManager;
import org.qortal.controller.repository.NamesDatabaseIntegrityCheck;
import org.qortal.controller.tradebot.TradeBot;
import org.qortal.crypto.Crypto;
import org.qortal.data.account.MintingAccountData;
import org.qortal.data.account.RewardShareData;
import org.qortal.data.block.BlockData;
import org.qortal.data.block.BlockSummaryData;
import org.qortal.data.network.OnlineAccountData;
import org.qortal.data.network.PeerChainTipData;
import org.qortal.data.network.PeerData;
import org.qortal.data.transaction.ArbitraryTransactionData;
@@ -77,14 +70,12 @@ import org.qortal.network.message.CachedBlockMessage;
import org.qortal.network.message.GetArbitraryDataMessage;
import org.qortal.network.message.GetBlockMessage;
import org.qortal.network.message.GetBlockSummariesMessage;
import org.qortal.network.message.GetOnlineAccountsMessage;
import org.qortal.network.message.GetPeersMessage;
import org.qortal.network.message.GetSignaturesV2Message;
import org.qortal.network.message.GetTransactionMessage;
import org.qortal.network.message.GetUnconfirmedTransactionsMessage;
import org.qortal.network.message.HeightV2Message;
import org.qortal.network.message.Message;
import org.qortal.network.message.OnlineAccountsMessage;
import org.qortal.network.message.SignaturesMessage;
import org.qortal.network.message.TransactionMessage;
import org.qortal.network.message.TransactionSignaturesMessage;
@@ -97,7 +88,6 @@ import org.qortal.transaction.Transaction.TransactionType;
import org.qortal.transaction.Transaction.ValidationResult;
import org.qortal.utils.*;
import com.google.common.primitives.Longs;
public class Controller extends Thread {
@@ -122,14 +112,6 @@ public class Controller extends Thread {
private static final long DELETE_EXPIRED_INTERVAL = 5 * 60 * 1000L; // ms
private static final long RECOVERY_MODE_TIMEOUT = 10 * 60 * 1000L; // ms
// To do with online accounts list
private static final long ONLINE_ACCOUNTS_TASKS_INTERVAL = 10 * 1000L; // ms
private static final long ONLINE_ACCOUNTS_BROADCAST_INTERVAL = 1 * 60 * 1000L; // ms
public static final long ONLINE_TIMESTAMP_MODULUS = 5 * 60 * 1000L;
private static final long LAST_SEEN_EXPIRY_PERIOD = (ONLINE_TIMESTAMP_MODULUS * 2) + (1 * 60 * 1000L);
/** How many (latest) blocks' worth of online accounts we cache */
private static final int MAX_BLOCKS_CACHED_ONLINE_ACCOUNTS = 2;
private static volatile boolean isStopping = false;
private static BlockMinter blockMinter = null;
private static volatile boolean requestSync = false;
@@ -161,8 +143,6 @@ public class Controller extends Thread {
private long ntpCheckTimestamp = startTime; // ms
private long deleteExpiredTimestamp = startTime + DELETE_EXPIRED_INTERVAL; // ms
private long onlineAccountsTasksTimestamp = startTime + ONLINE_ACCOUNTS_TASKS_INTERVAL; // ms
/** Whether we can mint new blocks, as reported by BlockMinter. */
private volatile boolean isMintingPossible = false;
@@ -203,11 +183,6 @@ public class Controller extends Thread {
/** Lock for only allowing one blockchain-modifying codepath at a time. e.g. synchronization or newly minted block. */
private final ReentrantLock blockchainLock = new ReentrantLock();
/** Cache of current 'online accounts' */
List<OnlineAccountData> onlineAccounts = new ArrayList<>();
/** Cache of latest blocks' online accounts */
Deque<List<OnlineAccountData>> latestBlocksOnlineAccounts = new ArrayDeque<>(MAX_BLOCKS_CACHED_ONLINE_ACCOUNTS);
// Stats
@XmlAccessorType(XmlAccessType.FIELD)
public static class StatsSnapshot {
@@ -438,10 +413,12 @@ public class Controller extends Thread {
return; // Not System.exit() so that GUI can display error
}
// Rebuild Names table and check database integrity
// Rebuild Names table and check database integrity (if enabled)
NamesDatabaseIntegrityCheck namesDatabaseIntegrityCheck = new NamesDatabaseIntegrityCheck();
namesDatabaseIntegrityCheck.rebuildAllNames();
namesDatabaseIntegrityCheck.runIntegrityCheck();
if (Settings.getInstance().isNamesIntegrityCheckEnabled()) {
namesDatabaseIntegrityCheck.runIntegrityCheck();
}
LOGGER.info("Validating blockchain");
try {
@@ -495,6 +472,10 @@ public class Controller extends Thread {
// LOGGER.info("Starting arbitrary-transaction data manager");
// ArbitraryDataManager.getInstance().start();
// Online accounts manager
LOGGER.info("Starting online accounts manager");
OnlineAccountsManager.getInstance().start();
// Auto-update service?
if (Settings.getInstance().isAutoUpdateEnabled()) {
LOGGER.info("Starting auto-update");
@@ -639,11 +620,9 @@ public class Controller extends Thread {
deleteExpiredTransactions();
}
// Perform tasks to do with managing online accounts list
if (now >= onlineAccountsTasksTimestamp) {
onlineAccountsTasksTimestamp = now + ONLINE_ACCOUNTS_TASKS_INTERVAL;
performOnlineAccountsTasks();
}
// Check and maybe perform online accounts tasks
OnlineAccountsManager.getInstance().checkOnlineAccountsTasks(now);
}
} catch (InterruptedException e) {
// Clear interrupted flag so we can shutdown trim threads
@@ -1056,6 +1035,10 @@ public class Controller extends Thread {
// LOGGER.info("Shutting down arbitrary-transaction data manager");
// ArbitraryDataManager.getInstance().shutdown();
// Online accounts manager
LOGGER.info("Shutting down online accounts manager");
OnlineAccountsManager.getInstance().shutdown();
if (blockMinter != null) {
LOGGER.info("Shutting down block minter");
blockMinter.shutdown();
@@ -1359,11 +1342,11 @@ public class Controller extends Thread {
break;
case GET_ONLINE_ACCOUNTS:
onNetworkGetOnlineAccountsMessage(peer, message);
OnlineAccountsManager.getInstance().onNetworkGetOnlineAccountsMessage(peer, message);
break;
case ONLINE_ACCOUNTS:
onNetworkOnlineAccountsMessage(peer, message);
OnlineAccountsManager.getInstance().onNetworkOnlineAccountsMessage(peer, message);
break;
default:
@@ -1828,299 +1811,6 @@ public class Controller extends Thread {
}
}
private void onNetworkGetOnlineAccountsMessage(Peer peer, Message message) {
GetOnlineAccountsMessage getOnlineAccountsMessage = (GetOnlineAccountsMessage) message;
List<OnlineAccountData> excludeAccounts = getOnlineAccountsMessage.getOnlineAccounts();
// Send online accounts info, excluding entries with matching timestamp & public key from excludeAccounts
List<OnlineAccountData> accountsToSend;
synchronized (this.onlineAccounts) {
accountsToSend = new ArrayList<>(this.onlineAccounts);
}
Iterator<OnlineAccountData> iterator = accountsToSend.iterator();
SEND_ITERATOR:
while (iterator.hasNext()) {
OnlineAccountData onlineAccountData = iterator.next();
for (int i = 0; i < excludeAccounts.size(); ++i) {
OnlineAccountData excludeAccountData = excludeAccounts.get(i);
if (onlineAccountData.getTimestamp() == excludeAccountData.getTimestamp() && Arrays.equals(onlineAccountData.getPublicKey(), excludeAccountData.getPublicKey())) {
iterator.remove();
continue SEND_ITERATOR;
}
}
}
Message onlineAccountsMessage = new OnlineAccountsMessage(accountsToSend);
peer.sendMessage(onlineAccountsMessage);
LOGGER.trace(() -> String.format("Sent %d of our %d online accounts to %s", accountsToSend.size(), this.onlineAccounts.size(), peer));
}
private void onNetworkOnlineAccountsMessage(Peer peer, Message message) {
OnlineAccountsMessage onlineAccountsMessage = (OnlineAccountsMessage) message;
List<OnlineAccountData> peersOnlineAccounts = onlineAccountsMessage.getOnlineAccounts();
LOGGER.trace(() -> String.format("Received %d online accounts from %s", peersOnlineAccounts.size(), peer));
try (final Repository repository = RepositoryManager.getRepository()) {
for (OnlineAccountData onlineAccountData : peersOnlineAccounts)
this.verifyAndAddAccount(repository, onlineAccountData);
} catch (DataException e) {
LOGGER.error(String.format("Repository issue while verifying online accounts from peer %s", peer), e);
}
}
// Utilities
private void verifyAndAddAccount(Repository repository, OnlineAccountData onlineAccountData) throws DataException {
final Long now = NTP.getTime();
if (now == null)
return;
PublicKeyAccount otherAccount = new PublicKeyAccount(repository, onlineAccountData.getPublicKey());
// Check timestamp is 'recent' here
if (Math.abs(onlineAccountData.getTimestamp() - now) > ONLINE_TIMESTAMP_MODULUS * 2) {
LOGGER.trace(() -> String.format("Rejecting online account %s with out of range timestamp %d", otherAccount.getAddress(), onlineAccountData.getTimestamp()));
return;
}
// Verify
byte[] data = Longs.toByteArray(onlineAccountData.getTimestamp());
if (!otherAccount.verify(onlineAccountData.getSignature(), data)) {
LOGGER.trace(() -> String.format("Rejecting invalid online account %s", otherAccount.getAddress()));
return;
}
// Qortal: check online account is actually reward-share
RewardShareData rewardShareData = repository.getAccountRepository().getRewardShare(onlineAccountData.getPublicKey());
if (rewardShareData == null) {
// Reward-share doesn't even exist - probably not a good sign
LOGGER.trace(() -> String.format("Rejecting unknown online reward-share public key %s", Base58.encode(onlineAccountData.getPublicKey())));
return;
}
Account mintingAccount = new Account(repository, rewardShareData.getMinter());
if (!mintingAccount.canMint()) {
// Minting-account component of reward-share can no longer mint - disregard
LOGGER.trace(() -> String.format("Rejecting online reward-share with non-minting account %s", mintingAccount.getAddress()));
return;
}
synchronized (this.onlineAccounts) {
OnlineAccountData existingAccountData = this.onlineAccounts.stream().filter(account -> Arrays.equals(account.getPublicKey(), onlineAccountData.getPublicKey())).findFirst().orElse(null);
if (existingAccountData != null) {
if (existingAccountData.getTimestamp() < onlineAccountData.getTimestamp()) {
this.onlineAccounts.remove(existingAccountData);
LOGGER.trace(() -> String.format("Updated online account %s with timestamp %d (was %d)", otherAccount.getAddress(), onlineAccountData.getTimestamp(), existingAccountData.getTimestamp()));
} else {
LOGGER.trace(() -> String.format("Not updating existing online account %s", otherAccount.getAddress()));
return;
}
} else {
LOGGER.trace(() -> String.format("Added online account %s with timestamp %d", otherAccount.getAddress(), onlineAccountData.getTimestamp()));
}
this.onlineAccounts.add(onlineAccountData);
}
}
public void ensureTestingAccountsOnline(PrivateKeyAccount... onlineAccounts) {
if (!BlockChain.getInstance().isTestChain()) {
LOGGER.warn("Ignoring attempt to ensure test account is online for non-test chain!");
return;
}
final Long now = NTP.getTime();
if (now == null)
return;
final long onlineAccountsTimestamp = Controller.toOnlineAccountTimestamp(now);
byte[] timestampBytes = Longs.toByteArray(onlineAccountsTimestamp);
synchronized (this.onlineAccounts) {
this.onlineAccounts.clear();
for (PrivateKeyAccount onlineAccount : onlineAccounts) {
// Check mintingAccount is actually reward-share?
byte[] signature = onlineAccount.sign(timestampBytes);
byte[] publicKey = onlineAccount.getPublicKey();
OnlineAccountData ourOnlineAccountData = new OnlineAccountData(onlineAccountsTimestamp, signature, publicKey);
this.onlineAccounts.add(ourOnlineAccountData);
}
}
}
private void performOnlineAccountsTasks() {
final Long now = NTP.getTime();
if (now == null)
return;
// Expire old entries
final long cutoffThreshold = now - LAST_SEEN_EXPIRY_PERIOD;
synchronized (this.onlineAccounts) {
Iterator<OnlineAccountData> iterator = this.onlineAccounts.iterator();
while (iterator.hasNext()) {
OnlineAccountData onlineAccountData = iterator.next();
if (onlineAccountData.getTimestamp() < cutoffThreshold) {
iterator.remove();
LOGGER.trace(() -> {
PublicKeyAccount otherAccount = new PublicKeyAccount(null, onlineAccountData.getPublicKey());
return String.format("Removed expired online account %s with timestamp %d", otherAccount.getAddress(), onlineAccountData.getTimestamp());
});
}
}
}
// Request data from other peers?
if ((this.onlineAccountsTasksTimestamp % ONLINE_ACCOUNTS_BROADCAST_INTERVAL) < ONLINE_ACCOUNTS_TASKS_INTERVAL) {
Message message;
synchronized (this.onlineAccounts) {
message = new GetOnlineAccountsMessage(this.onlineAccounts);
}
Network.getInstance().broadcast(peer -> message);
}
// Refresh our online accounts signatures?
sendOurOnlineAccountsInfo();
}
private void sendOurOnlineAccountsInfo() {
final Long now = NTP.getTime();
if (now == null)
return;
List<MintingAccountData> mintingAccounts;
try (final Repository repository = RepositoryManager.getRepository()) {
mintingAccounts = repository.getAccountRepository().getMintingAccounts();
// We have no accounts, but don't reset timestamp
if (mintingAccounts.isEmpty())
return;
// Only reward-share accounts allowed
Iterator<MintingAccountData> iterator = mintingAccounts.iterator();
while (iterator.hasNext()) {
MintingAccountData mintingAccountData = iterator.next();
RewardShareData rewardShareData = repository.getAccountRepository().getRewardShare(mintingAccountData.getPublicKey());
if (rewardShareData == null) {
// Reward-share doesn't even exist - probably not a good sign
iterator.remove();
continue;
}
Account mintingAccount = new Account(repository, rewardShareData.getMinter());
if (!mintingAccount.canMint()) {
// Minting-account component of reward-share can no longer mint - disregard
iterator.remove();
continue;
}
}
} catch (DataException e) {
LOGGER.warn(String.format("Repository issue trying to fetch minting accounts: %s", e.getMessage()));
return;
}
// 'current' timestamp
final long onlineAccountsTimestamp = Controller.toOnlineAccountTimestamp(now);
boolean hasInfoChanged = false;
byte[] timestampBytes = Longs.toByteArray(onlineAccountsTimestamp);
List<OnlineAccountData> ourOnlineAccounts = new ArrayList<>();
MINTING_ACCOUNTS:
for (MintingAccountData mintingAccountData : mintingAccounts) {
PrivateKeyAccount mintingAccount = new PrivateKeyAccount(null, mintingAccountData.getPrivateKey());
byte[] signature = mintingAccount.sign(timestampBytes);
byte[] publicKey = mintingAccount.getPublicKey();
// Our account is online
OnlineAccountData ourOnlineAccountData = new OnlineAccountData(onlineAccountsTimestamp, signature, publicKey);
synchronized (this.onlineAccounts) {
Iterator<OnlineAccountData> iterator = this.onlineAccounts.iterator();
while (iterator.hasNext()) {
OnlineAccountData existingOnlineAccountData = iterator.next();
if (Arrays.equals(existingOnlineAccountData.getPublicKey(), ourOnlineAccountData.getPublicKey())) {
// If our online account is already present, with same timestamp, then move on to next mintingAccount
if (existingOnlineAccountData.getTimestamp() == onlineAccountsTimestamp)
continue MINTING_ACCOUNTS;
// If our online account is already present, but with older timestamp, then remove it
iterator.remove();
break;
}
}
this.onlineAccounts.add(ourOnlineAccountData);
}
LOGGER.trace(() -> String.format("Added our online account %s with timestamp %d", mintingAccount.getAddress(), onlineAccountsTimestamp));
ourOnlineAccounts.add(ourOnlineAccountData);
hasInfoChanged = true;
}
if (!hasInfoChanged)
return;
Message message = new OnlineAccountsMessage(ourOnlineAccounts);
Network.getInstance().broadcast(peer -> message);
LOGGER.trace(()-> String.format("Broadcasted %d online account%s with timestamp %d", ourOnlineAccounts.size(), (ourOnlineAccounts.size() != 1 ? "s" : ""), onlineAccountsTimestamp));
}
public static long toOnlineAccountTimestamp(long timestamp) {
return (timestamp / ONLINE_TIMESTAMP_MODULUS) * ONLINE_TIMESTAMP_MODULUS;
}
/** Returns list of online accounts with timestamp recent enough to be considered currently online. */
public List<OnlineAccountData> getOnlineAccounts() {
final long onlineTimestamp = Controller.toOnlineAccountTimestamp(NTP.getTime());
synchronized (this.onlineAccounts) {
return this.onlineAccounts.stream().filter(account -> account.getTimestamp() == onlineTimestamp).collect(Collectors.toList());
}
}
/** Returns cached, unmodifiable list of latest block's online accounts. */
public List<OnlineAccountData> getLatestBlocksOnlineAccounts() {
synchronized (this.latestBlocksOnlineAccounts) {
return this.latestBlocksOnlineAccounts.peekFirst();
}
}
/** Caches list of latest block's online accounts. Typically called by Block.process() */
public void pushLatestBlocksOnlineAccounts(List<OnlineAccountData> latestBlocksOnlineAccounts) {
synchronized (this.latestBlocksOnlineAccounts) {
if (this.latestBlocksOnlineAccounts.size() == MAX_BLOCKS_CACHED_ONLINE_ACCOUNTS)
this.latestBlocksOnlineAccounts.pollLast();
this.latestBlocksOnlineAccounts.addFirst(latestBlocksOnlineAccounts == null
? Collections.emptyList()
: Collections.unmodifiableList(latestBlocksOnlineAccounts));
}
}
/** Reverts list of latest block's online accounts. Typically called by Block.orphan() */
public void popLatestBlocksOnlineAccounts() {
synchronized (this.latestBlocksOnlineAccounts) {
this.latestBlocksOnlineAccounts.pollFirst();
}
}
public byte[] fetchArbitraryData(byte[] signature) throws InterruptedException {
// Build request

View File

@@ -0,0 +1,404 @@
package org.qortal.controller;
import com.google.common.primitives.Longs;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.qortal.account.Account;
import org.qortal.account.PrivateKeyAccount;
import org.qortal.account.PublicKeyAccount;
import org.qortal.block.BlockChain;
import org.qortal.crypto.MemoryPoW;
import org.qortal.data.account.MintingAccountData;
import org.qortal.data.account.RewardShareData;
import org.qortal.data.network.OnlineAccountData;
import org.qortal.network.Network;
import org.qortal.network.Peer;
import org.qortal.network.message.GetOnlineAccountsMessage;
import org.qortal.network.message.Message;
import org.qortal.network.message.OnlineAccountsMessage;
import org.qortal.repository.DataException;
import org.qortal.repository.Repository;
import org.qortal.repository.RepositoryManager;
import org.qortal.utils.Base58;
import org.qortal.utils.NTP;
import java.util.*;
import java.util.stream.Collectors;
public class OnlineAccountsManager extends Thread {
private static final Logger LOGGER = LogManager.getLogger(OnlineAccountsManager.class);
private static OnlineAccountsManager instance;
private volatile boolean isStopping = false;
// To do with online accounts list
private static final long ONLINE_ACCOUNTS_TASKS_INTERVAL = 10 * 1000L; // ms
private static final long ONLINE_ACCOUNTS_BROADCAST_INTERVAL = 1 * 60 * 1000L; // ms
public static final long ONLINE_TIMESTAMP_MODULUS = 5 * 60 * 1000L;
private static final long LAST_SEEN_EXPIRY_PERIOD = (ONLINE_TIMESTAMP_MODULUS * 2) + (1 * 60 * 1000L);
/** How many (latest) blocks' worth of online accounts we cache */
private static final int MAX_BLOCKS_CACHED_ONLINE_ACCOUNTS = 2;
public static final int POW_BUFFER_SIZE = 8 * 1024 * 1024; // bytes
private long onlineAccountsTasksTimestamp = Controller.startTime + ONLINE_ACCOUNTS_TASKS_INTERVAL; // ms
/** Cache of current 'online accounts' */
List<OnlineAccountData> onlineAccounts = new ArrayList<>();
/** Cache of latest blocks' online accounts */
Deque<List<OnlineAccountData>> latestBlocksOnlineAccounts = new ArrayDeque<>(MAX_BLOCKS_CACHED_ONLINE_ACCOUNTS);
public OnlineAccountsManager() {
}
public static synchronized OnlineAccountsManager getInstance() {
if (instance == null) {
instance = new OnlineAccountsManager();
}
return instance;
}
@Override
public void run() {
Thread.currentThread().setName("Online Accounts Manager");
try {
while (!isStopping) {
Thread.sleep(2000);
int difficulty = 15;
long timestamp = System.currentTimeMillis();
LOGGER.info("Computing nonce at difficulty {} for timestamp {}...", difficulty, timestamp);
byte[] bytes = Longs.toByteArray(timestamp);
Integer nonce = MemoryPoW.compute2(bytes, POW_BUFFER_SIZE, difficulty);
long totalTime = System.currentTimeMillis() - timestamp;
double hashRate = nonce / (double)totalTime * 1000.0f;
LOGGER.info("Computed nonce: {}. Time taken: {} ms. Hashrate: {}", nonce, totalTime, hashRate);
}
} catch (InterruptedException e) {
// Fall-through to exit thread...
}
}
public void shutdown() {
isStopping = true;
this.interrupt();
}
public void checkOnlineAccountsTasks(long now) {
// Perform tasks to do with managing online accounts list
if (now >= onlineAccountsTasksTimestamp) {
onlineAccountsTasksTimestamp = now + ONLINE_ACCOUNTS_TASKS_INTERVAL;
performOnlineAccountsTasks();
}
}
private void sendOurOnlineAccountsInfo() {
final Long now = NTP.getTime();
if (now == null)
return;
List<MintingAccountData> mintingAccounts;
try (final Repository repository = RepositoryManager.getRepository()) {
mintingAccounts = repository.getAccountRepository().getMintingAccounts();
// We have no accounts, but don't reset timestamp
if (mintingAccounts.isEmpty())
return;
// Only reward-share accounts allowed
Iterator<MintingAccountData> iterator = mintingAccounts.iterator();
while (iterator.hasNext()) {
MintingAccountData mintingAccountData = iterator.next();
RewardShareData rewardShareData = repository.getAccountRepository().getRewardShare(mintingAccountData.getPublicKey());
if (rewardShareData == null) {
// Reward-share doesn't even exist - probably not a good sign
iterator.remove();
continue;
}
Account mintingAccount = new Account(repository, rewardShareData.getMinter());
if (!mintingAccount.canMint()) {
// Minting-account component of reward-share can no longer mint - disregard
iterator.remove();
continue;
}
}
} catch (DataException e) {
LOGGER.warn(String.format("Repository issue trying to fetch minting accounts: %s", e.getMessage()));
return;
}
// 'current' timestamp
final long onlineAccountsTimestamp = OnlineAccountsManager.toOnlineAccountTimestamp(now);
boolean hasInfoChanged = false;
byte[] timestampBytes = Longs.toByteArray(onlineAccountsTimestamp);
List<OnlineAccountData> ourOnlineAccounts = new ArrayList<>();
MINTING_ACCOUNTS:
for (MintingAccountData mintingAccountData : mintingAccounts) {
PrivateKeyAccount mintingAccount = new PrivateKeyAccount(null, mintingAccountData.getPrivateKey());
byte[] signature = mintingAccount.sign(timestampBytes);
byte[] publicKey = mintingAccount.getPublicKey();
// Our account is online
OnlineAccountData ourOnlineAccountData = new OnlineAccountData(onlineAccountsTimestamp, signature, publicKey);
synchronized (this.onlineAccounts) {
Iterator<OnlineAccountData> iterator = this.onlineAccounts.iterator();
while (iterator.hasNext()) {
OnlineAccountData existingOnlineAccountData = iterator.next();
if (Arrays.equals(existingOnlineAccountData.getPublicKey(), ourOnlineAccountData.getPublicKey())) {
// If our online account is already present, with same timestamp, then move on to next mintingAccount
if (existingOnlineAccountData.getTimestamp() == onlineAccountsTimestamp)
continue MINTING_ACCOUNTS;
// If our online account is already present, but with older timestamp, then remove it
iterator.remove();
break;
}
}
this.onlineAccounts.add(ourOnlineAccountData);
}
LOGGER.debug(() -> String.format("Added our online account %s with timestamp %d", mintingAccount.getAddress(), onlineAccountsTimestamp));
ourOnlineAccounts.add(ourOnlineAccountData);
hasInfoChanged = true;
}
if (!hasInfoChanged)
return;
Message message = new OnlineAccountsMessage(ourOnlineAccounts);
Network.getInstance().broadcast(peer -> message);
LOGGER.debug(()-> String.format("Broadcasted %d online account%s with timestamp %d", ourOnlineAccounts.size(), (ourOnlineAccounts.size() != 1 ? "s" : ""), onlineAccountsTimestamp));
}
private void performOnlineAccountsTasks() {
final Long now = NTP.getTime();
if (now == null)
return;
// Expire old entries
final long cutoffThreshold = now - LAST_SEEN_EXPIRY_PERIOD;
synchronized (this.onlineAccounts) {
Iterator<OnlineAccountData> iterator = this.onlineAccounts.iterator();
while (iterator.hasNext()) {
OnlineAccountData onlineAccountData = iterator.next();
if (onlineAccountData.getTimestamp() < cutoffThreshold) {
iterator.remove();
LOGGER.trace(() -> {
PublicKeyAccount otherAccount = new PublicKeyAccount(null, onlineAccountData.getPublicKey());
return String.format("Removed expired online account %s with timestamp %d", otherAccount.getAddress(), onlineAccountData.getTimestamp());
});
}
}
}
// Request data from other peers?
if ((this.onlineAccountsTasksTimestamp % ONLINE_ACCOUNTS_BROADCAST_INTERVAL) < ONLINE_ACCOUNTS_TASKS_INTERVAL) {
Message message;
synchronized (this.onlineAccounts) {
message = new GetOnlineAccountsMessage(this.onlineAccounts);
}
Network.getInstance().broadcast(peer -> message);
}
// Refresh our online accounts signatures?
sendOurOnlineAccountsInfo();
}
public static long toOnlineAccountTimestamp(long timestamp) {
return (timestamp / ONLINE_TIMESTAMP_MODULUS) * ONLINE_TIMESTAMP_MODULUS;
}
/** Returns list of online accounts with timestamp recent enough to be considered currently online. */
public List<OnlineAccountData> getOnlineAccounts() {
final long onlineTimestamp = OnlineAccountsManager.toOnlineAccountTimestamp(NTP.getTime());
synchronized (this.onlineAccounts) {
return this.onlineAccounts.stream().filter(account -> account.getTimestamp() == onlineTimestamp).collect(Collectors.toList());
}
}
/** Returns cached, unmodifiable list of latest block's online accounts. */
public List<OnlineAccountData> getLatestBlocksOnlineAccounts() {
synchronized (this.latestBlocksOnlineAccounts) {
return this.latestBlocksOnlineAccounts.peekFirst();
}
}
/** Caches list of latest block's online accounts. Typically called by Block.process() */
public void pushLatestBlocksOnlineAccounts(List<OnlineAccountData> latestBlocksOnlineAccounts) {
synchronized (this.latestBlocksOnlineAccounts) {
if (this.latestBlocksOnlineAccounts.size() == MAX_BLOCKS_CACHED_ONLINE_ACCOUNTS)
this.latestBlocksOnlineAccounts.pollLast();
this.latestBlocksOnlineAccounts.addFirst(latestBlocksOnlineAccounts == null
? Collections.emptyList()
: Collections.unmodifiableList(latestBlocksOnlineAccounts));
}
}
/** Reverts list of latest block's online accounts. Typically called by Block.orphan() */
public void popLatestBlocksOnlineAccounts() {
synchronized (this.latestBlocksOnlineAccounts) {
this.latestBlocksOnlineAccounts.pollFirst();
}
}
// Utilities
private void verifyAndAddAccount(Repository repository, OnlineAccountData onlineAccountData) throws DataException {
final Long now = NTP.getTime();
if (now == null)
return;
PublicKeyAccount otherAccount = new PublicKeyAccount(repository, onlineAccountData.getPublicKey());
// Check timestamp is 'recent' here
if (Math.abs(onlineAccountData.getTimestamp() - now) > ONLINE_TIMESTAMP_MODULUS * 2) {
LOGGER.trace(() -> String.format("Rejecting online account %s with out of range timestamp %d", otherAccount.getAddress(), onlineAccountData.getTimestamp()));
return;
}
// Verify
byte[] data = Longs.toByteArray(onlineAccountData.getTimestamp());
if (!otherAccount.verify(onlineAccountData.getSignature(), data)) {
LOGGER.trace(() -> String.format("Rejecting invalid online account %s", otherAccount.getAddress()));
return;
}
// Qortal: check online account is actually reward-share
RewardShareData rewardShareData = repository.getAccountRepository().getRewardShare(onlineAccountData.getPublicKey());
if (rewardShareData == null) {
// Reward-share doesn't even exist - probably not a good sign
LOGGER.trace(() -> String.format("Rejecting unknown online reward-share public key %s", Base58.encode(onlineAccountData.getPublicKey())));
return;
}
Account mintingAccount = new Account(repository, rewardShareData.getMinter());
if (!mintingAccount.canMint()) {
// Minting-account component of reward-share can no longer mint - disregard
LOGGER.trace(() -> String.format("Rejecting online reward-share with non-minting account %s", mintingAccount.getAddress()));
return;
}
synchronized (this.onlineAccounts) {
OnlineAccountData existingAccountData = this.onlineAccounts.stream().filter(account -> Arrays.equals(account.getPublicKey(), onlineAccountData.getPublicKey())).findFirst().orElse(null);
if (existingAccountData != null) {
if (existingAccountData.getTimestamp() < onlineAccountData.getTimestamp()) {
this.onlineAccounts.remove(existingAccountData);
LOGGER.trace(() -> String.format("Updated online account %s with timestamp %d (was %d)", otherAccount.getAddress(), onlineAccountData.getTimestamp(), existingAccountData.getTimestamp()));
} else {
LOGGER.trace(() -> String.format("Not updating existing online account %s", otherAccount.getAddress()));
return;
}
} else {
LOGGER.debug(() -> String.format("Added online account %s with timestamp %d", otherAccount.getAddress(), onlineAccountData.getTimestamp()));
}
this.onlineAccounts.add(onlineAccountData);
}
}
public void ensureTestingAccountsOnline(PrivateKeyAccount... onlineAccounts) {
if (!BlockChain.getInstance().isTestChain()) {
LOGGER.warn("Ignoring attempt to ensure test account is online for non-test chain!");
return;
}
final Long now = NTP.getTime();
if (now == null)
return;
final long onlineAccountsTimestamp = this.toOnlineAccountTimestamp(now);
byte[] timestampBytes = Longs.toByteArray(onlineAccountsTimestamp);
synchronized (this.onlineAccounts) {
this.onlineAccounts.clear();
for (PrivateKeyAccount onlineAccount : onlineAccounts) {
// Check mintingAccount is actually reward-share?
byte[] signature = onlineAccount.sign(timestampBytes);
byte[] publicKey = onlineAccount.getPublicKey();
OnlineAccountData ourOnlineAccountData = new OnlineAccountData(onlineAccountsTimestamp, signature, publicKey);
this.onlineAccounts.add(ourOnlineAccountData);
}
}
}
// Network handlers
public void onNetworkGetOnlineAccountsMessage(Peer peer, Message message) {
GetOnlineAccountsMessage getOnlineAccountsMessage = (GetOnlineAccountsMessage) message;
List<OnlineAccountData> excludeAccounts = getOnlineAccountsMessage.getOnlineAccounts();
// Send online accounts info, excluding entries with matching timestamp & public key from excludeAccounts
List<OnlineAccountData> accountsToSend;
synchronized (this.onlineAccounts) {
accountsToSend = new ArrayList<>(this.onlineAccounts);
}
Iterator<OnlineAccountData> iterator = accountsToSend.iterator();
SEND_ITERATOR:
while (iterator.hasNext()) {
OnlineAccountData onlineAccountData = iterator.next();
for (int i = 0; i < excludeAccounts.size(); ++i) {
OnlineAccountData excludeAccountData = excludeAccounts.get(i);
if (onlineAccountData.getTimestamp() == excludeAccountData.getTimestamp() && Arrays.equals(onlineAccountData.getPublicKey(), excludeAccountData.getPublicKey())) {
iterator.remove();
continue SEND_ITERATOR;
}
}
}
Message onlineAccountsMessage = new OnlineAccountsMessage(accountsToSend);
peer.sendMessage(onlineAccountsMessage);
LOGGER.debug(() -> String.format("Sent %d of our %d online accounts to %s", accountsToSend.size(), this.onlineAccounts.size(), peer));
}
public void onNetworkOnlineAccountsMessage(Peer peer, Message message) {
OnlineAccountsMessage onlineAccountsMessage = (OnlineAccountsMessage) message;
List<OnlineAccountData> peersOnlineAccounts = onlineAccountsMessage.getOnlineAccounts();
LOGGER.debug(() -> String.format("Received %d online accounts from %s", peersOnlineAccounts.size(), peer));
try (final Repository repository = RepositoryManager.getRepository()) {
for (OnlineAccountData onlineAccountData : peersOnlineAccounts)
this.verifyAndAddAccount(repository, onlineAccountData);
} catch (DataException e) {
LOGGER.error(String.format("Repository issue while verifying online accounts from peer %s", peer), e);
}
}
}

View File

@@ -187,7 +187,12 @@ public class NamesDatabaseIntegrityCheck {
// The old name will then be unregistered, or re-registered.
// FUTURE: check database integrity for names that have been updated and then the original name re-registered
else if (Objects.equals(updateNameTransactionData.getName(), registeredName)) {
NameData newNameData = repository.getNameRepository().fromName(updateNameTransactionData.getNewName());
String newName = updateNameTransactionData.getNewName();
if (newName == null || newName.length() == 0) {
// If new name is blank (or maybe null, just to be safe), it means that it stayed the same
newName = registeredName;
}
NameData newNameData = repository.getNameRepository().fromName(newName);
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());

View File

@@ -1,5 +1,7 @@
package org.qortal.crypto;
import org.qortal.controller.Controller;
import java.nio.ByteBuffer;
public class MemoryPoW {
@@ -33,6 +35,10 @@ public class MemoryPoW {
if (Thread.currentThread().isInterrupted())
return -1;
// Or, if the Controller is stopping, do the same
if (Controller.isStopping())
return -1;
seed *= seedMultiplier; // per nonce
state[0] = longHash[0] ^ seed;

View File

@@ -204,6 +204,10 @@ public class BlockData implements Serializable {
return this.onlineAccountsSignatures;
}
public void setOnlineAccountsSignatures(byte[] onlineAccountsSignatures) {
this.onlineAccountsSignatures = onlineAccountsSignatures;
}
// JAXB special
@XmlElement(name = "minterAddress")

View File

@@ -147,13 +147,13 @@ public class SysTray {
}
});
JMenuItem openUi = new JMenuItem(Translator.INSTANCE.translate("SysTray", "OPEN_UI"));
/* JMenuItem openUi = new JMenuItem(Translator.INSTANCE.translate("SysTray", "OPEN_UI"));
openUi.addActionListener(actionEvent -> {
destroyHiddenDialog();
new OpenUiWorker().execute();
});
menu.add(openUi);
menu.add(openUi); */
JMenuItem openTimeCheck = new JMenuItem(Translator.INSTANCE.translate("SysTray", "CHECK_TIME_ACCURACY"));
openTimeCheck.addActionListener(actionEvent -> {

View File

@@ -149,6 +149,10 @@ public class Settings {
private boolean bootstrap = true;
/** Registered names integrity check */
private boolean namesIntegrityCheckEnabled = false;
// Peer-to-peer related
private boolean isTestNet = false;
/** Port number for inbound peer-to-peer connections. */
@@ -639,6 +643,10 @@ public class Settings {
return this.blockPruneBatchSize;
}
public boolean isNamesIntegrityCheckEnabled() {
return this.namesIntegrityCheckEnabled;
}
public boolean isArchiveEnabled() {
if (this.topOnly) {

View File

@@ -12,7 +12,7 @@ import java.util.function.Supplier;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.qortal.account.Account;
import org.qortal.controller.Controller;
import org.qortal.controller.OnlineAccountsManager;
import org.qortal.crosschain.ACCT;
import org.qortal.crosschain.SupportedBlockchain;
import org.qortal.crypto.Crypto;
@@ -47,7 +47,7 @@ public class PresenceTransaction extends Transaction {
REWARD_SHARE(0) {
@Override
public long getLifetime() {
return Controller.ONLINE_TIMESTAMP_MODULUS;
return OnlineAccountsManager.ONLINE_TIMESTAMP_MODULUS;
}
},
TRADE_BOT(1) {

View File

@@ -0,0 +1,55 @@
### Commun ###
JSON = échec de l'analyse du message JSON
INSUFFICIENT_BALANCE = balance insuffisante
UNAUTHORIZED = appel de lAPI 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 ###
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
TRANSACTION_INVALID = transaction invalide: %s (%s)
### Nommage ###
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 ###
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 ###
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

View File

@@ -0,0 +1,41 @@
AUTO_UPDATE = Mise à jour automatique
APPLYING_UPDATE_AND_RESTARTING = Application de la mise à jour automatique et redémarrage...
BLOCK_HEIGHT = hauteur
BUILD_VERSION = Numéro de version
CHECK_TIME_ACCURACY = Vérifier l'heure
CONNECTING = Connexion en cours
CONNECTION = connexion
CONNECTIONS = connexions
CREATING_BACKUP_OF_DB_FILES = Création d'une sauvegarde des fichiers de la base de données...
DB_BACKUP = Sauvegarde de la base de données
DB_MAINTENANCE = Maintenance de la base de données
DB_CHECKPOINT = Point de contrôle de la base de données
EXIT = Quitter
MINTING_DISABLED = NE mint PAS
MINTING_ENABLED = \u2714 Minting
OPEN_UI = Ouvrir l'interface
PERFORMING_DB_CHECKPOINT = Enregistrement des modifications de base de données non validées...
PERFORMING_DB_MAINTENANCE = Entrain d'effectuer la maintenance programmée...
SYNCHRONIZE_CLOCK = Mettre l'heure à jour
SYNCHRONIZING_BLOCKCHAIN = Synchronisation
SYNCHRONIZING_CLOCK = Synchronisation de l'heure

View File

@@ -0,0 +1,151 @@
OK = OK
INVALID_ADDRESS = adresse invalide
NEGATIVE_AMOUNT = montant invalide/négatif
NEGATIVE_FEE = frais invalides/négatifs
NO_BALANCE = solde insuffisant
INVALID_REFERENCE = référence invalide
INVALID_NAME_LENGTH = longueur de nom invalide
INVALID_VALUE_LENGTH = longueur de 'valeur' invalide
NAME_ALREADY_REGISTERED = le nom est déjà enregistré
NAME_DOES_NOT_EXIST = le nom n'existe pas
INVALID_NAME_OWNER = le nom du propriétaire est invalide
NAME_ALREADY_FOR_SALE = le nom est déjà en vente
NAME_NOT_FOR_SALE = le nom n'est pas à vendre
BUYER_ALREADY_OWNER = l'acheteur est déjà le propriétaire
INVALID_AMOUNT = montant invalide
INVALID_SELLER = vendeur invalide
NAME_NOT_NORMALIZED = le nom n'est pas sous la forme 'normalisée' Unicode
INVALID_DESCRIPTION_LENGTH = longueur de description invalide
INVALID_OPTIONS_COUNT = nombre d'options invalides
INVALID_OPTION_LENGTH = longueur des options invalide
DUPLICATE_OPTION = option dupliquée
POLL_ALREADY_EXISTS = le scrutin existe déjà
POLL_DOES_NOT_EXIST = le scrutin n'existe pas
POLL_OPTION_DOES_NOT_EXIST = Ce choix de scrutin n'existe pas
ALREADY_VOTED_FOR_THAT_OPTION = Vous avez déjà voté pour ce choix
INVALID_DATA_LENGTH = longueur de données invalide
INVALID_QUANTITY = quantité invalide
ASSET_DOES_NOT_EXIST = l'actif n'existe pas
INVALID_RETURN = retour invalide
HAVE_EQUALS_WANT = l'actif désiré est le même que l'actif possédé
ORDER_DOES_NOT_EXIST = l'ordre d'échange d'actifs n'existe pas
INVALID_ORDER_CREATOR = créateur d'ordre invalide
INVALID_PAYMENTS_COUNT = nombre de paiements invalides
NEGATIVE_PRICE = prix invalide/négatif
INVALID_CREATION_BYTES = octets de création invalides
INVALID_TAGS_LENGTH = longueur de 'tags' invalide
INVALID_AT_TYPE_LENGTH = longueur 'type' AT invalide
INVALID_AT_TRANSACTION = transaction AT invalide
INSUFFICIENT_FEE = frais insuffisant
ASSET_DOES_NOT_MATCH_AT = l'actif ne correspond pas à l'actif d'AT
ASSET_ALREADY_EXISTS = l'actif existe déjà
MISSING_CREATOR = créateur manquant
TIMESTAMP_TOO_OLD = horodatage trop ancien
TIMESTAMP_TOO_NEW = horodatage trop récent
TOO_MANY_UNCONFIRMED = le compte a trop de transactions non confirmées en attente
GROUP_ALREADY_EXISTS = le groupe existe déjà
GROUP_DOES_NOT_EXIST = le groupe n'existe pas
INVALID_GROUP_OWNER = propriétaire de groupe invalide
ALREADY_GROUP_MEMBER = vous êtes déjà un(e) membre du groupe
GROUP_OWNER_CANNOT_LEAVE = le propriétaire du groupe ne peut pas quitter le groupe
NOT_GROUP_MEMBER = le compte n'est pas membre du groupe
ALREADY_GROUP_ADMIN = vous êtes déjà l'administrateur(trice) du groupe
NOT_GROUP_ADMIN = le compte n'est pas un administrateur du groupe
INVALID_LIFETIME = durée de vie invalide
INVITE_UNKNOWN = invitation de groupe inconnue
BAN_EXISTS = déjà banni
BAN_UNKNOWN = bannissement inconnu
BANNED_FROM_GROUP = banned from group
JOIN_REQUEST_EXISTS = la demande d'adhésion au groupe existe déjà
INVALID_GROUP_APPROVAL_THRESHOLD = seuil d'approbation de groupe non valide
GROUP_ID_MISMATCH = identifiant de groupe non-concorde
INVALID_GROUP_ID = identifiant de groupe invalide
TRANSACTION_UNKNOWN = transaction inconnue
TRANSACTION_ALREADY_CONFIRMED = la transaction a déjà été confirmée
INVALID_TX_GROUP_ID = identifiant du groupe de transactions invalide
TX_GROUP_ID_MISMATCH = l'identifiant du groupe de transaction ne correspond pas
MULTIPLE_NAMES_FORBIDDEN = l'enregistrement de plusieurs noms par compte est interdit
INVALID_ASSET_OWNER = propriétaire de l'actif invalide
AT_IS_FINISHED = l'AT est fini
NO_FLAG_PERMISSION = le compte n'a pas cette autorisation
NOT_MINTING_ACCOUNT = le compte ne peut pas mint
REWARD_SHARE_UNKNOWN = partage de récompense inconnu
INVALID_REWARD_SHARE_PERCENT = pourcentage du partage de récompense invalide
PUBLIC_KEY_UNKNOWN = clé publique inconnue
INVALID_PUBLIC_KEY = clé publique invalide
AT_UNKNOWN = AT inconnu
AT_ALREADY_EXISTS = AT déjà existante
GROUP_APPROVAL_NOT_REQUIRED = approbation de groupe non requise
GROUP_APPROVAL_DECIDED = approbation de groupe déjà décidée
MAXIMUM_REWARD_SHARES = déjà au nombre maximum de récompense pour ce compte
TRANSACTION_ALREADY_EXISTS = la transaction existe déjà
NO_BLOCKCHAIN_LOCK = nœud de la blockchain actuellement occupé
ORDER_ALREADY_CLOSED = l'ordre d'échange d'actifs est déjà fermé
CLOCK_NOT_SYNCED = horloge non synchronisée
ASSET_NOT_SPENDABLE = l'actif n'est pas dépensable
ACCOUNT_CANNOT_REWARD_SHARE = le compte ne peut pas récompenser
SELF_SHARE_EXISTS = l'auto-partage (récompense) existe déjà
ACCOUNT_ALREADY_EXISTS = Le compte existe déjà
INVALID_GROUP_BLOCK_DELAY = délai de blocage d'approbation de groupe invalide
INCORRECT_NONCE = PoW nonce incorrect
INVALID_TIMESTAMP_SIGNATURE = signature d'horodatage invalide
ADDRESS_IN_BLACKLIST = cette adresse est dans votre liste noire
ADDRESS_ABOVE_RATE_LIMIT = l'adresse a atteint la limite de débit spécifiée
DUPLICATE_MESSAGE = l'adresse a envoyé un message en double
INVALID_BUT_OK = invalide mais OK
NOT_YET_RELEASED = fonctionnalité pas encore publiée

View File

@@ -40,7 +40,7 @@ public class BlockApiTests extends ApiCommon {
byte[] signatureBytes = GenesisBlock.getInstance(repository).getSignature();
String signature = Base58.encode(signatureBytes);
assertNotNull(this.blocksResource.getBlock(signature));
assertNotNull(this.blocksResource.getBlock(signature, true));
}
}
@@ -72,7 +72,7 @@ public class BlockApiTests extends ApiCommon {
@Test
public void testGetBlockByHeight() {
assertNotNull(this.blocksResource.getByHeight(1));
assertNotNull(this.blocksResource.getByHeight(1, true));
}
@Test

View File

@@ -8,7 +8,7 @@ import org.junit.Before;
import org.junit.Test;
import org.qortal.account.PrivateKeyAccount;
import org.qortal.controller.BlockMinter;
import org.qortal.controller.Controller;
import org.qortal.controller.OnlineAccountsManager;
import org.qortal.data.account.RewardShareData;
import org.qortal.repository.DataException;
import org.qortal.repository.Repository;
@@ -77,7 +77,7 @@ public class BlocksMintedCountTests extends Common {
assertNotNull(testRewardShareData);
// Create signed timestamps
Controller.getInstance().ensureTestingAccountsOnline(mintingAccount, testRewardShareAccount);
OnlineAccountsManager.getInstance().ensureTestingAccountsOnline(mintingAccount, testRewardShareAccount);
// Even though Alice features in two online reward-shares, she should only gain +1 blocksMinted
// Bob only features in one online reward-share, so should also only gain +1 blocksMinted
@@ -87,7 +87,7 @@ public class BlocksMintedCountTests extends Common {
private void testRewardShare(Repository repository, PrivateKeyAccount testRewardShareAccount, int aliceDelta, int bobDelta) throws DataException {
// Create signed timestamps
Controller.getInstance().ensureTestingAccountsOnline(testRewardShareAccount);
OnlineAccountsManager.getInstance().ensureTestingAccountsOnline(testRewardShareAccount);
testRewardShareRetainingTimestamps(repository, testRewardShareAccount, aliceDelta, bobDelta);
}

View File

@@ -11,7 +11,7 @@ import org.junit.Before;
import org.junit.Test;
import org.qortal.account.PrivateKeyAccount;
import org.qortal.controller.BlockMinter;
import org.qortal.controller.Controller;
import org.qortal.controller.OnlineAccountsManager;
import org.qortal.data.account.RewardShareData;
import org.qortal.data.block.BlockData;
import org.qortal.data.transaction.TransactionData;
@@ -73,7 +73,7 @@ public class DisagreementTests extends Common {
assertNotNull(testRewardShareData);
// Create signed timestamps
Controller.getInstance().ensureTestingAccountsOnline(mintingAccount, testRewardShareAccount);
OnlineAccountsManager.getInstance().ensureTestingAccountsOnline(mintingAccount, testRewardShareAccount);
// Mint another block
BlockMinter.mintTestingBlockRetainingTimestamps(repository, mintingAccount);