Compare commits

...

26 Commits

Author SHA1 Message Date
catbref
5eafdf3c80 Bump version to 1.0.4 2020-03-19 14:30:02 +00:00
catbref
d7c26c27e1 Add debugging message to Peer regarding lost PING replies 2020-03-19 14:29:47 +00:00
catbref
2d18dd62eb Translation / API response improvements 2020-03-19 14:21:48 +00:00
catbref
51fd177d79 Add access to network engine stats
Added API call GET /peers/enginestats to allow external monitoring.

Extract all engine stats in one synchronized block, instead of
separate calls, for better consistency.
2020-03-19 11:19:49 +00:00
catbref
c4643538f1 Improve Synchronizer to reduce network load
Synchronizer now bails out early when trying to find common block with
a peer. There's no need to keep searching if common block is too far
behind that a TOO_DIVERGENT result would be returned.

fetchSummariesFromCommonBlock() reworked to return a useful
SynchronizationResult directly instead of caller trying to infer
what happened based on null/empty returned list!
2020-03-19 11:11:35 +00:00
catbref
0edadaf901 Remove warning 2020-03-19 11:11:05 +00:00
catbref
c05533fb71 Fix comment 2020-03-19 11:10:40 +00:00
catbref
db270f559f Improve minting/syncing status reporting
Added API call GET /admin/status which reports whether minting
is possible (have minting keys & up-to-date) and whether node is
currently attempting to sync.

Corresponding change to system tray mouseover text.

Corresponding text added to SysTray transaction resources.
2020-03-19 11:07:56 +00:00
catbref
79f7f68b0c Change when BlockMinter decides it's ok to mint a block
Previously BlockMinter would attempt to mint if there were at least
'minBlockchainPeers' connected peers and none of them had an
up-to-date block and we did. This was maybe useful for minting block 2
but possibly causes minting chain islands where a badly connected
node mints by itself, even though connected to not up-to-date peers.

Now BlockMinter requires 'minBlockchainPeers' up-to-date peers, not
simply just connected. This should let synchronization bring the
node up-to-date but does require the node to have better peers.

Currently, the default for minBlockchainPeers is 10. So a node
requires 10 up-to-date peers before it will consider minting. It
might be possible to reduce this in the future to lessen network load.
2020-03-19 10:58:53 +00:00
catbref
d30d61edab Reworking/speed-ups for block rewards & general account DB manipulation
**NOTE** currently under wider test - maybe not be final version!
2020-03-18 18:04:45 +00:00
catbref
f7e2ee383e More complete testing of block reward distribution to founders 2020-03-18 18:03:56 +00:00
catbref
544fdbfbe9 Add database-level CHECK constraint on account balances 2020-03-18 18:03:13 +00:00
catbref
c3d1ecb7e1 Reduce maximum allowed distance back to common block 2020-03-18 18:02:00 +00:00
catbref
873a9d0cee Add support for testing with multiple online accounts 2020-03-18 18:00:46 +00:00
catbref
95cb5f607b Use HSQLDB v2.5.0 but with fix for INSERT...ON DUPLICATE KEY UPDATE bug 2020-03-18 17:58:16 +00:00
catbref
54d0b721c4 Dynamically allocate/deallocate Peer byteBuffer to reduce memory load at the expense of extra GC 2020-03-16 17:50:49 +00:00
catbref
4a4678b331 Immediately close socketChannels after accepting peers we won't use 2020-03-16 16:07:17 +00:00
catbref
12f9ecaaca Faster Synchronizer shutdown by checking Controller.isStopping() 2020-03-16 16:05:27 +00:00
catbref
1d3ee77fb8 Increase default number of peers required before a node can mint/sync, and other settings 2020-03-15 14:14:58 +00:00
catbref
64055e280d Shutdown controller, and hence entire node, if networking or API fail to start. 2020-03-11 15:46:59 +00:00
catbref
90e0f9dddc Fix system-dependent path separator usage.
Although HSQLDB is happy being given unix-style path separator '/'
and converting as necessary on other platforms (e.g. Windows),
manipulation of repository pathnames in Java, outside of HSQLDB,
needs to use platform-specific path separators.

Thus, changes made to replace '/' with File.separator where
necessary.

This should fix repository rebuild errors, which then lead to odd
start-up errors like:

2020-03-11 13:55:19 INFO  Controller:270 - Starting repository
2020-03-11 13:55:20 INFO  Controller:287 - Validating blockchain
2020-03-11 13:55:20 INFO  HSQLDBRepository:227 - Rebuilding repository from scratch
2020-03-11 13:55:20 INFO  GenesisBlock:296 - Using genesis block timestamp of 1583870000000
2020-03-11 13:55:21 WARN  HSQLDBRepository:720 - Uncommitted changes (882) after connection close, session [3]
java.lang.NullPointerException
    at org.qortal.transform.block.BlockTransformer.decodeOnlineAccounts(BlockTransformer.java:422)
    at org.qortal.block.Block.getExpandedAccounts(Block.java:546)
    at org.qortal.block.Block.increaseAccountLevels(Block.java:1245)
    at org.qortal.block.Block.increaseAccountLevels(Block.java:1239)
    at org.qortal.block.Block.process(Block.java:1206)
    at org.qortal.block.GenesisBlock.process(GenesisBlock.java:345)
    at org.qortal.block.BlockChain.rebuildBlockchain(BlockChain.java:526)
    at org.qortal.block.BlockChain.validate(BlockChain.java:481)
    at org.qortal.controller.Controller.main(Controller.java:289)

The above happens because the old blockchain still exists when trying to process
the genesis block.
2020-03-11 15:25:04 +00:00
catbref
b0b0e2ac18 Strip JNI options before calling ApplyUpdate
AdvancedInstaller's Java launcher EXE seems to use JNI to launch
the JAR, instead of using the command-line 'java' binary directly.

When AI's launcher does this, it adds options like "abort" and "exit",
along with corresponding hook addresses.

These options are returned by the call to
ManagementFactory.getRuntimeMXBean().getInputArguments() which is
done in AutoUpdate while building the command line for launching
ApplyUpdate.

Because command-line 'java' binary doesn't support these options,
they are now stripped out.
2020-03-11 10:41:39 +00:00
catbref
9db606af5a Latest genesis block 2020-03-10 18:12:11 +00:00
catbref
5bfc17bd64 Remove node UI and have tray icon open local/remove UI server
No more "node UI". UI provided by 3rd party.

"Open UI" tray icon menu item now attempts to open UI at various
local servers (see Settings.uilocalServers) or some random
remote server (Settings.uiRemoteServers).

Default UI port now 12388 (Settings.uiPort).
2020-03-10 13:32:10 +00:00
catbref
a3c44428d3 Restrict TRANSFER_PRIVS recipients to new (non-existent) accounts. 2020-03-04 15:41:47 +00:00
catbref
450ff7318f Slightly more restrictive API access 2020-03-04 15:41:19 +00:00
41 changed files with 1075 additions and 631 deletions

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<groupId>org.hsqldb</groupId>
<artifactId>hsqldb</artifactId>
<version>2.5.0-fixed</version>
<description>POM was created from install:install-file</description>
</project>

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<metadata>
<groupId>org.hsqldb</groupId>
<artifactId>hsqldb</artifactId>
<versioning>
<release>2.5.0-fixed</release>
<versions>
<version>2.5.0-fixed</version>
</versions>
<lastUpdated>20200318133132</lastUpdated>
</versioning>
</metadata>

View File

@@ -3,7 +3,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>org.qortal</groupId>
<artifactId>qortal</artifactId>
<version>1.0</version>
<version>1.0.4</version>
<packaging>jar</packaging>
<properties>
<bitcoin.version>0.15.4</bitcoin.version>
@@ -13,7 +13,7 @@
<commons-text.version>1.8</commons-text.version>
<dagger.version>1.2.2</dagger.version>
<guava.version>28.1-jre</guava.version>
<hsqldb.version>2.5.0</hsqldb.version>
<hsqldb.version>2.5.0-fixed</hsqldb.version>
<hsqldb-sqltool.version>2.5.0</hsqldb-sqltool.version>
<jersey.version>2.29.1</jersey.version>
<jetty.version>9.4.22.v20191022</jetty.version>

View File

@@ -0,0 +1,15 @@
package org.qortal.api.model;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
@XmlAccessorType(XmlAccessType.FIELD)
public class NodeStatus {
public boolean isMintingPossible;
public boolean isSynchronizing;
public NodeStatus() {
}
}

View File

@@ -45,6 +45,7 @@ import org.qortal.api.ApiExceptionFactory;
import org.qortal.api.Security;
import org.qortal.api.model.ActivitySummary;
import org.qortal.api.model.NodeInfo;
import org.qortal.api.model.NodeStatus;
import org.qortal.block.BlockChain;
import org.qortal.controller.Controller;
import org.qortal.controller.Synchronizer.SynchronizationResult;
@@ -120,6 +121,27 @@ public class AdminResource {
return nodeInfo;
}
@GET
@Path("/status")
@Operation(
summary = "Fetch node status",
responses = {
@ApiResponse(
content = @Content(mediaType = MediaType.APPLICATION_JSON, schema = @Schema(implementation = NodeStatus.class))
)
}
)
public NodeStatus status() {
Security.checkApiCallAllowed(request);
NodeStatus nodeStatus = new NodeStatus();
nodeStatus.isMintingPossible = Controller.getInstance().isMintingPossible();
nodeStatus.isSynchronizing = Controller.getInstance().isSynchronizing();
return nodeStatus;
}
@GET
@Path("/stop")
@Operation(
@@ -202,6 +224,8 @@ public class AdminResource {
)
@ApiErrors({ApiError.REPOSITORY_ISSUE})
public List<MintingAccountData> getMintingAccounts() {
Security.checkApiCallAllowed(request);
try (final Repository repository = RepositoryManager.getRepository()) {
List<MintingAccountData> mintingAccounts = repository.getAccountRepository().getMintingAccounts();
@@ -246,6 +270,8 @@ public class AdminResource {
)
@ApiErrors({ApiError.INVALID_PRIVATE_KEY, ApiError.REPOSITORY_ISSUE, ApiError.CANNOT_MINT})
public String addMintingAccount(String seed58) {
Security.checkApiCallAllowed(request);
try (final Repository repository = RepositoryManager.getRepository()) {
byte[] seed = Base58.decode(seed58.trim());
@@ -296,6 +322,8 @@ public class AdminResource {
)
@ApiErrors({ApiError.INVALID_PRIVATE_KEY, ApiError.REPOSITORY_ISSUE})
public String deleteMintingAccount(String seed58) {
Security.checkApiCallAllowed(request);
try (final Repository repository = RepositoryManager.getRepository()) {
byte[] seed = Base58.decode(seed58.trim());

View File

@@ -31,6 +31,7 @@ import org.qortal.network.PeerAddress;
import org.qortal.repository.DataException;
import org.qortal.repository.Repository;
import org.qortal.repository.RepositoryManager;
import org.qortal.utils.ExecuteProduceConsume;
@Path("/peers")
@Tag(name = "Peers")
@@ -108,6 +109,29 @@ public class PeersResource {
return Network.getInstance().getSelfPeers();
}
@GET
@Path("/enginestats")
@Operation(
summary = "Fetch statistics snapshot for networking engine",
responses = {
@ApiResponse(
content = @Content(
mediaType = MediaType.APPLICATION_JSON,
array = @ArraySchema(
schema = @Schema(
implementation = ExecuteProduceConsume.StatsSnapshot.class
)
)
)
)
}
)
public ExecuteProduceConsume.StatsSnapshot getEngineStats() {
Security.checkApiCallAllowed(request);
return Network.getInstance().getStatsSnapshot();
}
@POST
@Operation(
summary = "Add new peer address",

View File

@@ -573,7 +573,7 @@ public class TransactionsResource {
public static ApiException createTransactionInvalidException(HttpServletRequest request, ValidationResult result) {
String translatedResult = Translator.INSTANCE.translate("TransactionValidity", request.getLocale().getLanguage(), result.name());
return ApiExceptionFactory.INSTANCE.createException(request, ApiError.TRANSACTION_INVALID, null, translatedResult);
return ApiExceptionFactory.INSTANCE.createException(request, ApiError.TRANSACTION_INVALID, null, translatedResult, result.name());
}
}

View File

@@ -9,7 +9,6 @@ import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
@@ -126,31 +125,43 @@ public class Block {
protected BigDecimal ourAtFees; // Generated locally
/** Lazy-instantiated expanded info on block's online accounts. */
class ExpandedAccount {
final RewardShareData rewardShareData;
final boolean isRecipientAlsoMinter;
static class ExpandedAccount {
private static final BigDecimal oneHundred = BigDecimal.valueOf(100L);
final Account mintingAccount;
final AccountData mintingAccountData;
final boolean isMinterFounder;
private final Repository repository;
final Account recipientAccount;
final AccountData recipientAccountData;
final boolean isRecipientFounder;
private final RewardShareData rewardShareData;
private final boolean isRecipientAlsoMinter;
private final Account mintingAccount;
private final AccountData mintingAccountData;
private final boolean isMinterFounder;
private final Account recipientAccount;
private final AccountData recipientAccountData;
private final boolean isRecipientFounder;
ExpandedAccount(Repository repository, int accountIndex) throws DataException {
this.repository = repository;
this.rewardShareData = repository.getAccountRepository().getRewardShareByIndex(accountIndex);
this.mintingAccount = new PublicKeyAccount(repository, this.rewardShareData.getMinterPublicKey());
this.recipientAccount = new Account(repository, this.rewardShareData.getRecipient());
this.mintingAccountData = repository.getAccountRepository().getAccount(this.mintingAccount.getAddress());
this.isMinterFounder = Account.isFounder(mintingAccountData.getFlags());
this.recipientAccountData = repository.getAccountRepository().getAccount(this.recipientAccount.getAddress());
this.isRecipientFounder = Account.isFounder(recipientAccountData.getFlags());
this.isRecipientAlsoMinter = this.rewardShareData.getRecipient().equals(this.mintingAccount.getAddress());
this.isRecipientAlsoMinter = this.mintingAccountData.getAddress().equals(this.recipientAccountData.getAddress());
if (this.isRecipientAlsoMinter) {
// Self-share: minter is also recipient
this.recipientAccount = this.mintingAccount;
this.recipientAccountData = this.mintingAccountData;
this.isRecipientFounder = this.isMinterFounder;
} else {
// Recipient differs from minter
this.recipientAccount = new Account(repository, this.rewardShareData.getRecipient());
this.recipientAccountData = repository.getAccountRepository().getAccount(this.recipientAccount.getAddress());
this.isRecipientFounder = Account.isFounder(recipientAccountData.getFlags());
}
}
/**
@@ -176,22 +187,26 @@ public class Block {
}
void distribute(BigDecimal accountAmount) throws DataException {
final BigDecimal oneHundred = BigDecimal.valueOf(100L);
if (this.mintingAccount.getAddress().equals(this.recipientAccount.getAddress())) {
if (this.isRecipientAlsoMinter) {
// minter & recipient the same - simpler case
LOGGER.trace(() -> String.format("Minter/recipient account %s share: %s", this.mintingAccount.getAddress(), accountAmount.toPlainString()));
this.mintingAccount.setConfirmedBalance(Asset.QORT, this.mintingAccount.getConfirmedBalance(Asset.QORT).add(accountAmount));
if (accountAmount.signum() != 0)
// this.mintingAccount.setConfirmedBalance(Asset.QORT, this.mintingAccount.getConfirmedBalance(Asset.QORT).add(accountAmount));
this.repository.getAccountRepository().modifyAssetBalance(this.mintingAccount.getAddress(), Asset.QORT, accountAmount);
} else {
// minter & recipient different - extra work needed
BigDecimal recipientAmount = accountAmount.multiply(this.rewardShareData.getSharePercent()).divide(oneHundred, RoundingMode.DOWN);
BigDecimal minterAmount = accountAmount.subtract(recipientAmount);
LOGGER.trace(() -> String.format("Minter account %s share: %s", this.mintingAccount.getAddress(), minterAmount.toPlainString()));
this.mintingAccount.setConfirmedBalance(Asset.QORT, this.mintingAccount.getConfirmedBalance(Asset.QORT).add(minterAmount));
if (minterAmount.signum() != 0)
// this.mintingAccount.setConfirmedBalance(Asset.QORT, this.mintingAccount.getConfirmedBalance(Asset.QORT).add(minterAmount));
this.repository.getAccountRepository().modifyAssetBalance(this.mintingAccount.getAddress(), Asset.QORT, minterAmount);
LOGGER.trace(() -> String.format("Recipient account %s share: %s", this.recipientAccount.getAddress(), recipientAmount.toPlainString()));
this.recipientAccount.setConfirmedBalance(Asset.QORT, this.recipientAccount.getConfirmedBalance(Asset.QORT).add(recipientAmount));
if (recipientAmount.signum() != 0)
// this.recipientAccount.setConfirmedBalance(Asset.QORT, this.recipientAccount.getConfirmedBalance(Asset.QORT).add(recipientAmount));
this.repository.getAccountRepository().modifyAssetBalance(this.recipientAccount.getAddress(), Asset.QORT, recipientAmount);
}
}
}
@@ -1256,8 +1271,9 @@ public class Block {
AccountData accountData = getAccountData.apply(expandedAccount);
accountData.setBlocksMinted(accountData.getBlocksMinted() + 1);
repository.getAccountRepository().setMintedBlockCount(accountData);
LOGGER.trace(() -> String.format("Block minter %s up to %d minted block%s", accountData.getAddress(), accountData.getBlocksMinted(), (accountData.getBlocksMinted() != 1 ? "s" : "")));
// repository.getAccountRepository().setMintedBlockCount(accountData); int rowCount = 1; // Until HSQLDB rev 6100 is fixed
int rowCount = repository.getAccountRepository().modifyMintedBlockCount(accountData.getAddress(), +1);
LOGGER.trace(() -> String.format("Block minter %s up to %d minted block%s (rowCount: %d)", accountData.getAddress(), accountData.getBlocksMinted(), (accountData.getBlocksMinted() != 1 ? "s" : ""), rowCount));
}
// We are only interested in accounts that are NOT already highest level
@@ -1425,35 +1441,40 @@ public class Block {
public void orphan() throws DataException {
LOGGER.trace(() -> String.format("Orphaning block %d", this.blockData.getHeight()));
// Return AT fees and delete AT states from repository
orphanAtFeesAndStates();
this.repository.setDebug(false);
try {
// Return AT fees and delete AT states from repository
orphanAtFeesAndStates();
// Orphan, and unlink, transactions from this block
orphanTransactionsFromBlock();
// Orphan, and unlink, transactions from this block
orphanTransactionsFromBlock();
// Undo any group-approval decisions that happen at this block
orphanGroupApprovalTransactions();
// Undo any group-approval decisions that happen at this block
orphanGroupApprovalTransactions();
if (this.blockData.getHeight() > 1) {
// Invalidate expandedAccounts as they may have changed due to orphaning TRANSFER_PRIVS transactions, etc.
this.cachedExpandedAccounts = null;
if (this.blockData.getHeight() > 1) {
// Invalidate expandedAccounts as they may have changed due to orphaning TRANSFER_PRIVS transactions, etc.
this.cachedExpandedAccounts = null;
// Deduct any transaction fees from minter/reward-share account(s)
deductTransactionFees();
// Deduct any transaction fees from minter/reward-share account(s)
deductTransactionFees();
// Block rewards removed after transactions undone
orphanBlockRewards();
// Block rewards removed after transactions undone
orphanBlockRewards();
// Decrease account levels
decreaseAccountLevels();
// Decrease account levels
decreaseAccountLevels();
}
// Delete orphaned balances
this.repository.getAccountRepository().deleteBalancesFromHeight(this.blockData.getHeight());
// Delete block from blockchain
this.repository.getBlockRepository().delete(this.blockData);
this.blockData.setHeight(null);
} finally {
this.repository.setDebug(false);
}
// Delete orphaned balances
this.repository.getAccountRepository().deleteBalancesFromHeight(this.blockData.getHeight());
// Delete block from blockchain
this.repository.getBlockRepository().delete(this.blockData);
this.blockData.setHeight(null);
}
protected void orphanTransactionsFromBlock() throws DataException {
@@ -1571,8 +1592,9 @@ public class Block {
AccountData accountData = getAccountData.apply(expandedAccount);
accountData.setBlocksMinted(accountData.getBlocksMinted() - 1);
repository.getAccountRepository().setMintedBlockCount(accountData);
LOGGER.trace(() -> String.format("Block minter %s down to %d minted block%s", accountData.getAddress(), accountData.getBlocksMinted(), (accountData.getBlocksMinted() != 1 ? "s" : "")));
// repository.getAccountRepository().setMintedBlockCount(accountData); int rowCount = 1; // Until HSQLDB rev 6100 is fixed
int rowCount = repository.getAccountRepository().modifyMintedBlockCount(accountData.getAddress(), -1);
LOGGER.trace(() -> String.format("Block minter %s down to %d minted block%s (rowCount: %d)", accountData.getAddress(), accountData.getBlocksMinted(), (accountData.getBlocksMinted() != 1 ? "s" : ""), rowCount));
}
// We are only interested in accounts that are NOT already lowest level
@@ -1602,8 +1624,22 @@ public class Block {
protected void distributeBlockReward(BigDecimal totalAmount) throws DataException {
LOGGER.trace(() -> String.format("Distributing: %s", totalAmount.toPlainString()));
List<ShareByLevel> sharesByLevel = BlockChain.getInstance().getBlockSharesByLevel();
// Distribute according to account level
BigDecimal sharedByLevelAmount = distributeBlockRewardByLevel(totalAmount);
LOGGER.trace(() -> String.format("Shared %s of %s based on account levels", sharedByLevelAmount.toPlainString(), totalAmount.toPlainString()));
// Distribute amongst legacy QORA holders
BigDecimal sharedByQoraHoldersAmount = distributeBlockRewardToQoraHolders(totalAmount);
LOGGER.trace(() -> String.format("Shared %s of %s to legacy QORA holders", sharedByQoraHoldersAmount.toPlainString(), totalAmount.toPlainString()));
// Spread remainder across founder accounts
BigDecimal foundersAmount = totalAmount.subtract(sharedByLevelAmount).subtract(sharedByQoraHoldersAmount);
distributeBlockRewardToFounders(foundersAmount);
}
private BigDecimal distributeBlockRewardByLevel(BigDecimal totalAmount) throws DataException {
List<ExpandedAccount> expandedAccounts = this.getExpandedAccounts();
List<ShareByLevel> sharesByLevel = BlockChain.getInstance().getBlockSharesByLevel();
// Distribute amount across bins
BigDecimal sharedAmount = BigDecimal.ZERO;
@@ -1628,36 +1664,17 @@ public class Block {
}
}
// Distribute share across legacy QORA holders
return sharedAmount;
}
private BigDecimal distributeBlockRewardToQoraHolders(BigDecimal totalAmount) throws DataException {
BigDecimal qoraHoldersAmount = BlockChain.getInstance().getQoraHoldersShare().multiply(totalAmount).setScale(8, RoundingMode.DOWN);
LOGGER.trace(() -> String.format("Legacy QORA holders share of %s: %s", totalAmount.toPlainString(), qoraHoldersAmount.toPlainString()));
List<AccountBalanceData> qoraHolders = this.repository.getAccountRepository().getAssetBalances(Asset.LEGACY_QORA, true);
final boolean isProcessingNotOrphaning = totalAmount.signum() >= 0;
// Filter out qoraHolders who have received max QORT due to holding legacy QORA, (ratio from blockchain config)
BigDecimal qoraPerQortReward = BlockChain.getInstance().getQoraPerQortReward();
Iterator<AccountBalanceData> qoraHoldersIterator = qoraHolders.iterator();
while (qoraHoldersIterator.hasNext()) {
AccountBalanceData qoraHolder = qoraHoldersIterator.next();
Account qoraHolderAccount = new Account(repository, qoraHolder.getAddress());
BigDecimal qortFromQora = qoraHolderAccount.getConfirmedBalance(Asset.QORT_FROM_QORA);
// If we're processing a block, then totalAmount will be positive
if (totalAmount.signum() >= 0) {
BigDecimal maxQortFromQora = qoraHolder.getBalance().divide(qoraPerQortReward, RoundingMode.DOWN);
// Disregard qora holders who have already received maximum qort from holding legacy qora
if (qortFromQora.compareTo(maxQortFromQora) >= 0)
qoraHoldersIterator.remove();
} else {
// We're orphaning a block
// so disregard qora holders who have already had their final qort-from-qora reward (i.e. reward reward block is earlier than this one)
QortFromQoraData qortFromQoraData = this.repository.getAccountRepository().getQortFromQoraInfo(qoraHolder.getAddress());
if (qortFromQoraData != null && qortFromQoraData.getFinalBlockHeight() < this.blockData.getHeight())
qoraHoldersIterator.remove();
}
}
List<AccountBalanceData> qoraHolders = this.repository.getAccountRepository().getEligibleLegacyQoraHolders(isProcessingNotOrphaning ? null : this.blockData.getHeight());
BigDecimal totalQoraHeld = BigDecimal.ZERO;
for (int i = 0; i < qoraHolders.size(); ++i)
@@ -1666,6 +1683,7 @@ public class Block {
BigDecimal finalTotalQoraHeld = totalQoraHeld;
LOGGER.trace(() -> String.format("Total legacy QORA held: %s", finalTotalQoraHeld.toPlainString()));
BigDecimal sharedAmount = BigDecimal.ZERO;
for (int h = 0; h < qoraHolders.size(); ++h) {
AccountBalanceData qoraHolder = qoraHolders.get(h);
@@ -1674,12 +1692,16 @@ public class Block {
LOGGER.trace(() -> String.format("QORA holder %s has %s / %s QORA so share: %s",
qoraHolder.getAddress(), qoraHolder.getBalance().toPlainString(), finalTotalQoraHeld, finalHolderReward.toPlainString()));
// Too small to register this time?
if (holderReward.signum() == 0)
continue;
Account qoraHolderAccount = new Account(repository, qoraHolder.getAddress());
BigDecimal newQortFromQoraBalance = qoraHolderAccount.getConfirmedBalance(Asset.QORT_FROM_QORA).add(holderReward);
// If processing, make sure we don't overpay
if (totalAmount.signum() >= 0) {
if (isProcessingNotOrphaning) {
BigDecimal maxQortFromQora = qoraHolder.getBalance().divide(qoraPerQortReward, RoundingMode.DOWN);
if (newQortFromQoraBalance.compareTo(maxQortFromQora) >= 0) {
@@ -1689,7 +1711,7 @@ public class Block {
holderReward = holderReward.subtract(adjustment);
newQortFromQoraBalance = newQortFromQoraBalance.subtract(adjustment);
// This is also qora holders final qort-from-qora block
// This is also the QORA holder's final QORT-from-QORA block
QortFromQoraData qortFromQoraData = new QortFromQoraData(qoraHolder.getAddress(), holderReward, this.blockData.getHeight());
this.repository.getAccountRepository().save(qortFromQoraData);
@@ -1701,9 +1723,10 @@ public class Block {
// Orphaning
QortFromQoraData qortFromQoraData = this.repository.getAccountRepository().getQortFromQoraInfo(qoraHolder.getAddress());
if (qortFromQoraData != null) {
// Note use of negate() here as qortFromQora will be negative during orphaning,
// but final qort-from-qora is stored in repository during processing (and hence positive).
BigDecimal adjustment = holderReward.subtract(qortFromQoraData.getFinalQortFromQora().negate());
// Final QORT-from-QORA amount from repository was stored during processing, and hence positive.
// So we use add() here as qortFromQora is negative during orphaning.
// More efficient than holderReward.subtract(final-qort-from-qora.negate())
BigDecimal adjustment = holderReward.add(qortFromQoraData.getFinalQortFromQora());
holderReward = holderReward.subtract(adjustment);
newQortFromQoraBalance = newQortFromQoraBalance.subtract(adjustment);
@@ -1716,7 +1739,8 @@ public class Block {
}
}
qoraHolderAccount.setConfirmedBalance(Asset.QORT, qoraHolderAccount.getConfirmedBalance(Asset.QORT).add(holderReward));
// qoraHolderAccount.setConfirmedBalance(Asset.QORT, qoraHolderAccount.getConfirmedBalance(Asset.QORT).add(holderReward));
this.repository.getAccountRepository().modifyAssetBalance(qoraHolder.getAddress(), Asset.QORT, holderReward);
if (newQortFromQoraBalance.signum() > 0)
qoraHolderAccount.setConfirmedBalance(Asset.QORT_FROM_QORA, newQortFromQoraBalance);
@@ -1727,27 +1751,39 @@ public class Block {
sharedAmount = sharedAmount.add(holderReward);
}
// Spread remainder across founder accounts
BigDecimal foundersAmount = totalAmount.subtract(sharedAmount);
BigDecimal finalSharedAmount = sharedAmount;
return sharedAmount;
}
private void distributeBlockRewardToFounders(BigDecimal foundersAmount) throws DataException {
// Remaining reward portion is spread across all founders, online or not
List<AccountData> founderAccounts = this.repository.getAccountRepository().getFlaggedAccounts(Account.FOUNDER_FLAG);
BigDecimal foundersCount = BigDecimal.valueOf(founderAccounts.size());
BigDecimal perFounderAmount = foundersAmount.divide(foundersCount, RoundingMode.DOWN);
LOGGER.trace(() -> String.format("Shared %s of %s, remaining %s to %d founder%s, %s each",
finalSharedAmount.toPlainString(), totalAmount.toPlainString(),
LOGGER.trace(() -> String.format("Sharing remaining %s to %d founder%s, %s each",
foundersAmount.toPlainString(), founderAccounts.size(), (founderAccounts.size() != 1 ? "s" : ""),
perFounderAmount.toPlainString()));
List<ExpandedAccount> expandedAccounts = this.getExpandedAccounts();
for (int a = 0; a < founderAccounts.size(); ++a) {
Account founderAccount = new Account(this.repository, founderAccounts.get(a).getAddress());
// If founder is minter in any online reward-shares then founder's amount is spread across these, otherwise founder gets whole amount.
/* Fixed version:
List<ExpandedAccount> founderExpandedAccounts = expandedAccounts.stream().filter(
accountInfo -> accountInfo.isMinterFounder &&
accountInfo.mintingAccountData.getAddress().equals(founderAccount.getAddress())
).collect(Collectors.toList());
*/
// Broken version:
List<ExpandedAccount> founderExpandedAccounts = expandedAccounts.stream().filter(accountInfo -> accountInfo.isMinterFounder).collect(Collectors.toList());
if (founderExpandedAccounts.isEmpty()) {
// Simple case: no founder-as-minter reward-shares online so founder gets whole amount.
Account founderAccount = new Account(this.repository, founderAccounts.get(a).getAddress());
founderAccount.setConfirmedBalance(Asset.QORT, founderAccount.getConfirmedBalance(Asset.QORT).add(perFounderAmount));
// founderAccount.setConfirmedBalance(Asset.QORT, founderAccount.getConfirmedBalance(Asset.QORT).add(perFounderAmount));
this.repository.getAccountRepository().modifyAssetBalance(founderAccount.getAddress(), Asset.QORT, perFounderAmount);
} else {
// Distribute over reward-shares
BigDecimal perFounderRewardShareAmount = perFounderAmount.divide(BigDecimal.valueOf(founderExpandedAccounts.size()), RoundingMode.DOWN);

View File

@@ -137,19 +137,18 @@ public class BlockMinter extends Thread {
// Disregard peers that have "misbehaved" recently
peers.removeIf(Controller.hasMisbehaved);
// Don't mint if we don't have enough connected peers as where would the transactions/consensus come from?
if (peers.size() < Settings.getInstance().getMinBlockchainPeers())
continue;
// Disregard peers that don't have a recent block
peers.removeIf(Controller.hasNoRecentBlock);
// If we have any peers with a recent block, but our latest block isn't recent
// then we need to synchronize instead of minting.
// Don't mint if we don't have enough up-to-date peers as where would the transactions/consensus come from?
if (peers.size() < Settings.getInstance().getMinBlockchainPeers())
continue;
// If our latest block isn't recent then we need to synchronize instead of minting.
if (!peers.isEmpty() && lastBlockData.getTimestamp() < minLatestBlockTimestamp)
continue;
// There are no peers with a recent block and/or our latest block is recent
// There are enough peers with a recent block and our latest block is recent
// so go ahead and mint a block if possible.
isMintingPossible = true;
@@ -341,17 +340,19 @@ public class BlockMinter extends Thread {
this.interrupt();
}
public static void mintTestingBlock(Repository repository, PrivateKeyAccount mintingAccount) throws DataException {
public static void mintTestingBlock(Repository repository, PrivateKeyAccount... mintingAndOnlineAccounts) throws DataException {
if (!BlockChain.getInstance().isTestChain()) {
LOGGER.warn("Ignoring attempt to mint testing block for non-test chain!");
return;
}
// Ensure mintingAccount is 'online' so blocks can be minted
Controller.getInstance().ensureTestingAccountOnline(mintingAccount);
Controller.getInstance().ensureTestingAccountsOnline(mintingAndOnlineAccounts);
BlockData previousBlockData = repository.getBlockRepository().getLastBlock();
PrivateKeyAccount mintingAccount = mintingAndOnlineAccounts[0];
Block newBlock = Block.mint(repository, previousBlockData, mintingAccount);
// Make sure we're the only thread modifying the blockchain

View File

@@ -231,6 +231,10 @@ public class AutoUpdate extends Thread {
// JVM arguments
javaCmd.addAll(ManagementFactory.getRuntimeMXBean().getInputArguments());
// Remove JNI options as they won't be supported by command-line 'java'
// These are typically added by the AdvancedInstaller Java launcher EXE
javaCmd.removeAll(Arrays.asList("abort", "exit", "vfprintf"));
// Call ApplyUpdate using new JAR
javaCmd.addAll(Arrays.asList("-cp", NEW_JAR_FILENAME, ApplyUpdate.class.getCanonicalName()));

View File

@@ -1,5 +1,6 @@
package org.qortal.controller;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.security.SecureRandom;
@@ -77,7 +78,6 @@ import org.qortal.transaction.ArbitraryTransaction;
import org.qortal.transaction.Transaction;
import org.qortal.transaction.Transaction.TransactionType;
import org.qortal.transaction.Transaction.ValidationResult;
import org.qortal.ui.UiService;
import org.qortal.utils.Base58;
import org.qortal.utils.ByteArray;
import org.qortal.utils.NTP;
@@ -100,7 +100,7 @@ public class Controller extends Thread {
private static final long MISBEHAVIOUR_COOLOFF = 10 * 60 * 1000L; // ms
private static final int MAX_BLOCKCHAIN_TIP_AGE = 5; // blocks
private static final Object shutdownLock = new Object();
private static final String repositoryUrlTemplate = "jdbc:hsqldb:file:%s/blockchain;create=true;hsqldb.full_log_replay=true";
private static final String repositoryUrlTemplate = "jdbc:hsqldb:file:%s" + File.separator + "blockchain;create=true;hsqldb.full_log_replay=true";
private static final long ARBITRARY_REQUEST_TIMEOUT = 5 * 1000L; // ms
private static final long REPOSITORY_BACKUP_PERIOD = 123 * 60 * 1000L; // ms
private static final long NTP_PRE_SYNC_CHECK_PERIOD = 5 * 1000L; // ms
@@ -133,6 +133,9 @@ public class Controller extends Thread {
/** Whether we can mint new blocks, as reported by BlockMinter. */
private volatile boolean isMintingPossible = false;
/** Whether we are attempting to synchronize. */
private volatile boolean isSynchronizing = false;
/** Latest block signatures from other peers that we know are on inferior chains. */
List<ByteArray> inferiorChainSignatures = new ArrayList<>();
@@ -241,6 +244,19 @@ public class Controller extends Thread {
return this.savedArgs;
}
/* package */ static boolean isStopping() {
return isStopping;
}
// For API use
public boolean isMintingPossible() {
return this.isMintingPossible;
}
public boolean isSynchronizing() {
return this.isSynchronizing;
}
// Entry point
public static void main(String[] args) {
@@ -311,6 +327,7 @@ public class Controller extends Thread {
network.start();
} catch (IOException e) {
LOGGER.error("Unable to start networking", e);
Controller.getInstance().shutdown();
Gui.getInstance().fatalError("Networking failure", e);
return; // Not System.exit() so that GUI can display error
}
@@ -344,20 +361,11 @@ public class Controller extends Thread {
apiService.start();
} catch (Exception e) {
LOGGER.error("Unable to start API", e);
Controller.getInstance().shutdown();
Gui.getInstance().fatalError("API failure", e);
return; // Not System.exit() so that GUI can display error
}
LOGGER.info(String.format("Starting node management UI on port %d", Settings.getInstance().getUiPort()));
try {
UiService uiService = UiService.getInstance();
uiService.start();
} catch (Exception e) {
LOGGER.error("Unable to start node management UI", e);
Gui.getInstance().fatalError("Node management UI failure", e);
return; // Not System.exit() so that GUI can display error
}
// If GUI is enabled, we're no longer starting up but actually running now
Gui.getInstance().notifyRunning();
}
@@ -501,7 +509,13 @@ public class Controller extends Thread {
int index = new SecureRandom().nextInt(peers.size());
Peer peer = peers.get(index);
isSynchronizing = true;
updateSysTray();
actuallySynchronize(peer, false);
isSynchronizing = false;
requestSysTrayUpdate = true;
}
public SynchronizationResult actuallySynchronize(Peer peer, boolean force) throws InterruptedException {
@@ -553,6 +567,10 @@ public class Controller extends Thread {
LOGGER.debug(() -> String.format("Failed to synchronize with peer %s (%s)", peer, syncResult.name()));
break;
case SHUTTING_DOWN:
// Just quietly exit
break;
case OK:
requestSysTrayUpdate = true;
// fall-through...
@@ -601,9 +619,17 @@ public class Controller extends Thread {
String connectionsText = Translator.INSTANCE.translate("SysTray", numberOfPeers != 1 ? "CONNECTIONS" : "CONNECTION");
String heightText = Translator.INSTANCE.translate("SysTray", "BLOCK_HEIGHT");
String mintingText = Translator.INSTANCE.translate("SysTray", isMintingPossible ? "MINTING_ENABLED" : "MINTING_DISABLED");
String tooltip = String.format("%s - %d %s - %s %d", mintingText, numberOfPeers, connectionsText, heightText, height);
String actionKey;
if (isMintingPossible)
actionKey = "MINTING_ENABLED";
else if (isSynchronizing)
actionKey = "SYNCHRONIZING_BLOCKCHAIN";
else
actionKey = "MINTING_DISABLED";
String actionText = Translator.INSTANCE.translate("SysTray", actionKey);
String tooltip = String.format("%s - %d %s - %s %d", actionText, numberOfPeers, connectionsText, heightText, height);
SysTray.getInstance().setToolTipText(tooltip);
}
@@ -638,9 +664,6 @@ public class Controller extends Thread {
if (!isStopping) {
isStopping = true;
LOGGER.info("Shutting down node management UI");
UiService.getInstance().stop();
LOGGER.info("Shutting down API");
ApiService.getInstance().stop();
@@ -1345,7 +1368,7 @@ public class Controller extends Thread {
}
}
public void ensureTestingAccountOnline(PrivateKeyAccount mintingAccount) {
public void ensureTestingAccountsOnline(PrivateKeyAccount... onlineAccounts) {
if (!BlockChain.getInstance().isTestChain()) {
LOGGER.warn("Ignoring attempt to ensure test account is online for non-test chain!");
return;
@@ -1355,19 +1378,21 @@ public class Controller extends Thread {
if (now == null)
return;
// Check mintingAccount is actually reward-share?
// Add reward-share & timestamp to online accounts
final long onlineAccountsTimestamp = Controller.toOnlineAccountTimestamp(now);
byte[] timestampBytes = Longs.toByteArray(onlineAccountsTimestamp);
byte[] signature = mintingAccount.sign(timestampBytes);
byte[] publicKey = mintingAccount.getPublicKey();
OnlineAccountData ourOnlineAccountData = new OnlineAccountData(onlineAccountsTimestamp, signature, publicKey);
synchronized (this.onlineAccounts) {
this.onlineAccounts.clear();
this.onlineAccounts.add(ourOnlineAccountData);
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);
}
}
}

View File

@@ -3,7 +3,7 @@ package org.qortal.controller;
import java.math.BigInteger;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.util.Collections;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.locks.ReentrantLock;
import java.util.stream.Collectors;
@@ -39,13 +39,13 @@ public class Synchronizer {
private static final int INITIAL_BLOCK_STEP = 8;
private static final int MAXIMUM_BLOCK_STEP = 500;
private static final int MAXIMUM_COMMON_DELTA = 1440; // XXX move to Settings?
private static final int MAXIMUM_COMMON_DELTA = 240; // XXX move to Settings?
private static final int SYNC_BATCH_SIZE = 200;
private static Synchronizer instance;
public enum SynchronizationResult {
OK, NOTHING_TO_DO, GENESIS_ONLY, NO_COMMON_BLOCK, TOO_DIVERGENT, NO_REPLY, INFERIOR_CHAIN, INVALID_DATA, NO_BLOCKCHAIN_LOCK, REPOSITORY_ISSUE;
OK, NOTHING_TO_DO, GENESIS_ONLY, NO_COMMON_BLOCK, TOO_DIVERGENT, NO_REPLY, INFERIOR_CHAIN, INVALID_DATA, NO_BLOCKCHAIN_LOCK, REPOSITORY_ISSUE, SHUTTING_DOWN;
}
// Constructors
@@ -93,15 +93,11 @@ public class Synchronizer {
peerHeight, Base58.encode(peersLastBlockSignature), peer.getChainTipData().getLastBlockTimestamp(),
ourInitialHeight, Base58.encode(ourLastBlockSignature), ourLatestBlockData.getTimestamp()));
List<BlockSummaryData> peerBlockSummaries = fetchSummariesFromCommonBlock(repository, peer, ourInitialHeight);
if (peerBlockSummaries == null) {
LOGGER.info(String.format("Error while trying to find common block with peer %s", peer));
return SynchronizationResult.NO_REPLY;
}
if (peerBlockSummaries.isEmpty()) {
LOGGER.info(String.format("Failure to find common block with peer %s", peer));
return SynchronizationResult.NO_COMMON_BLOCK;
}
List<BlockSummaryData> peerBlockSummaries = new ArrayList<>();
SynchronizationResult findCommonBlockResult = fetchSummariesFromCommonBlock(repository, peer, ourInitialHeight, force, peerBlockSummaries);
if (findCommonBlockResult != SynchronizationResult.OK)
// Logging performed by fetchSummariesFromCommonBlock() above
return findCommonBlockResult;
// First summary is common block
final BlockData commonBlockData = repository.getBlockRepository().fromSignature(peerBlockSummaries.get(0).getSignature());
@@ -129,13 +125,6 @@ public class Synchronizer {
return SynchronizationResult.NOTHING_TO_DO;
}
// If common block is too far behind us then we're on massively different forks so give up.
int minCommonHeight = ourInitialHeight - MAXIMUM_COMMON_DELTA;
if (!force && commonBlockHeight < minCommonHeight) {
LOGGER.info(String.format("Blockchain too divergent with peer %s", peer));
return SynchronizationResult.TOO_DIVERGENT;
}
// Unless we're doing a forced sync, we might need to compare blocks after common block
if (!force && ourInitialHeight > commonBlockHeight) {
// If our latest block is very old, we're very behind and should ditch our fork.
@@ -154,6 +143,9 @@ public class Synchronizer {
int peerBlockCount = peerHeight - commonBlockHeight;
while (peerBlockSummaries.size() < peerBlockCount) {
if (Controller.isStopping())
return SynchronizationResult.SHUTTING_DOWN;
int lastSummaryHeight = commonBlockHeight + peerBlockSummaries.size();
byte[] previousSignature;
if (peerBlockSummaries.isEmpty())
@@ -212,6 +204,9 @@ public class Synchronizer {
LOGGER.debug(String.format("Orphaning blocks back to common block height %d, sig %.8s", commonBlockHeight, commonBlockSig58));
while (ourHeight > commonBlockHeight) {
if (Controller.isStopping())
return SynchronizationResult.SHUTTING_DOWN;
BlockData blockData = repository.getBlockRepository().fromHeight(ourHeight);
Block block = new Block(repository, blockData);
block.orphan();
@@ -232,6 +227,9 @@ public class Synchronizer {
List<byte[]> peerBlockSignatures = peerBlockSummaries.stream().map(BlockSummaryData::getSignature).collect(Collectors.toList());
while (ourHeight < peerHeight && ourHeight < maxBatchHeight) {
if (Controller.isStopping())
return SynchronizationResult.SHUTTING_DOWN;
// Do we need more signatures?
if (peerBlockSignatures.isEmpty()) {
int numberRequested = maxBatchHeight - ourHeight;
@@ -320,45 +318,59 @@ public class Synchronizer {
* @throws DataException
* @throws InterruptedException
*/
private List<BlockSummaryData> fetchSummariesFromCommonBlock(Repository repository, Peer peer, int ourHeight) throws DataException, InterruptedException {
private SynchronizationResult fetchSummariesFromCommonBlock(Repository repository, Peer peer, int ourHeight, boolean force, List<BlockSummaryData> blockSummariesFromCommon) throws DataException, InterruptedException {
// Start by asking for a few recent block hashes as this will cover a majority of reorgs
// Failing that, back off exponentially
int step = INITIAL_BLOCK_STEP;
List<BlockSummaryData> blockSummaries = null;
int testHeight = Math.max(ourHeight - step, 1);
BlockData testBlockData = null;
List<BlockSummaryData> blockSummariesBatch = null;
while (testHeight >= 1) {
// Are we shutting down?
if (Controller.isStopping())
return SynchronizationResult.SHUTTING_DOWN;
// Fetch our block signature at this height
testBlockData = repository.getBlockRepository().fromHeight(testHeight);
if (testBlockData == null) {
// Not found? But we've locked the blockchain and height is below blockchain's tip!
LOGGER.error("Failed to get block at height lower than blockchain tip during synchronization?");
return null;
return SynchronizationResult.REPOSITORY_ISSUE;
}
// Ask for block signatures since test block's signature
byte[] testSignature = testBlockData.getSignature();
LOGGER.trace(String.format("Requesting %d summar%s after height %d", step, (step != 1 ? "ies": "y"), testHeight));
blockSummaries = this.getBlockSummaries(peer, testSignature, step);
blockSummariesBatch = this.getBlockSummaries(peer, testSignature, step);
if (blockSummaries == null)
if (blockSummariesBatch == null) {
// No response - give up this time
return null;
LOGGER.info(String.format("Error while trying to find common block with peer %s", peer));
return SynchronizationResult.NO_REPLY;
}
LOGGER.trace(String.format("Received %s summar%s", blockSummaries.size(), (blockSummaries.size() != 1 ? "ies" : "y")));
LOGGER.trace(String.format("Received %s summar%s", blockSummariesBatch.size(), (blockSummariesBatch.size() != 1 ? "ies" : "y")));
// Empty list means remote peer is unaware of test signature OR has no new blocks after test signature
if (!blockSummaries.isEmpty())
if (!blockSummariesBatch.isEmpty())
// We have entries so we have found a common block
break;
// No blocks after genesis block?
// We don't get called for a peer at genesis height so this means NO blocks in common
if (testHeight == 1)
return Collections.emptyList();
if (testHeight == 1) {
LOGGER.info(String.format("Failure to find common block with peer %s", peer));
return SynchronizationResult.NO_COMMON_BLOCK;
}
// If common block is too far behind us then we're on massively different forks so give up.
if (!force && testHeight < ourHeight - MAXIMUM_COMMON_DELTA) {
LOGGER.info(String.format("Blockchain too divergent with peer %s", peer));
return SynchronizationResult.TOO_DIVERGENT;
}
if (peer.getVersion() >= 2) {
step <<= 1;
@@ -373,20 +385,21 @@ public class Synchronizer {
// Prepend test block's summary as first block summary, as summaries returned are *after* test block
BlockSummaryData testBlockSummary = new BlockSummaryData(testBlockData);
blockSummaries.add(0, testBlockSummary);
blockSummariesFromCommon.add(0, testBlockSummary);
blockSummariesFromCommon.addAll(blockSummariesBatch);
// Trim summaries so that first summary is common block.
// Currently we work back from the end until we hit a block we also have.
// TODO: rewrite as modified binary search!
for (int i = blockSummaries.size() - 1; i > 0; --i) {
if (repository.getBlockRepository().exists(blockSummaries.get(i).getSignature())) {
for (int i = blockSummariesFromCommon.size() - 1; i > 0; --i) {
if (repository.getBlockRepository().exists(blockSummariesFromCommon.get(i).getSignature())) {
// Note: index i isn't cleared: List.subList is fromIndex inclusive to toIndex exclusive
blockSummaries.subList(0, i).clear();
blockSummariesFromCommon.subList(0, i).clear();
break;
}
}
return blockSummaries;
return SynchronizationResult.OK;
}
private List<BlockSummaryData> getBlockSummaries(Peer peer, byte[] parentSignature, int numberRequested) throws InterruptedException {

View File

@@ -10,11 +10,14 @@ import java.awt.event.WindowEvent;
import java.awt.event.WindowFocusListener;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetSocketAddress;
import java.net.URL;
import java.nio.channels.SocketChannel;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -30,7 +33,7 @@ import org.apache.logging.log4j.Logger;
import org.qortal.controller.Controller;
import org.qortal.globalization.Translator;
import org.qortal.settings.Settings;
import org.qortal.ui.UiService;
import org.qortal.utils.RandomizeList;
import org.qortal.utils.URLViewer;
public class SysTray {
@@ -144,15 +147,11 @@ public class SysTray {
}
});
JMenuItem openUi = new JMenuItem(Translator.INSTANCE.translate("SysTray", "OPEN_NODE_UI"));
JMenuItem openUi = new JMenuItem(Translator.INSTANCE.translate("SysTray", "OPEN_UI"));
openUi.addActionListener(actionEvent -> {
destroyHiddenDialog();
try {
URLViewer.openWebpage(new URL("http://localhost:" + Settings.getInstance().getUiPort()));
} catch (Exception e) {
LOGGER.error("Unable to open node UI in browser");
}
new OpenUiWorker().execute();
});
menu.add(openUi);
@@ -174,7 +173,7 @@ public class SysTray {
syncTime.addActionListener(actionEvent -> {
destroyHiddenDialog();
new SynchronizeWorker().execute();
new SynchronizeClockWorker().execute();
});
menu.add(syncTime);
}
@@ -190,11 +189,53 @@ public class SysTray {
return menu;
}
class SynchronizeWorker extends SwingWorker<Void, Void> {
static class OpenUiWorker extends SwingWorker<Void, Void> {
@Override
protected Void doInBackground() {
List<String> uiServers = new ArrayList<>();
String[] remoteUiServers = Settings.getInstance().getRemoteUiServers();
uiServers.addAll(Arrays.asList(remoteUiServers));
// Randomize remote servers
uiServers = RandomizeList.randomize(uiServers);
// Prepend local servers
String[] localUiServers = Settings.getInstance().getLocalUiServers();
uiServers.addAll(0, Arrays.asList(localUiServers));
// Check each server in turn before opening browser tab
int uiPort = Settings.getInstance().getUiServerPort();
for (String uiServer : uiServers) {
InetSocketAddress socketAddress = new InetSocketAddress(uiServer, uiPort);
// If we couldn't resolve try next
if (socketAddress.isUnresolved())
continue;
try (SocketChannel socketChannel = SocketChannel.open()) {
socketChannel.socket().connect(socketAddress, 100);
// If we reach here, then socket connected to UI server!
URLViewer.openWebpage(new URL(String.format("http://%s:%d", uiServer, uiPort)));
return null;
} catch (IOException e) {
// try next server
} catch (Exception e) {
LOGGER.error("Unable to open UI website in browser");
return null;
}
}
return null;
}
}
static class SynchronizeClockWorker extends SwingWorker<Void, Void> {
@Override
protected Void doInBackground() {
// Extract reconfiguration script from resources
String resourceName = "/" + UiService.DOWNLOADS_RESOURCE_PATH + "/" + NTP_SCRIPT;
String resourceName = "/node-management/" + NTP_SCRIPT;
Path scriptPath = Paths.get(NTP_SCRIPT);
try (InputStream in = SysTray.class.getResourceAsStream(resourceName)) {
@@ -218,7 +259,7 @@ public class SysTray {
}
}
class ClosingWorker extends SwingWorker<Void, Void> {
static class ClosingWorker extends SwingWorker<Void, Void> {
@Override
protected Void doInBackground() {
Controller.getInstance().shutdown();

View File

@@ -56,6 +56,7 @@ import org.qortal.repository.Repository;
import org.qortal.repository.RepositoryManager;
import org.qortal.settings.Settings;
import org.qortal.utils.ExecuteProduceConsume;
import org.qortal.utils.ExecuteProduceConsume.StatsSnapshot;
import org.qortal.utils.NTP;
// For managing peers
@@ -88,7 +89,10 @@ public class Network {
"node4.qortal.org",
"node5.qortal.org",
"node6.qortal.org",
"node7.qortal.org"
"node7.qortal.org",
"node8.qortal.org",
"node9.qortal.org",
"node10.qortal.org"
};
public static final int MAX_SIGNATURES_PER_REPLY = 500;
@@ -196,6 +200,10 @@ public class Network {
return this.maxMessageSize;
}
public StatsSnapshot getStatsSnapshot() {
return this.networkEPC.getStatsSnapshot();
}
// Peer lists
public List<Peer> getConnectedPeers() {
@@ -478,18 +486,20 @@ public class Network {
try {
if (now == null) {
LOGGER.debug(String.format("Connection discarded from peer %s due to lack of NTP sync", socketChannel.getRemoteAddress()));
LOGGER.debug(() -> String.format("Connection discarded from peer %s due to lack of NTP sync", PeerAddress.fromSocket(socketChannel.socket())));
socketChannel.close();
return;
}
synchronized (this.connectedPeers) {
if (connectedPeers.size() >= maxPeers) {
// We have enough peers
LOGGER.debug(String.format("Connection discarded from peer %s", socketChannel.getRemoteAddress()));
LOGGER.debug(() -> String.format("Connection discarded from peer %s", PeerAddress.fromSocket(socketChannel.socket())));
socketChannel.close();
return;
}
LOGGER.debug(String.format("Connection accepted from peer %s", socketChannel.getRemoteAddress()));
LOGGER.debug(() -> String.format("Connection accepted from peer %s", PeerAddress.fromSocket(socketChannel.socket())));
newPeer = new Peer(socketChannel);
this.connectedPeers.add(newPeer);

View File

@@ -61,6 +61,8 @@ public class Peer {
private InetSocketAddress resolvedAddress = null;
/** True if remote address is loopback/link-local/site-local, false otherwise. */
private boolean isLocal;
private final Object byteBufferLock = new Object();
private volatile ByteBuffer byteBuffer;
private Map<Integer, BlockingQueue<Message>> replyQueues;
private LinkedBlockingQueue<Message> pendingMessages;
@@ -256,7 +258,7 @@ public class Peer {
this.connectionTimestamp = NTP.getTime();
this.socketChannel.setOption(StandardSocketOptions.TCP_NODELAY, true);
this.socketChannel.configureBlocking(false);
this.byteBuffer = ByteBuffer.allocate(Network.getInstance().getMaxMessageSize());
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.pendingMessages = new LinkedBlockingQueue<>();
}
@@ -292,11 +294,15 @@ public class Peer {
* @throws IOException
*/
/* package */ void readChannel() throws IOException {
synchronized (this.byteBuffer) {
synchronized (this.byteBufferLock) {
while(true) {
if (!this.socketChannel.isOpen() || this.socketChannel.socket().isClosed())
return;
// Do we need to allocate byteBuffer?
if (this.byteBuffer == null)
this.byteBuffer = ByteBuffer.allocate(Network.getInstance().getMaxMessageSize());
final int bytesRead = this.socketChannel.read(this.byteBuffer);
if (bytesRead == -1) {
this.disconnect("EOF");
@@ -318,9 +324,15 @@ public class Peer {
return;
}
if (message == null && bytesRead == 0 && !wasByteBufferFull)
if (message == null && bytesRead == 0 && !wasByteBufferFull) {
// No complete message in buffer, no more bytes to read from socket even though there was room to read bytes
// If byteBuffer is empty then we can deallocate it, to save memory, albeit costing GC
if (this.byteBuffer.remaining() == this.byteBuffer.capacity())
this.byteBuffer = null;
return;
}
if (message == null)
// No complete message in buffer, but maybe more bytes to read from socket
@@ -452,7 +464,7 @@ public class Peer {
}
/* package */ void startPings() {
// Replacing initial null value allows pingCheck() to start sending pings.
// Replacing initial null value allows getPingTask() to start sending pings.
LOGGER.trace(() -> String.format("Enabling pings for peer %s", this));
this.lastPingSent = System.currentTimeMillis();
}
@@ -477,6 +489,7 @@ public class Peer {
final long after = System.currentTimeMillis();
if (message == null || message.getType() != MessageType.PING) {
LOGGER.debug(() -> String.format("Didn't receive reply from %s for PING ID %d", this, pingMessage.getId()));
this.disconnect("no ping received");
return;
}

View File

@@ -1,5 +1,6 @@
package org.qortal.repository;
import java.math.BigDecimal;
import java.util.List;
import org.qortal.data.account.AccountBalanceData;
@@ -82,6 +83,12 @@ public interface AccountRepository {
*/
public void setMintedBlockCount(AccountData accountData) throws DataException;
/** Modifies account's minted block count only.
* <p>
* @return 2 if minted block count updated, 1 if block count set to delta, 0 if address not found.
*/
public int modifyMintedBlockCount(String address, int delta) throws DataException;
/** Delete account from repository. */
public void delete(String address) throws DataException;
@@ -105,6 +112,8 @@ public interface AccountRepository {
public List<AccountBalanceData> getAssetBalances(List<String> addresses, List<Long> assetIds, BalanceOrdering balanceOrdering, Boolean excludeZero, Integer limit, Integer offset, Boolean reverse) throws DataException;
public void modifyAssetBalance(String address, long assetId, BigDecimal deltaBalance) throws DataException;
public void save(AccountBalanceData accountBalanceData) throws DataException;
public void delete(String address, long assetId) throws DataException;
@@ -155,6 +164,21 @@ public interface AccountRepository {
// Managing QORT from legacy QORA
/**
* Returns balance data for accounts with legacy QORA asset that are eligible
* for more block reward (block processing) or for block reward removal (block orphaning).
* <p>
* For block processing, accounts that have already received their final QORT reward for owning
* legacy QORA are omitted from the results. <tt>blockHeight</tt> should be <tt>null</tt>.
* <p>
* For block orphaning, accounts that did not receive a QORT reward at <tt>blockHeight</tt>
* are omitted from the results.
*
* @param blockHeight QORT reward must have be present at this height (for orphaning only)
* @throws DataException
*/
public List<AccountBalanceData> getEligibleLegacyQoraHolders(Integer blockHeight) throws DataException;
public QortFromQoraData getQortFromQoraInfo(String address) throws DataException;
public void save(QortFromQoraData qortFromQoraData) throws DataException;

View File

@@ -4,9 +4,9 @@ import java.math.BigDecimal;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.qortal.asset.Asset;
import org.qortal.data.account.AccountBalanceData;
import org.qortal.data.account.AccountData;
import org.qortal.data.account.MintingAccountData;
@@ -144,6 +144,10 @@ public class HSQLDBAccountRepository implements AccountRepository {
@Override
public void ensureAccount(AccountData accountData) throws DataException {
/*
* Why do we need to check/set the public_key?
* Is there something that sets an account's balance which also needs to set the public key?
byte[] publicKey = accountData.getPublicKey();
String sql = "SELECT public_key FROM Accounts WHERE account = ?";
@@ -168,6 +172,15 @@ public class HSQLDBAccountRepository implements AccountRepository {
} catch (SQLException e) {
throw new DataException("Unable to ensure minimal account in repository", e);
}
*/
String sql = "INSERT IGNORE INTO Accounts (account) VALUES (?)"; // MySQL syntax
try {
this.repository.checkedExecuteUpdateCount(sql, accountData.getAddress());
} catch (SQLException e) {
throw new DataException("Unable to ensure minimal account in repository", e);
}
}
@Override
@@ -273,6 +286,18 @@ public class HSQLDBAccountRepository implements AccountRepository {
}
}
@Override
public int modifyMintedBlockCount(String address, int delta) throws DataException {
String sql = "INSERT INTO Accounts (account, blocks_minted) VALUES (?, ?) " +
"ON DUPLICATE KEY UPDATE blocks_minted = blocks_minted + ?";
try {
return this.repository.checkedExecuteUpdateCount(sql, address, delta, delta);
} catch (SQLException e) {
throw new DataException("Unable to modify account's minted block count in repository", e);
}
}
@Override
public void delete(String address) throws DataException {
// NOTE: Account balances are deleted automatically by the database thanks to "ON DELETE CASCADE" in AccountBalances' FOREIGN KEY
@@ -470,6 +495,54 @@ public class HSQLDBAccountRepository implements AccountRepository {
}
}
@Override
public void modifyAssetBalance(String address, long assetId, BigDecimal deltaBalance) throws DataException {
// If deltaBalance is zero then do nothing
if (deltaBalance.signum() == 0)
return;
// If deltaBalance is negative then we assume AccountBalances & parent Accounts rows exist
if (deltaBalance.signum() < 0) {
// Perform actual balance change
String sql = "UPDATE AccountBalances set balance = balance + ? WHERE account = ? AND asset_id = ?";
try {
this.repository.checkedExecuteUpdateCount(sql, deltaBalance, address, assetId);
} catch (SQLException e) {
throw new DataException("Unable to reduce account balance in repository", e);
}
// If balance is now zero, and there are no prior historic balances, then simply delete row for this address-assetId (typically during orphaning)
String deleteWhereSql = "account = ? AND asset_id = ? AND balance = 0 " + // covers "if balance now zero"
"AND (" +
"SELECT TRUE FROM HistoricAccountBalances " +
"WHERE account = ? AND asset_id = ? AND height < (SELECT height - 1 FROM NextBlockHeight) " +
"LIMIT 1" +
")";
try {
this.repository.delete("AccountBalances", deleteWhereSql, address, assetId, address, assetId);
} catch (SQLException e) {
throw new DataException("Unable to prune account balance in repository", e);
}
} else {
// We have to ensure parent row exists to satisfy foreign key constraint
try {
String sql = "INSERT IGNORE INTO Accounts (account) VALUES (?)"; // MySQL syntax
this.repository.checkedExecuteUpdateCount(sql, address);
} catch (SQLException e) {
throw new DataException("Unable to ensure minimal account in repository", e);
}
// Perform actual balance change
String sql = "INSERT INTO AccountBalances (account, asset_id, balance) VALUES (?, ?, ?) " +
"ON DUPLICATE KEY UPDATE balance = balance + ?";
try {
this.repository.checkedExecuteUpdateCount(sql, address, assetId, deltaBalance, deltaBalance);
} catch (SQLException e) {
throw new DataException("Unable to increase account balance in repository", e);
}
}
}
@Override
public void save(AccountBalanceData accountBalanceData) throws DataException {
// If balance is zero and there are no prior historic balance, then simply delete balances for this assetId (typically during orphaning)
@@ -490,13 +563,17 @@ public class HSQLDBAccountRepository implements AccountRepository {
throw new DataException("Unable to delete account balance from repository", e);
}
// I don't think we need to do this as Block.orphan() would do this for us?
/*
* I don't think we need to do this as Block.orphan() would do this for us?
try {
this.repository.delete("HistoricAccountBalances", "account = ? AND asset_id = ?", accountBalanceData.getAddress(), accountBalanceData.getAssetId());
} catch (SQLException e) {
throw new DataException("Unable to delete historic account balances from repository", e);
}
*/
return;
}
}
@@ -768,6 +845,7 @@ public class HSQLDBAccountRepository implements AccountRepository {
// Minting accounts used by BlockMinter
@Override
public List<MintingAccountData> getMintingAccounts() throws DataException {
List<MintingAccountData> mintingAccounts = new ArrayList<>();
@@ -787,6 +865,7 @@ public class HSQLDBAccountRepository implements AccountRepository {
}
}
@Override
public void save(MintingAccountData mintingAccountData) throws DataException {
HSQLDBSaver saveHelper = new HSQLDBSaver("MintingAccounts");
@@ -799,6 +878,7 @@ public class HSQLDBAccountRepository implements AccountRepository {
}
}
@Override
public int delete(byte[] minterPrivateKey) throws DataException {
try {
return this.repository.delete("MintingAccounts", "minter_private_key = ?", minterPrivateKey);
@@ -809,6 +889,42 @@ public class HSQLDBAccountRepository implements AccountRepository {
// Managing QORT from legacy QORA
@Override
public List<AccountBalanceData> getEligibleLegacyQoraHolders(Integer blockHeight) throws DataException {
StringBuilder sql = new StringBuilder(1024);
sql.append("SELECT account, balance from AccountBalances ");
sql.append("LEFT OUTER JOIN AccountQortFromQoraInfo USING (account) ");
sql.append("WHERE asset_id = ");
sql.append(Asset.LEGACY_QORA); // int is safe to use literally
sql.append(" AND (final_block_height IS NULL");
if (blockHeight != null) {
sql.append(" OR final_block_height >= ");
sql.append(blockHeight);
}
sql.append(")");
List<AccountBalanceData> accountBalances = new ArrayList<>();
try (ResultSet resultSet = this.repository.checkedExecute(sql.toString())) {
if (resultSet == null)
return accountBalances;
do {
String address = resultSet.getString(1);
BigDecimal balance = resultSet.getBigDecimal(2).setScale(8);
accountBalances.add(new AccountBalanceData(address, Asset.LEGACY_QORA, balance));
} while (resultSet.next());
return accountBalances;
} catch (SQLException e) {
throw new DataException("Unable to fetch eligible legacy QORA holders from repository", e);
}
}
@Override
public QortFromQoraData getQortFromQoraInfo(String address) throws DataException {
String sql = "SELECT final_qort_from_qora, final_block_height FROM AccountQortFromQoraInfo WHERE account = ?";
@@ -827,6 +943,7 @@ public class HSQLDBAccountRepository implements AccountRepository {
}
}
@Override
public void save(QortFromQoraData qortFromQoraData) throws DataException {
HSQLDBSaver saveHelper = new HSQLDBSaver("AccountQortFromQoraInfo");
@@ -841,6 +958,7 @@ public class HSQLDBAccountRepository implements AccountRepository {
}
}
@Override
public int deleteQortFromQoraInfo(String address) throws DataException {
try {
return this.repository.delete("AccountQortFromQoraInfo", "account = ?", address);

View File

@@ -928,6 +928,11 @@ public class HSQLDBDatabaseUpdates {
stmt.execute("CREATE INDEX IF NOT EXISTS HistoricAccountBalancesHeightIndex ON HistoricAccountBalances (height)");
break;
case 66:
// Add CHECK constraint to account balances
stmt.execute("ALTER TABLE AccountBalances ADD CONSTRAINT CheckBalanceNotNegative CHECK (balance >= 0)");
break;
default:
// nothing to do
return false;

View File

@@ -337,8 +337,8 @@ public class HSQLDBRepository implements Repository {
Path oldRepoFilePath = oldRepoPath.getFileName();
// Try to open backup. We need to remove "create=true" and insert "backup" dir before final filename.
String backupUrlTemplate = "jdbc:hsqldb:file:%s/backup/%s;create=false;hsqldb.full_log_replay=true";
return String.format(backupUrlTemplate, oldRepoDirPath.toString(), oldRepoFilePath.toString());
String backupUrlTemplate = "jdbc:hsqldb:file:%s%sbackup%s%s;create=false;hsqldb.full_log_replay=true";
return String.format(backupUrlTemplate, oldRepoDirPath.toString(), File.separator, File.separator, oldRepoFilePath.toString());
}
/* package */ static void attemptRecovery(String connectionUrl) throws DataException {
@@ -361,8 +361,8 @@ public class HSQLDBRepository implements Repository {
.forEach(File::delete);
try (Statement stmt = connection.createStatement()) {
// Now "backup" the backup back to original repository location (the parent)
// NOTE: trailing / is OK because HSQLDB checks for both / and O/S-specific separator
// Now "backup" the backup back to original repository location (the parent).
// NOTE: trailing / is OK because HSQLDB checks for both / and O/S-specific separator.
// textdb.allow_full_path connection property is required to be able to use '..'
stmt.execute("BACKUP DATABASE TO '../' BLOCKING AS FILES");
} catch (SQLException e) {

View File

@@ -31,9 +31,6 @@ public class Settings {
private static final int MAINNET_API_PORT = 12391;
private static final int TESTNET_API_PORT = 62391;
private static final int MAINNET_UI_PORT = 12390;
private static final int TESTNET_UI_PORT = 62390;
private static final Logger LOGGER = LogManager.getLogger(Settings.class);
private static final String SETTINGS_FILENAME = "settings.json";
@@ -43,14 +40,17 @@ public class Settings {
// Settings, and other config files
private String userPath;
// Common to all networking (UI/API/P2P)
// Common to all networking (API/P2P)
private String bindAddress = "::"; // Use IPv6 wildcard to listen on all local addresses
// Node management UI
private boolean uiEnabled = true;
private Integer uiPort;
private String[] uiWhitelist = new String[] {
"::1", "127.0.0.1"
// UI servers
private int uiPort = 12388;
private String[] uiLocalServers = new String[] {
"localhost", "172.24.1.1", "qor.tal"
};
private String[] uiRemoteServers = new String[] {
"node1.qortal.org", "node2.qortal.org", "node3.qortal.org", "node4.qortal.org", "node5.qortal.org",
"node6.qortal.org", "node7.qortal.org", "node8.qortal.org", "node9.qortal.org", "node10.qortal.org"
};
// API-related
@@ -77,11 +77,11 @@ public class Settings {
/** Port number for inbound peer-to-peer connections. */
private Integer listenPort;
/** Minimum number of peers to allow block minting / synchronization. */
private int minBlockchainPeers = 5;
private int minBlockchainPeers = 10;
/** Target number of outbound connections to peers we should make. */
private int minOutboundPeers = 20;
private int minOutboundPeers = 40;
/** Maximum number of peer connections we allow. */
private int maxPeers = 50;
private int maxPeers = 80;
// Which blockchains this node is running
private String blockchainConfig = null; // use default from resources
@@ -244,19 +244,16 @@ public class Settings {
return this.userPath;
}
public boolean isUiEnabled() {
return this.uiEnabled;
public int getUiServerPort() {
return this.uiPort;
}
public int getUiPort() {
if (this.uiPort != null)
return this.uiPort;
return this.isTestNet ? TESTNET_UI_PORT : MAINNET_UI_PORT;
public String[] getLocalUiServers() {
return this.uiLocalServers;
}
public String[] getUiWhitelist() {
return this.uiWhitelist;
public String[] getRemoteUiServers() {
return this.uiRemoteServers;
}
public boolean isApiEnabled() {

View File

@@ -241,6 +241,7 @@ public abstract class Transaction {
ASSET_NOT_SPENDABLE(89),
ACCOUNT_CANNOT_REWARD_SHARE(90),
SELF_SHARE_EXISTS(91),
ACCOUNT_ALREADY_EXISTS(92),
NOT_YET_RELEASED(1000);
public final int value;

View File

@@ -83,6 +83,10 @@ public class TransferPrivsTransaction extends Transaction {
if (!Crypto.isValidAddress(this.transferPrivsTransactionData.getRecipient()))
return ValidationResult.INVALID_ADDRESS;
// Check recipient is new account
if (this.repository.getAccountRepository().accountExists(this.transferPrivsTransactionData.getRecipient()))
return ValidationResult.ACCOUNT_ALREADY_EXISTS;
return ValidationResult.OK;
}
@@ -183,8 +187,11 @@ public class TransferPrivsTransaction extends Transaction {
accountRepository.setFlags(senderData);
// Restore recipient's flags
recipientData.setFlags(this.transferPrivsTransactionData.getPreviousRecipientFlags());
accountRepository.setFlags(recipientData);
Integer previousRecipientFlags = this.transferPrivsTransactionData.getPreviousRecipientFlags();
if (previousRecipientFlags != null) {
recipientData.setFlags(previousRecipientFlags);
accountRepository.setFlags(recipientData);
}
// Clean values in transaction data
this.transferPrivsTransactionData.setPreviousSenderFlags(null);

View File

@@ -1,46 +0,0 @@
package org.qortal.ui;
import java.io.IOException;
import java.util.Enumeration;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.http.HttpContent;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.server.ResourceService;
import org.eclipse.jetty.util.URIUtil;
/**
* Replace ResourceService that delivers content as "attachments", typically forcing download instead of rendering.
* <p>
* Sets <tt>Content-Type</tt> header to <tt>application/octet-stream</tt><br>
* Sets <tt>Content-Disposition</tt> header to <tt>attachment; filename="<i>basename</i>"</tt><br>
* where <i>basename</i> is that last component of requested URI path.
* <p>
* Example usage:<br>
* <br>
* <tt>... = new ServletHolder("servlet-name", new DefaultServlet(new DownloadResourceService()));</tt>
*/
public class DownloadResourceService extends ResourceService {
@Override
protected boolean sendData(HttpServletRequest request, HttpServletResponse response, boolean include, final HttpContent content, Enumeration<String> reqRanges) throws IOException {
final boolean _pathInfoOnly = super.isPathInfoOnly();
String servletPath = _pathInfoOnly ? "/" : request.getServletPath();
String pathInfo = request.getPathInfo();
String pathInContext = URIUtil.addPaths(servletPath,pathInfo);
// Find basename of requested content
final int slashIndex = pathInContext.lastIndexOf(URIUtil.SLASH);
if (slashIndex != -1)
pathInContext = pathInContext.substring(slashIndex + 1);
// Add appropriate headers
response.setHeader(HttpHeader.CONTENT_TYPE.asString(), "application/octet-stream");
response.setHeader("Content-Disposition", "attachment; filename=\"" + pathInContext + "\"");
return super.sendData(request, response, include, content, reqRanges);
}
}

View File

@@ -1,101 +0,0 @@
package org.qortal.ui;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import org.eclipse.jetty.rewrite.handler.RedirectPatternRule;
import org.eclipse.jetty.rewrite.handler.RewriteHandler;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.InetAccessHandler;
import org.eclipse.jetty.servlet.DefaultServlet;
import org.eclipse.jetty.servlet.FilterHolder;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jetty.servlets.CrossOriginFilter;
import org.qortal.settings.Settings;
public class UiService {
public static final String DOWNLOADS_RESOURCE_PATH = "node-ui-downloads";
private static UiService instance;
private Server server;
private UiService() {
}
public static UiService getInstance() {
if (instance == null)
instance = new UiService();
return instance;
}
public void start() {
try {
// Create node management UI server
InetAddress bindAddr = InetAddress.getByName(Settings.getInstance().getBindAddress());
InetSocketAddress endpoint = new InetSocketAddress(bindAddr, Settings.getInstance().getUiPort());
this.server = new Server(endpoint);
// IP address based access control
InetAccessHandler accessHandler = new InetAccessHandler();
for (String pattern : Settings.getInstance().getUiWhitelist()) {
accessHandler.include(pattern);
}
this.server.setHandler(accessHandler);
// URL rewriting
RewriteHandler rewriteHandler = new RewriteHandler();
accessHandler.setHandler(rewriteHandler);
// Context
ServletContextHandler context = new ServletContextHandler(ServletContextHandler.NO_SESSIONS);
context.setContextPath("/");
rewriteHandler.setHandler(context);
// Cross-origin resource sharing
FilterHolder corsFilterHolder = new FilterHolder(CrossOriginFilter.class);
corsFilterHolder.setInitParameter(CrossOriginFilter.ALLOWED_ORIGINS_PARAM, "*");
corsFilterHolder.setInitParameter(CrossOriginFilter.ALLOWED_METHODS_PARAM, "GET, POST, DELETE");
corsFilterHolder.setInitParameter(CrossOriginFilter.CHAIN_PREFLIGHT_PARAM, "false");
context.addFilter(corsFilterHolder, "/*", null);
ClassLoader loader = this.getClass().getClassLoader();
// Node management UI download servlet
ServletHolder uiDownloadServlet = new ServletHolder("node-ui-download", new DefaultServlet(new DownloadResourceService()));
uiDownloadServlet.setInitParameter("resourceBase", loader.getResource(DOWNLOADS_RESOURCE_PATH + "/").toString());
uiDownloadServlet.setInitParameter("dirAllowed", "true");
uiDownloadServlet.setInitParameter("pathInfoOnly", "true");
context.addServlet(uiDownloadServlet, "/downloads/*");
// Node management UI static content servlet
ServletHolder uiServlet = new ServletHolder("node-management-ui", DefaultServlet.class);
uiServlet.setInitParameter("resourceBase", loader.getResource("node-management-ui/").toString());
uiServlet.setInitParameter("dirAllowed", "true");
uiServlet.setInitParameter("pathInfoOnly", "true");
context.addServlet(uiServlet, "/*");
rewriteHandler.addRule(new RedirectPatternRule("", "/index.html")); // node management UI start page
// Start server
this.server.start();
} catch (Exception e) {
// Failed to start
throw new RuntimeException("Failed to start node management UI", e);
}
}
public void stop() {
try {
// Stop server
this.server.stop();
} catch (Exception e) {
// Failed to stop
}
this.server = null;
}
}

View File

@@ -5,11 +5,26 @@ import java.util.concurrent.Executors;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.TimeUnit;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public abstract class ExecuteProduceConsume implements Runnable {
@XmlAccessorType(XmlAccessType.FIELD)
public static class StatsSnapshot {
public int activeThreadCount = 0;
public int greatestActiveThreadCount = 0;
public int consumerCount = 0;
public int tasksProduced = 0;
public int tasksConsumed = 0;
public StatsSnapshot() {
}
}
private final String className;
private final Logger logger;
@@ -51,28 +66,18 @@ public abstract class ExecuteProduceConsume implements Runnable {
return this.executor.awaitTermination(timeout, TimeUnit.MILLISECONDS);
}
public int getActiveThreadCount() {
synchronized (this) {
return this.activeThreadCount;
}
}
public StatsSnapshot getStatsSnapshot() {
StatsSnapshot snapshot = new StatsSnapshot();
public int getGreatestActiveThreadCount() {
synchronized (this) {
return this.greatestActiveThreadCount;
snapshot.activeThreadCount = this.activeThreadCount;
snapshot.greatestActiveThreadCount = this.greatestActiveThreadCount;
snapshot.consumerCount = this.consumerCount;
snapshot.tasksProduced = this.tasksProduced;
snapshot.tasksConsumed = this.tasksConsumed;
}
}
public int getTasksProduced() {
synchronized (this) {
return this.tasksProduced;
}
}
public int getTasksConsumed() {
synchronized (this) {
return this.tasksConsumed;
}
return snapshot;
}
/**

View File

@@ -0,0 +1,29 @@
package org.qortal.utils;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Random;
public class RandomizeList {
private static final Random random = new Random();
public static <T> List<T> randomize(List<T> inputList) {
List<T> outputList = new ArrayList<T>();
Iterator<T> inputIterator = inputList.iterator();
while (inputIterator.hasNext()) {
T element = inputIterator.next();
if (outputList.isEmpty()) {
outputList.add(element);
} else {
int outputIndex = random.nextInt(outputList.size() + 1);
outputList.add(outputIndex, element);
}
}
return outputList;
}
}

View File

@@ -57,7 +57,7 @@
},
"genesisInfo": {
"version": 4,
"timestamp": "1583250000000",
"timestamp": "1583870000000",
"transactions": [
{ "type": "ISSUE_ASSET", "owner": "QUwGVHPPxJNJ2dq95abQNe79EyBN2K26zM", "assetName": "QORT", "description": "QORTAL coin", "quantity": 0, "isDivisible": true, "reference": "28u54WRcMfGujtQMZ9dNKFXVqucY7XfPihXAqPFsnx853NPUwfDJy1sMH5boCkahFgjUNYqc5fkduxdBhQTKgUsC", "data": "{}" },
{ "type": "ISSUE_ASSET", "owner": "QUwGVHPPxJNJ2dq95abQNe79EyBN2K26zM", "assetName": "Legacy-QORA", "description": "Representative legacy QORA", "quantity": 0, "isDivisible": true, "data": "{}", "isUnspendable": true },
@@ -72,6 +72,7 @@
{ "type": "ACCOUNT_FLAGS", "target": "Qd453ewoyESrEgUab6dTFe2pufWkD94Tsm", "andMask": -1, "orMask": 1, "xorMask": 0 },
{ "type": "ACCOUNT_FLAGS", "target": "QfjoMGib4trpZHzxUSMdmtiRnsrLNf74zp", "andMask": -1, "orMask": 1, "xorMask": 0 },
{ "type": "ACCOUNT_FLAGS", "target": "Qgfh143pRJyxpS92JoazjXNMH1uZueQBZ2", "andMask": -1, "orMask": 1, "xorMask": 0 },
{ "type": "ACCOUNT_FLAGS", "target": "Qi3N6fNRrs15EHmkxYyWHyh4z3Dp2rVU2i", "andMask": -1, "orMask": 1, "xorMask": 0 },
{ "type": "ACCOUNT_FLAGS", "target": "QicRwDhfk8M2CGNvpMEmYzQEjESvF7WrFY", "andMask": -1, "orMask": 1, "xorMask": 0 },
{ "type": "ACCOUNT_FLAGS", "target": "QLwMaXmDDUvh7aN5MdpY28rqTKE8U1Cepc", "andMask": -1, "orMask": 1, "xorMask": 0 },
{ "type": "ACCOUNT_FLAGS", "target": "QP3J3GHgjqP69neTAprpYe4co33eKQiQpS", "andMask": -1, "orMask": 1, "xorMask": 0 },
@@ -87,21 +88,24 @@
{ "type": "ACCOUNT_FLAGS", "target": "QSJ5cDLWivGcn9ym21azufBfiqeuGH1maq", "andMask": -1, "orMask": 0, "xorMask": 0 },
{ "type": "ACCOUNT_FLAGS", "target": "QhPXuFFRa9s91Q2qYKpSN5LVCUTqYkgRLz", "andMask": -1, "orMask": 0, "xorMask": 0 },
{ "type": "ACCOUNT_LEVEL", "target": "QagyUZdmnKJA9LyEqcxJFFf2ehmcqVZsKb", "level": 5 },
{ "type": "ACCOUNT_LEVEL", "target": "QVhboSLD1VmX2YvAnfAXkbzvsmXkDJZTNR", "level": 5 },
{ "type": "ACCOUNT_LEVEL", "target": "Qg3TKLhPvn7bKVrT9x37wJiJ7YZ4jBuqQW", "level": 5 },
{ "type": "ACCOUNT_LEVEL", "target": "QcCYuXos5xBXXHbRg1RTfSdxiZEkGa3N2P", "level": 5 },
{ "type": "ACCOUNT_LEVEL", "target": "QPFM4xX2826MuBEhMtdReW1QR3vRYrQff3", "level": 5 },
{ "type": "ACCOUNT_LEVEL", "target": "QVhboSLD1VmX2YvAnfAXkbzvsmXkDJZTNR", "level": 5 },
{ "type": "ACCOUNT_LEVEL", "target": "QagyUZdmnKJA9LyEqcxJFFf2ehmcqVZsKb", "level": 5 },
{ "type": "ACCOUNT_LEVEL", "target": "QcCYuXos5xBXXHbRg1RTfSdxiZEkGa3N2P", "level": 5 },
{ "type": "ACCOUNT_LEVEL", "target": "Qg3TKLhPvn7bKVrT9x37wJiJ7YZ4jBuqQW", "level": 5 },
{ "type": "ACCOUNT_LEVEL", "target": "QiQUssukhoo1ft4G9Mxa8JpViqFW4PdBjJ", "level": 5 },
{ "type": "ACCOUNT_LEVEL", "target": "QTVLRwoopfg7qSG9CMfCPJz3UydnT3jDxD", "level": 4 },
{ "type": "ACCOUNT_LEVEL", "target": "QPLoqpwAoytvpQKwvJ6GRsaRcVZ3xnYgVB", "level": 3 },
{ "type": "ACCOUNT_LEVEL", "target": "QVxGkDgXt4nHj4MAd1afV9AxT1XCUVLGja", "level": 3 },
{ "type": "ACCOUNT_LEVEL", "target": "QTrosCDj7oCdJc8K7sJZW6ssHU3StVoY7s", "level": 3 },
{ "type": "ACCOUNT_LEVEL", "target": "QeoFPT4xPxLp2P42yKVo22wx2V8kydfNmr", "level": 3 },
{ "type": "ACCOUNT_LEVEL", "target": "Qata5oApMShnD4F1kcgSJMTiYsxTPSFW4F", "level": 3 },
{ "type": "ACCOUNT_LEVEL", "target": "QXHmtFXzf4D7PEu73NfBm3sZyeuGrm3QC5", "level": 3 },
{ "type": "ACCOUNT_LEVEL", "target": "QRPhK2FwijTaMQ4PeJ9gFK1vvGBaYaht2P", "level": 3 },
{ "type": "ACCOUNT_LEVEL", "target": "QRk5TG57SQGLkybXUqxBnobADTFGj9GR3Z", "level": 3 },
{ "type": "ACCOUNT_LEVEL", "target": "QTrosCDj7oCdJc8K7sJZW6ssHU3StVoY7s", "level": 3 },
{ "type": "ACCOUNT_LEVEL", "target": "QVgX5FVNvAvESmeVD67vz3xE7BQTQARLLM", "level": 3 },
{ "type": "ACCOUNT_LEVEL", "target": "QVxGkDgXt4nHj4MAd1afV9AxT1XCUVLGja", "level": 3 },
{ "type": "ACCOUNT_LEVEL", "target": "QXHmtFXzf4D7PEu73NfBm3sZyeuGrm3QC5", "level": 3 },
{ "type": "ACCOUNT_LEVEL", "target": "Qata5oApMShnD4F1kcgSJMTiYsxTPSFW4F", "level": 3 },
{ "type": "ACCOUNT_LEVEL", "target": "QeoFPT4xPxLp2P42yKVo22wx2V8kydfNmr", "level": 3 },
{ "type": "ACCOUNT_LEVEL", "target": "Qi7Lbvz1UdWgzgbKzfpRisLsdLR4smVoVh", "level": 3 },
{ "type": "REWARD_SHARE", "minterPublicKey": "HFDmuc4HAAoVs9Siea3MugjBHasbotgVz2gsRDuLAAcB", "recipient": "QY82MasqEH6ChwXaETH4piMtE8Pk4NBWD3", "rewardSharePublicKey": "F35TbQXmgzz32cALj29jxzpdYSUKQvssqThLsZSabSXx", "sharePercent": 0 },
@@ -124,8 +128,8 @@
{ "type": "ACCOUNT_LEVEL", "target": "QcrnYL6yNwHKuEzYLXQ8LewG3m2B5k9K5f", "level": 5 },
{ "type": "ACCOUNT_LEVEL", "target": "QPYfRd1uhnAgqkZNmjNCjgPhkguMnHWuc4", "level": 5 },
{ "type": "ACCOUNT_LEVEL", "target": "QM9zVbXXnfrtQ1X7zPQ5zxPYPAWTaVMXqZ", "level": 5 },
{ "type": "ACCOUNT_LEVEL", "target": "Qft1ktvJ14eBFjpJaphT24ks4WRcN3K6tB", "level": 5 },
{ "type": "ACCOUNT_LEVEL", "target": "QcHF9YogbuzZhG4fK4116pgE2qrmbkGh2n", "level": 5 },
{ "type": "ACCOUNT_LEVEL", "target": "Qft1ktvJ14eBFjpJaphT24ks4WRcN3K6tB", "level": 5 },
{ "type": "ACCOUNT_LEVEL", "target": "QaUciVnbQDXdNygJadEY31PuDEBLi6Spmu", "level": 5 },
{ "type": "ACCOUNT_LEVEL", "target": "QWe1iPDudLU189BggPykbH1DrAeaFEgX6W", "level": 5 },
{ "type": "ACCOUNT_LEVEL", "target": "QgECFJiiri2dDN4zA32URvbdDid2cFrJwM", "level": 5 },
@@ -134,25 +138,26 @@
{ "type": "ACCOUNT_LEVEL", "target": "QXan6JJ1WuRi7GigvFDjtTzJY1rfYEqEqv", "level": 5 },
{ "type": "ACCOUNT_LEVEL", "target": "QQa3MTgdnru5B7wSqPcq7qXcZcpbDQ7oyE", "level": 5 },
{ "type": "ACCOUNT_LEVEL", "target": "QQoHo1x4hvZcSFbj2uQAXQhq2ZXBUgPeNj", "level": 5 },
{ "type": "ACCOUNT_LEVEL", "target": "QQEZEGWt3sAPwEWYD2RQ6tMwnpkayG81dY", "level": 5 },
{ "type": "ACCOUNT_LEVEL", "target": "QUvoLFfkuVuRe1KGMLQS4nUHry6CBTuTYz", "level": 5 },
{ "type": "ACCOUNT_LEVEL", "target": "QTEE4ZJXv68ke4841HWjTLAAU8mfccxwbE", "level": 5 },
{ "type": "ACCOUNT_LEVEL", "target": "QQEZEGWt3sAPwEWYD2RQ6tMwnpkayG81dY", "level": 5 },
{ "type": "ACCOUNT_LEVEL", "target": "QVurebcEbe4USR4xcS3Mbk12mhxsjRX31u", "level": 5 },
{ "type": "ACCOUNT_LEVEL", "target": "QfmM8dgfikTB2FYVuJ9owzQXVm8wP7T4QT", "level": 5 },
{ "type": "ACCOUNT_LEVEL", "target": "QPUMyJ59kkrp75tDzDPxSyw1GWCrbC2cS2", "level": 5 },
{ "type": "ACCOUNT_LEVEL", "target": "Qc9dZchoYfc1eRJhSLXR9rxSHcqNB47Dex", "level": 5 },
{ "type": "ACCOUNT_LEVEL", "target": "QTMTFswUU83XVmk6T4Gez7qUJCccbAad7S", "level": 5 },
{ "type": "ACCOUNT_LEVEL", "target": "QjrC8NXwR8gFkEauvRwCPxqHroPFqAJbhK", "level": 5 },
{ "type": "ACCOUNT_LEVEL", "target": "QPcTWoAhYWmwjmWbQAS8muisrQVaLJMbg7", "level": 5 },
{ "type": "ACCOUNT_LEVEL", "target": "QjrC8NXwR8gFkEauvRwCPxqHroPFqAJbhK", "level": 5 },
{ "type": "ACCOUNT_LEVEL", "target": "QXv4WiqgryFPi8BgX7RU7gqtAgrVutmU4S", "level": 5 },
{ "type": "ACCOUNT_LEVEL", "target": "QSkiGy3v3MwvGy3aACuyLDv3Xy2AWAYfPS", "level": 5 },
{ "type": "ACCOUNT_LEVEL", "target": "QgpAW6uqwNR58gNYWRCVXNLm4F5TuckAw4", "level": 5 },
{ "type": "ACCOUNT_LEVEL", "target": "QLpLY9o3X21q7q4L6u5JdDuMskYA838iYE", "level": 5 },
{ "type": "ACCOUNT_LEVEL", "target": "QguNJ4evpN9i76dYHysTzLiKoWKFhE4B4U", "level": 5 },
{ "type": "ACCOUNT_LEVEL", "target": "QWpJxitn53ovwknQfrCaqivvoUNuPrX2sb", "level": 5 },
{ "type": "ACCOUNT_LEVEL", "target": "QdGbhtkFHUqd9nK9UegxxGXD1eSRYSoKjt", "level": 5 },
{ "type": "ACCOUNT_LEVEL", "target": "QXv4WiqgryFPi8BgX7RU7gqtAgrVutmU4S", "level": 5 },
{ "type": "ACCOUNT_LEVEL", "target": "QP8xG56L8b28h1mguSk9LuzNhxbHgAoL9b", "level": 5 },
{ "type": "ACCOUNT_LEVEL", "target": "QfjL32jLsxtumbfx6ufmfCFCBccVCQFkrh", "level": 5 },
{ "type": "ACCOUNT_LEVEL", "target": "QguNJ4evpN9i76dYHysTzLiKoWKFhE4B4U", "level": 5 },
{ "type": "ACCOUNT_LEVEL", "target": "QSkiGy3v3MwvGy3aACuyLDv3Xy2AWAYfPS", "level": 5 },
{ "type": "ACCOUNT_LEVEL", "target": "QWLpsGYrkF2cy3tH6DCxso7kXZpZJvv13e", "level": 5 },
{ "type": "ACCOUNT_LEVEL", "target": "QbwgBD6LWdk1hZsb8EwdVVmDZdvpxMzyGT", "level": 5 },
{ "type": "ACCOUNT_LEVEL", "target": "Qbcy4uyMkQF2JXYqGkueDiFNZ4tHjRg8CR", "level": 5 },
{ "type": "ACCOUNT_LEVEL", "target": "QdXe21sjY8smjVmiAUgZY8xWVzwgxMgK5A", "level": 5 },
@@ -161,7 +166,6 @@
{ "type": "ACCOUNT_LEVEL", "target": "QXKmtkHHwaUQzGeHHG2dFiHUnKAp815Mzq", "level": 5 },
{ "type": "ACCOUNT_LEVEL", "target": "QZkThgFfExognAbxLjYZGVHpL7X6g3EG4A", "level": 4 },
{ "type": "ACCOUNT_LEVEL", "target": "QbH8srDKeS5VcsQsgsaF3nqCzGT1NqfsTx", "level": 4 },
{ "type": "ACCOUNT_LEVEL", "target": "QWLpsGYrkF2cy3tH6DCxso7kXZpZJvv13e", "level": 4 },
{ "type": "ACCOUNT_LEVEL", "target": "QdtAQm1EGNgM7QDSaC2qvV9WdpRHwpApUT", "level": 4 },
{ "type": "ACCOUNT_LEVEL", "target": "QbqRyFw7Xu6Nsb4FraaUSe7nUPukuUpekG", "level": 4 },
{ "type": "ACCOUNT_LEVEL", "target": "QRFHr4jnVgvAsPTubeSrh8bPy1yzwzYaWD", "level": 4 },
@@ -170,25 +174,25 @@
{ "type": "ACCOUNT_LEVEL", "target": "QQPYyoE3Bm2vh8Wr5aaBNyirC8dd3BhBGH", "level": 4 },
{ "type": "ACCOUNT_LEVEL", "target": "QUzUCfoakDqBaL5zBgfvTKLHcuxbUfB38Q", "level": 4 },
{ "type": "ACCOUNT_LEVEL", "target": "QSBJuNoCAFUTcevuCTcMi3i5nzNPhC5R4b", "level": 4 },
{ "type": "ACCOUNT_LEVEL", "target": "QTGeQqn3XEFdnnCqvifCFXYdKym7SaHzTd", "level": 4 },
{ "type": "ACCOUNT_LEVEL", "target": "QVSqUrNFR4mPTMa7UdVmNKZTSaDVAv8XXF", "level": 4 },
{ "type": "ACCOUNT_LEVEL", "target": "QMLjFiPiPa46YBRoushe6a227kSrXnXyKb", "level": 4 },
{ "type": "ACCOUNT_LEVEL", "target": "QTGeQqn3XEFdnnCqvifCFXYdKym7SaHzTd", "level": 4 },
{ "type": "ACCOUNT_LEVEL", "target": "QNJ8jDx6Mni2GKLHEY1BMh9xDumT3vnJQM", "level": 4 },
{ "type": "ACCOUNT_LEVEL", "target": "QMLjFiPiPa46YBRoushe6a227kSrXnXyKb", "level": 4 },
{ "type": "ACCOUNT_LEVEL", "target": "QgVZb632eqF1eLQm9gBGuBtyp9Dyz2FKUK", "level": 4 },
{ "type": "ACCOUNT_LEVEL", "target": "QPREQjU2defiYdgA33HDiLNGBpxtuebeqE", "level": 4 },
{ "type": "ACCOUNT_LEVEL", "target": "QiBYApdEYRwsFYjt59UJqZV55wcwykvhsh", "level": 4 },
{ "type": "ACCOUNT_LEVEL", "target": "QMsKXQAYKmR7dBH4P3kMLiKzYatK3h1CeS", "level": 4 },
{ "type": "ACCOUNT_LEVEL", "target": "QRCtc67FTNKS5zVXM8omw8F55h9DP7herL", "level": 4 },
{ "type": "ACCOUNT_LEVEL", "target": "QZvHW7amu5DNktsBgaMrR1brHZhhhVwKLW", "level": 4 },
{ "type": "ACCOUNT_LEVEL", "target": "QRCtc67FTNKS5zVXM8omw8F55h9DP7herL", "level": 4 },
{ "type": "ACCOUNT_LEVEL", "target": "QgqM5bKs3tNqKNAnVeaQp4oaYMXCmX6YJr", "level": 4 },
{ "type": "ACCOUNT_LEVEL", "target": "QhMEWhZCufhKLkfuNU2DAzj1mmWoAxX147", "level": 4 },
{ "type": "ACCOUNT_LEVEL", "target": "Qgwb5SLVGAperXAMoVNBDvGpVAxTVY7f6F", "level": 4 },
{ "type": "ACCOUNT_LEVEL", "target": "QbvxC3ENqomXp11833APchdjeyCNd49nLj", "level": 4 },
{ "type": "ACCOUNT_LEVEL", "target": "Qj5ncwncQY4KPYgKHD1eYpXHbR717PeLcJ", "level": 4 },
{ "type": "ACCOUNT_LEVEL", "target": "QY1RFZTD2ogRohf3UrdT4g1Qo9D122AZDN", "level": 4 },
{ "type": "ACCOUNT_LEVEL", "target": "QcU4VhU9ohDXU4k4AUMapgJRYSzEpizjLN", "level": 4 },
{ "type": "ACCOUNT_LEVEL", "target": "QY1RFZTD2ogRohf3UrdT4g1Qo9D122AZDN", "level": 4 },
{ "type": "ACCOUNT_LEVEL", "target": "QZsygC1chppsJK1cnaHG4fEsNaDYfLF7ZJ", "level": 4 },
{ "type": "ACCOUNT_LEVEL", "target": "QYGcPZcRhGaY1MsiDr3VtwTXmB9TAbLFSn", "level": 4 },
{ "type": "ACCOUNT_LEVEL", "target": "Qgwb5SLVGAperXAMoVNBDvGpVAxTVY7f6F", "level": 4 },
{ "type": "ACCOUNT_LEVEL", "target": "QaFN7DWcGF7keNACJpwCVnegePbfsAoFCw", "level": 4 },
{ "type": "ACCOUNT_LEVEL", "target": "QYGNMWBmqWgVtMWGHypAsKhDVQw5mrFZww", "level": 4 },
{ "type": "ACCOUNT_LEVEL", "target": "QcNmqT8CZ6zSZwuRm5LahRZnuGBJRnPY8o", "level": 4 },
@@ -199,300 +203,318 @@
{ "type": "ACCOUNT_LEVEL", "target": "QgCQq4cFaGrJhwvKs4XwvccKiLZ8GVMCXR", "level": 4 },
{ "type": "ACCOUNT_LEVEL", "target": "QRtRELSSASzqiYy2FtNcrePH6TVnqJkv9B", "level": 4 },
{ "type": "ACCOUNT_LEVEL", "target": "QMtm8wVPHGE3qHg2hMaj6SZ78D5eXw3VWZ", "level": 4 },
{ "type": "ACCOUNT_LEVEL", "target": "QY6ZGZdi8h5op2VrRXkG1W5Jp3feLwp7ZD", "level": 3 },
{ "type": "ACCOUNT_LEVEL", "target": "QY6ZGZdi8h5op2VrRXkG1W5Jp3feLwp7ZD", "level": 4 },
{ "type": "ACCOUNT_LEVEL", "target": "QVrvy4ac2jBTfxyCKB7MLimqJooTDBApmS", "level": 3 },
{ "type": "ACCOUNT_LEVEL", "target": "QcCBVfL35rxSyQ416L2MBz14FYbNrbeNPx", "level": 3 },
{ "type": "ACCOUNT_LEVEL", "target": "QNHdGeFJmPcDdN8prPzPL4bk2dpnJ2ZZFr", "level": 3 },
{ "type": "ACCOUNT_LEVEL", "target": "QRWEbcRnLoGccAndtLcGgpeQFH2ZBcMqHo", "level": 3 },
{ "type": "ACCOUNT_LEVEL", "target": "QcCBVfL35rxSyQ416L2MBz14FYbNrbeNPx", "level": 3 },
{ "type": "ACCOUNT_LEVEL", "target": "Qce2Djqrk2WzG1QhMZ3BqFok9HGsz4wtM3", "level": 3 },
{ "type": "ACCOUNT_LEVEL", "target": "QRWEbcRnLoGccAndtLcGgpeQFH2ZBcMqHo", "level": 3 },
{ "type": "ACCOUNT_LEVEL", "target": "QeDhJRqTyeDmcgGJoms6FHK49ZGVpVahxg", "level": 3 },
{ "type": "ACCOUNT_LEVEL", "target": "QUXga5K8nzd9EqYtvEesZWEYuA688h6D3d", "level": 3 },
{ "type": "ACCOUNT_LEVEL", "target": "QfbX8JJupEw5ckNtU4upQgET35oLTr5e6v", "level": 3 },
{ "type": "ACCOUNT_LEVEL", "target": "QRaDef6H2zYfefqLwYGmUg7T6DAqo6DDqc", "level": 3 },
{ "type": "ACCOUNT_LEVEL", "target": "QibuD4c6gvXgS4iut7q3sXuVb23rgFJq2M", "level": 3 },
{ "type": "ACCOUNT_LEVEL", "target": "QawSgZ7i2LLFTKyPxQptk9gN526ihy5yZi", "level": 3 },
{ "type": "ACCOUNT_LEVEL", "target": "QMozpRT9aUunfmPh7EtQ6LPoth2JFJWBXC", "level": 3 },
{ "type": "ACCOUNT_LEVEL", "target": "QTpYQqRyMekaEuECziirzy3HvCVofZS1wJ", "level": 3 },
{ "type": "ACCOUNT_LEVEL", "target": "QawSgZ7i2LLFTKyPxQptk9gN526ihy5yZi", "level": 3 },
{ "type": "ACCOUNT_LEVEL", "target": "QMuWNAJ2tbeViHtBUN3yD2KARrrzcanLAd", "level": 3 },
{ "type": "ACCOUNT_LEVEL", "target": "QUGo9SErgc6ceB5aBzcSJDNqBkQ9eaCKZS", "level": 3 },
{ "type": "ACCOUNT_LEVEL", "target": "QdFZk74skMUu4rKMPEmcSVwR87LNDe6o3Y", "level": 3 },
{ "type": "ACCOUNT_LEVEL", "target": "Qgp16aMcdiS2EUkxCm5NSZgB8DixGK51zT", "level": 3 },
{ "type": "ACCOUNT_LEVEL", "target": "QScBgSw74MquesXmVJxerX3YgyhtShRr4q", "level": 3 },
{ "type": "ACCOUNT_LEVEL", "target": "QgcF6KgVZ9eDAMHJdSEeAtp91t931VKZMv", "level": 3 },
{ "type": "ACCOUNT_LEVEL", "target": "QjgGeEkyiXa43pyqkXxZbvAChQpVYfUyKz", "level": 3 },
{ "type": "ACCOUNT_LEVEL", "target": "QRWEbzH4niUcu9dL3Yq42X4j89aqQk3qWw", "level": 3 },
{ "type": "ACCOUNT_LEVEL", "target": "QYD3kXchZ86vUyJBXNCVQ4LUvTAd6PUZW3", "level": 3 },
{ "type": "ACCOUNT_LEVEL", "target": "QPqfuZpmyA6cK6WUFwcGeKH2Te1aegkHBM", "level": 3 },
{ "type": "ACCOUNT_LEVEL", "target": "QhQdzLn36SDgrgoMfvdZAkoWtTUHpB3acJ", "level": 3 },
{ "type": "ACCOUNT_LEVEL", "target": "QgcF6KgVZ9eDAMHJdSEeAtp91t931VKZMv", "level": 3 },
{ "type": "ACCOUNT_LEVEL", "target": "QRWEbzH4niUcu9dL3Yq42X4j89aqQk3qWw", "level": 3 },
{ "type": "ACCOUNT_LEVEL", "target": "QWL7kZp6Pdd1bhxZ6SXPhVf5g7GParG9CC", "level": 3 },
{ "type": "ACCOUNT_LEVEL", "target": "QbxJvwrEHZs7MDE8rbqBwZAZkcywue5F3W", "level": 3 },
{ "type": "ACCOUNT_LEVEL", "target": "QhQdzLn36SDgrgoMfvdZAkoWtTUHpB3acJ", "level": 3 },
{ "type": "ACCOUNT_LEVEL", "target": "QPqfuZpmyA6cK6WUFwcGeKH2Te1aegkHBM", "level": 3 },
{ "type": "ACCOUNT_LEVEL", "target": "QUKKwug9PNai3DBggXUXP8Ag7WmR5SVUR4", "level": 3 },
{ "type": "ACCOUNT_LEVEL", "target": "QWwrtjBL4ah965XPXHYJhymreC9jyryNLZ", "level": 3 },
{ "type": "ACCOUNT_LEVEL", "target": "QjEaMxcBKMsj91ytKe6GdTBJP8Mu1Ru3r4", "level": 3 },
{ "type": "ACCOUNT_LEVEL", "target": "QeDhJRqTyeDmcgGJoms6FHK49ZGVpVahxg", "level": 3 },
{ "type": "ACCOUNT_LEVEL", "target": "QbYTowTHCr9WzfrR6b8uDfJKwL41nG1vyr", "level": 3 },
{ "type": "ACCOUNT_LEVEL", "target": "QPusqAVBVFGAAeE7RdospttA18AuyLP7sB", "level": 3 },
{ "type": "ACCOUNT_LEVEL", "target": "Qd1Px9vhWuEmF2SbLx3Ez7HhGtifGMa8TJ", "level": 3 },
{ "type": "ACCOUNT_LEVEL", "target": "QSq8y4ZrSbF55ZddWNcw1ett2LDtjQEvNn", "level": 3 },
{ "type": "ACCOUNT_LEVEL", "target": "QNw9xAm9TUerin9QsapCPL9mV6zmoXyJrh", "level": 3 },
{ "type": "ACCOUNT_LEVEL", "target": "Qd1Px9vhWuEmF2SbLx3Ez7HhGtifGMa8TJ", "level": 3 },
{ "type": "ACCOUNT_LEVEL", "target": "QbYTowTHCr9WzfrR6b8uDfJKwL41nG1vyr", "level": 3 },
{ "type": "ACCOUNT_LEVEL", "target": "QU8XJyQEZxSHgrS1XyooWUo5MmruXg36Pe", "level": 3 },
{ "type": "ACCOUNT_LEVEL", "target": "QYuMATkLjz7YB6s4EG1aWCmmmrAPj3W9Ce", "level": 3 },
{ "type": "ACCOUNT_LEVEL", "target": "QNw9xAm9TUerin9QsapCPL9mV6zmoXyJrh", "level": 3 },
{ "type": "ACCOUNT_LEVEL", "target": "QNiTnonHpXTeUrgNdyYWVDPP4ZdjkLpW72", "level": 3 },
{ "type": "ACCOUNT_LEVEL", "target": "QTtXS6fZGThRLq4qgkwM4ngBYkLoFyZ3bK", "level": 3 },
{ "type": "ACCOUNT_LEVEL", "target": "QjrCFCi6dqvka4UELg2SHhM2oWnQWepd1o", "level": 3 },
{ "type": "ACCOUNT_LEVEL", "target": "QYuMATkLjz7YB6s4EG1aWCmmmrAPj3W9Ce", "level": 3 },
{ "type": "ACCOUNT_LEVEL", "target": "QREtYDhP4HkpeCCZroemuGXMGVFoZHH3Lp", "level": 3 },
{ "type": "ACCOUNT_LEVEL", "target": "QgesuKa3zwx8VAseF1oHZAFHMf29k8ergq", "level": 2 },
{ "type": "ACCOUNT_LEVEL", "target": "QjrCFCi6dqvka4UELg2SHhM2oWnQWepd1o", "level": 3 },
{ "type": "ACCOUNT_LEVEL", "target": "QTtXS6fZGThRLq4qgkwM4ngBYkLoFyZ3bK", "level": 3 },
{ "type": "ACCOUNT_LEVEL", "target": "QjEAs2or122weKppv5zALzoQzXxbsDjy3f", "level": 3 },
{ "type": "ACCOUNT_LEVEL", "target": "QdXdUxnyKGGo7eEfTcx85oEikNe5nYnuwa", "level": 2 },
{ "type": "ACCOUNT_LEVEL", "target": "QgesuKa3zwx8VAseF1oHZAFHMf29k8ergq", "level": 2 },
{ "type": "ACCOUNT_LEVEL", "target": "QWBFK5h61ZxGfqQpEkwwKTcLAo8t9VWe4K", "level": 2 },
{ "type": "ACCOUNT_LEVEL", "target": "QjEAs2or122weKppv5zALzoQzXxbsDjy3f", "level": 2 },
{ "type": "ACCOUNT_LEVEL", "target": "QZ19JRpSsgvm4z6EjnbhdxJBoUYzDGvP3x", "level": 2 },
{ "type": "ACCOUNT_LEVEL", "target": "QUZQPWhrxpze32vGiux6wa85kg9iwuhCDx", "level": 2 },
{ "type": "ACCOUNT_LEVEL", "target": "QZ19JRpSsgvm4z6EjnbhdxJBoUYzDGvP3x", "level": 2 },
{ "type": "ACCOUNT_LEVEL", "target": "QdwSxr3t4hdGHjQFy6EVGR9yGMipefsTuo", "level": 2 },
{ "type": "ACCOUNT_LEVEL", "target": "QZ2gi6BhUNpGmrErgJLFuY1WHy6xK1J7qX", "level": 2 },
{ "type": "ACCOUNT_LEVEL", "target": "QWinRb65f2g3yBoaZvTrQKQk7CW7vfBgGX", "level": 2 },
{ "type": "ACCOUNT_LEVEL", "target": "QQaKBSjAt9RK2bqJoSriR77X4ULstGzrFQ", "level": 2 },
{ "type": "ACCOUNT_LEVEL", "target": "QisSQZ7Et7Rfzx2SCC2o9UDSeRZWMyFKWc", "level": 2 },
{ "type": "ACCOUNT_LEVEL", "target": "QWinRb65f2g3yBoaZvTrQKQk7CW7vfBgGX", "level": 2 },
{ "type": "ACCOUNT_LEVEL", "target": "QXYk68x2tiUrDBv8eq6wd4KtBmLHYiC4zR", "level": 2 },
{ "type": "ACCOUNT_LEVEL", "target": "QisSQZ7Et7Rfzx2SCC2o9UDSeRZWMyFKWc", "level": 2 },
{ "type": "ACCOUNT_LEVEL", "target": "QTyokTJrR4b2y76An3BFUEbqQy5vvg76iN", "level": 2 },
{ "type": "ACCOUNT_LEVEL", "target": "QPyx2bNiAnJEjitfeAh8jZXzQVKio2B7Mi", "level": 2 },
{ "type": "ACCOUNT_LEVEL", "target": "QMJwdufHY9dMoARHCUyGbMPAqUB4BcqGKm", "level": 2 },
{ "type": "ACCOUNT_LEVEL", "target": "QNffmqjCUQsLaHLZXBaA47tQW7bERdpP4R", "level": 2 },
{ "type": "ACCOUNT_LEVEL", "target": "QeUc5NfzubZD4eA4eJ7bfXAFraAWc7jVz5", "level": 2 },
{ "type": "ACCOUNT_LEVEL", "target": "QfPcwetW3BErP4ySTurxFJSHpNkNXPEhGk", "level": 2 },
{ "type": "ACCOUNT_LEVEL", "target": "QMMh94Pfs5LVE4xJee1yggViqP1YDdQHT4", "level": 2 },
{ "type": "ACCOUNT_LEVEL", "target": "QQHWFSJpzuDupPfcTvGMRNJp2UGz98Kb7j", "level": 2 },
{ "type": "ACCOUNT_LEVEL", "target": "QdepeWWLMD3LiDRKkKBria29rDhZP9V5RJ", "level": 2 },
{ "type": "ACCOUNT_LEVEL", "target": "QeSh3t1AnaRcRThkkUTvvdMEouixCADeVh", "level": 2 },
{ "type": "ACCOUNT_LEVEL", "target": "QMMh94Pfs5LVE4xJee1yggViqP1YDdQHT4", "level": 2 },
{ "type": "ACCOUNT_LEVEL", "target": "QWez1VBfYVJ8KFoZ6MhJDzYVLbn5mr38VT", "level": 2 },
{ "type": "ACCOUNT_LEVEL", "target": "QZiYh4m4Uh3FH52cnow8MrNyXhSH88bp2H", "level": 2 },
{ "type": "ACCOUNT_LEVEL", "target": "QYsh2NB6TogqV1iXHmHXcVaWw25WEYA94o", "level": 2 },
{ "type": "ACCOUNT_LEVEL", "target": "QU9dnN47Nc5KaH7JwNoCZ7TANwCW5VX9iG", "level": 2 },
{ "type": "ACCOUNT_LEVEL", "target": "QZjCgcSVvSRsFZeLJz9C5dTa36s3cSKqvB", "level": 2 },
{ "type": "ACCOUNT_LEVEL", "target": "QXoW5M113C3SDUeFdjVVs2m1RT9XtzXS3z", "level": 2 },
{ "type": "ACCOUNT_LEVEL", "target": "QfhJM5CpX5MhfjCcopwfw7pgS6w1hVJ49E", "level": 2 },
{ "type": "ACCOUNT_LEVEL", "target": "QZjCgcSVvSRsFZeLJz9C5dTa36s3cSKqvB", "level": 2 },
{ "type": "ACCOUNT_LEVEL", "target": "QjsD7pMh2LpmgYudaaRM8gmzhBC4c9uwyj", "level": 2 },
{ "type": "ACCOUNT_LEVEL", "target": "QZxcntxfJimHus9pgfvVVPbpHs5yU7ZMhB", "level": 2 },
{ "type": "ACCOUNT_LEVEL", "target": "QRBVE5gHumH6RUgEUxJdQ5417NUvc14k3F", "level": 2 },
{ "type": "ACCOUNT_LEVEL", "target": "QakZtV3nZeRp5UEoEUNX7p8Qz5VbVXTQid", "level": 2 },
{ "type": "ACCOUNT_LEVEL", "target": "QXeiJa7ftN6dAYNZcdqKDBhmAFyGjX2zkm", "level": 2 },
{ "type": "ACCOUNT_LEVEL", "target": "Qc2eRuYXtpATrg87Pr1WDXkUBCsgNRYcQF", "level": 2 },
{ "type": "ACCOUNT_LEVEL", "target": "QbEJ5tfeskASVnmLFeKzspuZJJb6cJVPZ4", "level": 2 },
{ "type": "ACCOUNT_LEVEL", "target": "Qc2eRuYXtpATrg87Pr1WDXkUBCsgNRYcQF", "level": 2 },
{ "type": "ACCOUNT_LEVEL", "target": "QYpYshfVzMMVU71KqAV4erX3NJAUCnJXgD", "level": 2 },
{ "type": "ACCOUNT_LEVEL", "target": "QViKVZa3M3ar7RBRSBMTx8FdzLh1zxUhN8", "level": 2 },
{ "type": "ACCOUNT_LEVEL", "target": "QWvTdm9LU1GSX9q6Rrvgx7xjo2iuV2Gxn1", "level": 2 },
{ "type": "ACCOUNT_LEVEL", "target": "QMb2cAKb2BUxknneuoXQynJ7uzosJ57Top", "level": 2 },
{ "type": "ACCOUNT_LEVEL", "target": "QZw7tgMttSySNMKfcMrEbdtnqHVrQ9w9fT", "level": 2 },
{ "type": "ACCOUNT_LEVEL", "target": "QbGwYRV3UDek4VNpzoAVVQWfoKjZmm4qPb", "level": 1 },
{ "type": "ACCOUNT_LEVEL", "target": "QWvTdm9LU1GSX9q6Rrvgx7xjo2iuV2Gxn1", "level": 2 },
{ "type": "ACCOUNT_LEVEL", "target": "QYM76r3tuf6FJWz2USqVjwXJrV9tLMz1cj", "level": 1 },
{ "type": "ACCOUNT_LEVEL", "target": "QfnbnWrRQ4HNDQvtg3wG2B1eC4ycUsFqZz", "level": 1 },
{ "type": "ACCOUNT_LEVEL", "target": "QUdjqijDoyc83K4WcMW1sCn7zLd2t1WTqn", "level": 1 },
{ "type": "ACCOUNT_LEVEL", "target": "QbGwYRV3UDek4VNpzoAVVQWfoKjZmm4qPb", "level": 1 },
{ "type": "ACCOUNT_LEVEL", "target": "QRUbzEbLd7fRjAx2fBdXAH4QS1WQyetvDc", "level": 1 },
{ "type": "ACCOUNT_LEVEL", "target": "QU9K6pWhkok9mokkPDoCwgSu3j62ECuKLD", "level": 1 },
{ "type": "ACCOUNT_LEVEL", "target": "QaZs97g4Mbq9tXMoBWbhw3jFvBBVkWKS5F", "level": 1 },
{ "type": "ACCOUNT_LEVEL", "target": "QcJwVCyzraPy51uB4xd4f94n2UFYAsznGC", "level": 1 },
{ "type": "ACCOUNT_LEVEL", "target": "QMb2cAKb2BUxknneuoXQynJ7uzosJ57Top", "level": 1 },
{ "type": "ACCOUNT_LEVEL", "target": "QZbKMgYBwWGyTDJzoDir2aNqXsFdDp6fS9", "level": 1 },
{ "type": "ACCOUNT_LEVEL", "target": "QZ7wvWAUcHKRhvQ3ijdrqM4zucQKCgQ1hQ", "level": 1 },
{ "type": "ACCOUNT_LEVEL", "target": "QgmEtScSZWJmTUAidCZKj6gDr3LznZ6rr4", "level": 1 },
{ "type": "ACCOUNT_LEVEL", "target": "QVuksgNt3QAr7KCrkxtE5FWrczfgLKxs4H", "level": 1 },
{ "type": "ACCOUNT_LEVEL", "target": "QQXgH4CnQCB76BbXhsApu6ShhohFfvoXv7", "level": 1 },
{ "type": "ACCOUNT_LEVEL", "target": "QQzMut6erjgSKCpZ1dHDcjKcj9KAce7cug", "level": 1 },
{ "type": "ACCOUNT_LEVEL", "target": "QZJc1V32oFm8tufB4bk7fa3aepu4EdkeDU", "level": 1 },
{ "type": "ACCOUNT_LEVEL", "target": "QZbKMgYBwWGyTDJzoDir2aNqXsFdDp6fS9", "level": 1 },
{ "type": "ACCOUNT_LEVEL", "target": "QaZs97g4Mbq9tXMoBWbhw3jFvBBVkWKS5F", "level": 1 },
{ "type": "ACCOUNT_LEVEL", "target": "QSkicapNH35a3UebSxxSMCfntBhwwi6veW", "level": 1 },
{ "type": "ACCOUNT_LEVEL", "target": "QLqZD8eiNk8nrzyWcURfQDS9T5NWx98vrz", "level": 1 },
{ "type": "ACCOUNT_LEVEL", "target": "QQzMut6erjgSKCpZ1dHDcjKcj9KAce7cug", "level": 1 },
{ "type": "ACCOUNT_LEVEL", "target": "QUdjqijDoyc83K4WcMW1sCn7zLd2t1WTqn", "level": 1 },
{ "type": "ACCOUNT_LEVEL", "target": "QfnbnWrRQ4HNDQvtg3wG2B1eC4ycUsFqZz", "level": 1 },
{ "type": "ACCOUNT_LEVEL", "target": "QU9K6pWhkok9mokkPDoCwgSu3j62ECuKLD", "level": 1 },
{ "type": "ACCOUNT_LEVEL", "target": "QcJwVCyzraPy51uB4xd4f94n2UFYAsznGC", "level": 1 },
{ "type": "ACCOUNT_LEVEL", "target": "QVuksgNt3QAr7KCrkxtE5FWrczfgLKxs4H", "level": 1 },
{ "type": "ACCOUNT_LEVEL", "target": "QZJc1V32oFm8tufB4bk7fa3aepu4EdkeDU", "level": 1 },
{ "type": "ACCOUNT_LEVEL", "target": "QZ7wvWAUcHKRhvQ3ijdrqM4zucQKCgQ1hQ", "level": 1 },
{ "type": "ACCOUNT_LEVEL", "target": "QQXgH4CnQCB76BbXhsApu6ShhohFfvoXv7", "level": 1 },
{ "type": "ACCOUNT_LEVEL", "target": "QXpCzKnANyh8jrRRxemvsXHAeFRHrDvdXF", "level": 1 },
{ "type": "ACCOUNT_LEVEL", "target": "Qd1Xw41BzN1CgASqsh2PcrrkTKyDs2MVYF", "level": 1 },
{ "type": "ACCOUNT_LEVEL", "target": "QVEfMEeEHP768c9rEyi3WcH2JQwqheVDe3", "level": 1 },
{ "type": "ACCOUNT_LEVEL", "target": "QehM6hQZVkrYAst8WrBVdQiMFfTtDtbKQu", "level": 1 },
{ "type": "ACCOUNT_LEVEL", "target": "QY7gQwZKiPYMHYjYASSDBhhAoyYaxmex17", "level": 1 },
{ "type": "ACCOUNT_LEVEL", "target": "QXqAtZJsLU6HFGTzuegjeUbmEW8cGEnYt6", "level": 1 },
{ "type": "ACCOUNT_LEVEL", "target": "QLqZD8eiNk8nrzyWcURfQDS9T5NWx98vrz", "level": 1 },
{ "type": "ACCOUNT_LEVEL", "target": "QPE3EGR8LTYSLtWBq52SFte66oud6cxchF", "level": 1 },
{ "type": "ACCOUNT_LEVEL", "target": "Qa9NXeLAvmaTsKackfDRmm1A9zy7JeEjgV", "level": 1 },
{ "type": "ACCOUNT_LEVEL", "target": "QXqAtZJsLU6HFGTzuegjeUbmEW8cGEnYt6", "level": 1 },
{ "type": "ACCOUNT_LEVEL", "target": "QXpCzKnANyh8jrRRxemvsXHAeFRHrDvdXF", "level": 1 },
{ "type": "ACCOUNT_LEVEL", "target": "Qd1Xw41BzN1CgASqsh2PcrrkTKyDs2MVYF", "level": 1 },
{ "type": "ACCOUNT_LEVEL", "target": "QVb7P596imucSwDqb5HHEjqpnAre615PbS", "level": 1 },
{ "type": "ACCOUNT_LEVEL", "target": "QZgDpQ1Vyi5pNNe2ZzBRhcUgSU13yeWvmH", "level": 1 },
{ "type": "ACCOUNT_LEVEL", "target": "QTgfJFaEj5wuFvufwjUmYPhQ8eVfHR4u22", "level": 1 },
{ "type": "ACCOUNT_LEVEL", "target": "QNMtHRjEWPgZUVCpiC2qE2LyJsxq1BC1Yj", "level": 1 },
{ "type": "ACCOUNT_LEVEL", "target": "QUNTxxwr9f47RcwY48ZUP6FmmKnWCsxes5", "level": 1 },
{ "type": "ACCOUNT_LEVEL", "target": "QiQernJdiP4zCECB93v247FUtovkfJyTYm", "level": 1 },
{ "type": "ACCOUNT_LEVEL", "target": "QSTDdguUfKe6TaQrik2zq4Xrbu6unxNa9o", "level": 1 },
{ "type": "ACCOUNT_LEVEL", "target": "QVb7P596imucSwDqb5HHEjqpnAre615PbS", "level": 1 },
{ "type": "ACCOUNT_LEVEL", "target": "QQfFiVWkyAmfs1hDUcWU6XBfonxLdz6RSN", "level": 1 },
{ "type": "ACCOUNT_LEVEL", "target": "QgsxpySDm1qja6D2EyKuHPiUhyTM1RMk6c", "level": 1 },
{ "type": "ACCOUNT_LEVEL", "target": "QgJDnJF1zfoFBxB1LRvi5Rxb8p345UmVos", "level": 1 },
{ "type": "ACCOUNT_LEVEL", "target": "QWZwi5NUzHuJfg5fh9HzjWRpQc1fmMknAh", "level": 1 },
{ "type": "ACCOUNT_LEVEL", "target": "QP9vU5yTsBjuTSFxH5Cb9VXYNRHKhMNAJ4", "level": 1 },
{ "type": "ACCOUNT_LEVEL", "target": "QXm5e16Lq6dnYwpZJ8Rn2cME3ziHZfRRnp", "level": 1 },
{ "type": "ACCOUNT_LEVEL", "target": "QWkLaA4fYK51m4CszSgBd7efoDsSKamdf1", "level": 1 },
{ "type": "ACCOUNT_LEVEL", "target": "QZgDpQ1Vyi5pNNe2ZzBRhcUgSU13yeWvmH", "level": 1 },
{ "type": "ACCOUNT_LEVEL", "target": "QiQernJdiP4zCECB93v247FUtovkfJyTYm", "level": 1 },
{ "type": "ACCOUNT_LEVEL", "target": "QUNTxxwr9f47RcwY48ZUP6FmmKnWCsxes5", "level": 1 },
{ "type": "ACCOUNT_LEVEL", "target": "Qa4KygekpW1Brr3D66K6rGtS5AY8qkhvD3", "level": 1 },
{ "type": "ACCOUNT_LEVEL", "target": "QQfFiVWkyAmfs1hDUcWU6XBfonxLdz6RSN", "level": 1 },
{ "type": "ACCOUNT_LEVEL", "target": "QgJDnJF1zfoFBxB1LRvi5Rxb8p345UmVos", "level": 1 },
{ "type": "ACCOUNT_LEVEL", "target": "QWZwi5NUzHuJfg5fh9HzjWRpQc1fmMknAh", "level": 1 },
{ "type": "ACCOUNT_LEVEL", "target": "QNMtHRjEWPgZUVCpiC2qE2LyJsxq1BC1Yj", "level": 1 },
{ "type": "ACCOUNT_LEVEL", "target": "QP9vU5yTsBjuTSFxH5Cb9VXYNRHKhMNAJ4", "level": 1 },
{ "type": "GENESIS", "recipient": "QS2k8PMpmvUHzFDp2JfbVxRo8SiGiV72xx", "amount": "637557960.49687541", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QUM95jekFMye3Lmkg2gj7CdhFreYye4DaA", "amount": "400671036.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QaYAUoT2SzYA6kqBEW6W11brkYpSf7g4Wy", "amount": "352652351.30705076", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QdoLZqB3pY4NgbSpvbsFzBDq2LcShjXsoq", "amount": "263574650.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QTnPQVphizFhBFtSbTQzAC4Kubj4uqpv96", "amount": "232356835.62157121", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QMtx2UmUuRZckCmRJRyxdzSAazHP8hU5rA", "amount": "160672815.43629771", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QVz1rKM9QQrGX7gkxR9EV8a2T4AuD1QT87", "amount": "128506517.58266672", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QUUcETWLi7EcjQ9M83Aur3SwmESFBFuQye", "amount": "126998534.24906898", "assetId": 1 },
{ "type": "GENESIS", "recipient": "Qa15fMdEiY7WqK2xwvdHrdhU2TE6yk3V3L", "amount": "122690068.33700012", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QM4LF9EzQnXJ9VyFwnJbJzskJLrybuSzsw", "amount": "114361793.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QQKeokRiFCgAhBSdu1DUf5e1LCkgApvrxZ", "amount": "100000078.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QiaaoNZ54wKoaUMXxW72UsPt1MiPpeUTWm", "amount": "100000031.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QYoRWAxw6CVMeYeWHKJh3csmTVkVzjpdBo", "amount": "100000026.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QPwLkxemtGsrdcPJSA4iMgKbBPdecL7dwZ", "amount": "99999999.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QMkgf9Y6Ac2TUrynDvyhX69ekpC3P3GQmN", "amount": "99008835.47860426", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QUnfm6t9SrhgFB8YqCV1vQhmqFzhncxioK", "amount": "80000207.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QWZm17rRXeUehcM4TprVNNRSTHWQmG2bME", "amount": "62663714.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QMWSbqWDC1eHU7fCGJ3UwABHiTxBgBJGRC", "amount": "60036435.28601510", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QXdcbgN6vdQRBKJzHSB2JaSDDzPEsTUi14", "amount": "49450000.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QczMrVLEnEAXwvucwrfasH9H7QHNMPxLKu", "amount": "46488501.53473695", "assetId": 1 },
{ "type": "GENESIS", "recipient": "Qaa5hs9dj1gPxhqigH7tVyZ3oRuVcMULXB", "amount": "44766868.70000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QQMswzmv65MZSdafqzCACm3tbLj23ixkVR", "amount": "42447489.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "Qa95hURaNK4kPhDhbdmDFm2wMkkoWFZ4Zz", "amount": "40976709.97984710", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QVEde9kJt2ejqXavBzK35zY3eztnkSXTwN", "amount": "38172492.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QZjDFrMrgeR9SGL8bprqyiXVtAZMVCXESZ", "amount": "37291276.42323117", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QPmmPjdDXW35ykF557KbaVy5jYBbaM3pRp", "amount": "33333332.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QaWReiLe5tAsTNoq7hMiSGNasJEXTX5bmn", "amount": "31465829.00562504", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QLrErVJvpRdYAt4dGwk8i8rG3PMUSgpp3i", "amount": "26593505.67231102", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QYsLsfwMRBPnunmuWmFkM4hvGsfooY8ssU", "amount": "25303386.66951814", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QgULJXk3qyLDr4EKkz48rxwwvWK7BDL8Ux", "amount": "22476856.86000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QNwedkYs6hToBRPZvBb6naKvDEgB7BeaKi", "amount": "21967101.30582773", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QQ2DsD3fKWy88dQ6PMmdaNkAwSQrGof9aX", "amount": "20555555.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QgP7vQABSDJwuE8VTdVkiNnbkorqWWctQm", "amount": "20442665.63310002", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QM5Fg3kBzj9BqsGAJKHhsEtgRgf4x2Yn5G", "amount": "20000006.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QLdC7wm9PYC1KjgZaeVdGzH4M88BYhEeEw", "amount": "19927901.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QLgBTDkt7t7WwbTd1RM8iGxu9FexPZcn7C", "amount": "19000025.40000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QQShonrAdJXg4CzhegtynzaBP7S631vKGq", "amount": "16577946.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QM5gE4YYAL6byjpbbGH26TvuCUPmjcksY9", "amount": "16000000.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QMFJGxGLCbegHL3NZ7HRo3UYaCXhweJVKS", "amount": "14977498.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QcAVj3i3drVkiC9P8FtDgVdYz8Zeu61uNg", "amount": "13142764.62938834", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QTDB2mmxEU7L4oUnmBh9F3qhCxfVpJUzaA", "amount": "12960806.75574690", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QLgewDr48AZxWbDbRZ12xEvCePwPj9bMBE", "amount": "12684967.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QPEoMF2dA7NHrHhsSG9zczCFwx9wFdWvzT", "amount": "10033147.61257500", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QQSiZAgdMjadXZRVTHa252AAY2yjZfQKR1", "amount": "10000000.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QWLhUT55hZHgdD7ayEqwHnF9h62dHV45G1", "amount": "9999999.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QjnHejJPyuyTRs7huzcqXtbM6sufWyLf9o", "amount": "9999999.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QjJjjBUJSZAMuYiwTyfJTFthH6SrofjG6d", "amount": "8871800.22712502", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QRh9zNPmz5McxeWx3iCy2xnBRrBoYDZfMJ", "amount": "8739995.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QWGwGBzZG8UeNXJYZq9tjzN6ceriqTgMRd", "amount": "7825760.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QRj4VNEthakckhYpCJMEBhEFk12pa7GPJT", "amount": "7810001.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QbdHzyw6q2hMAhg12JFbXSEcbrj43xNnYG", "amount": "7593814.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QNFWWhmsun3HVUChjck8XqhsQW2tckNxxM", "amount": "7239123.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QeosmemBtE7aosv2QZhq1PTjqpKurKXKzy", "amount": "6185646.80750010", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QU8SzA12SVKPgqpTzRna5itKktGhvEdmgP", "amount": "5931486.64530000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QNifyiT4LRxtkTp97VgJ23LuZ3qqTgLc33", "amount": "5580161.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QdxuYD638XE84UGdKnb8QPoss1shkMRKYw", "amount": "5389659.68181819", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QQM9B3ipQcSvATsxb4vgVuYs9kuxgnuBwG", "amount": "5032347.69657146", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QLvdNNMagFRS3B25pAknRH1LMv5urwufHL", "amount": "4996995.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QLu6X6CVhr6tovNR6k9pQijxN4DHiuLeJS", "amount": "4918400.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QZgmtZrVPHd1rbHaRjF7zbYdnnS8a8mVZ3", "amount": "4418544.42625000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QQ44LmLWzkWWe2UJfdvsySkd4ZRs1is3sX", "amount": "4384750.69674618", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QNK7x1kgiA45Zvc5jZFaoDeDzrBrxivwYd", "amount": "4135907.02294526", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QPdkHcN8DBLuxaFf4Q4zXNn3rq2G78VgNt", "amount": "4128493.31116798", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QMNdPz11XubtvxXLGeiG3PHKaQW67LkZMp", "amount": "4056950.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QhUWSWWFt6vDy2qNFn68JPTPLjyDrzrh4D", "amount": "3220564.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QQ7wKkXyQtkwrgG6zeSHNrnDhXQ6fGUsYS", "amount": "3088411.29750001", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QcxXT317FQi3mq9hrQggw7xZujPX63XCq7", "amount": "2779141.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QMTcDA7v1dbRXHkryn4ppiM9Q7LGZFNkVT", "amount": "2752011.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QTekAqCoXPy5R7NE78GXxjSaXBuekZge88", "amount": "2740816.93000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QVQErjTVBkPTphbyDhPvhHKGX2etNXZsgy", "amount": "1999999.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QNPfWoyuvYaScXuCdiCM2PEzvD7PWybBvh", "amount": "1749402.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QaGhALQm9h3G6m728ZM9L2rrE7Y63nk9e8", "amount": "1637480.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QV96D15Y3qM1r55odiDf7j2TTYY8jCMJPs", "amount": "1572208.70562500", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QLsEy8syg8EXttzajE8AWGg3CxccNR6YML", "amount": "1300001.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QLeNqYjzid2oJFL2iVtcHeAkyunuTsHae4", "amount": "1185185.44326193", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QaUhQiyBZpZWSmFo56zRu2XXfZVL4D2xii", "amount": "1033451.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QQYnwDmw6Kar4HzoSEG1m2p83mqnY3div7", "amount": "997720.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QMrECKgkohx6ZXEMdLzikqBmAkdyHeQDqL", "amount": "997498.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QR1GoYcR3iasGNWtJjeNcZFPf9BH4TjCeC", "amount": "858153.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QNsdyTKN7MVsTYqSyZNPatzRTgNyWDqPZW", "amount": "832163.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QMK8v74dCqJ3d3p4JMZZknvQ1wEJovHEjd", "amount": "805444.62885757", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QMFysMKXLubP21BCYeYiSNP3v5mLhp81Cd", "amount": "676881.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QMdfkckSTcCMKnRRve51SEfZQPVLZg9S7b", "amount": "333086.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QN2UFyKfUsJBHmM5hZrrD2wrwZ1SctSfwQ", "amount": "296438.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QMrSxECG64fp2JaoXkw9DYEp6jFPYmh5Jr", "amount": "124684.50000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QQRUVwMGifreiLv3FuPTTcA1WbsvhH7Mux", "amount": "102825.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QMQAG28pyv2aZVjWbKoRn39Ytir6rLPnTK", "amount": "100745.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QWY9HkkCvbKJaqWMEkLbbAJcncywJgy31Q", "amount": "100004.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QQDpKmaKa4uz61CSGVD2zSFaQzeRjaQ8FT", "amount": "100002.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QRQvk1PCt7pNm6JSohtEQQdMEu7BrWjmtr", "amount": "100002.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QTmKWUXtuQ3ihfigtuLSeFddskmSy3fGN5", "amount": "100002.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QV6rVM4Ywh7TzdN1hgSWVrArKxyEUMuMfs", "amount": "100002.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QTBjfXU6ctuRFBTGkcJySZLqB3L9BdxNbb", "amount": "100002.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QVdEoLEamBDg8MC8uoMe6fsWrpbd9J3mTL", "amount": "100001.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QW7o4Cd6tvFLe2WryK4eNmFCxz7a9Bs5pJ", "amount": "100001.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QWCEJXDjfZLUfgCJ5V6Uy16KkdigLqEvvo", "amount": "100001.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QWCTesfyodV4CnKF5cP1RCnksEonZhyJEj", "amount": "100001.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QVjAVUkKjFHX9N2UQntuPWqHe3PGdBgKgx", "amount": "100000.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QVEuVYqdLfkAyTU5paKkSj2kGid5X7upPi", "amount": "100000.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QV1FfiEGhYRfNSN5HeXL6ZB1t2NeQJ761N", "amount": "100000.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QUqDXJzwKrte2YNpR4b44ttKWwnSk6pRTZ", "amount": "100000.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QQEBW8LU48cuV4uiwQUJ94k3UkJo5eszeg", "amount": "100000.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QgkNZyrj4zAEAwCDZHTLQ7RcbcCTwcNzPL", "amount": "99999.09400001", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QLecTtNrkgGFAwsSPVpJLgkqdjmjLmsT3F", "amount": "99999.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QVuKDbWVeM92yEta1fagk79NGQsLkvKdCf", "amount": "99999.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QW1NHhKUUPGg2WwMxNyV8VbzmEJJfBTXvy", "amount": "99999.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QUVkDZ2ercosZrRBnC8YZt7et9sXfwfKP4", "amount": "99999.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QQginD86frBSnoNjHEk7JE4zaBWbTqnHk6", "amount": "99999.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QPNuGywKCewuy66DXj7GAJrev5HkcVWjuu", "amount": "99999.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QR2cz8ErrLtcos58iKrQA9aPa8T8kaGc5w", "amount": "99999.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QM1KcVDtpkfsrSyDx6v9okdigbXrNTDKmZ", "amount": "99999.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QVvq5dE352hhujmDcoj6CaEajp7mBuAuKK", "amount": "99999.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QWHinsRV49Nf2ue2ZBumK8jWHWrSFhHr91", "amount": "99999.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QSYyv5yJBhkrGeB33NV3NbRYTys9mfkzze", "amount": "99999.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QMHjTaHRi1zTRgXq1vddRiuMBxZpHEJFeb", "amount": "99999.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QVxFXg9QMJp7FFD4XPU2BXR4hySCB9u9Zm", "amount": "99999.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QQa6YP9ocmaMBZPEAPnY7dDekdqo9os4gr", "amount": "99999.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QRdmMSmXk65qRYD8r1yzMBSKFdZf89nmZ8", "amount": "99999.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QLm1JSR6mph8vXrJ4ZHj5PjchAP7UoN9zm", "amount": "99999.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QPSEbEbncD9aUrSzNie658DuoFgex641Sd", "amount": "99999.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QU54p4EUyEr6EDEK7tmWEgwMBkr5MHNJr9", "amount": "99999.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QRMbQS8Tz9tEH5Wxg6GcoqJ22NqVEcVLFY", "amount": "99999.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QSu3hoF3n9qjMSBfmEn6bGVKNXpn4SrtiX", "amount": "99999.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QQodoJ4PdvwWbPwLErJsXjxtrW2BDZ52Gd", "amount": "99999.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QLpBVjVAxhygwbvHyGLLnKbyvsZ1jUqaWC", "amount": "99999.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QPbWwDxBK3m1LU7uPv55Rq8bwvqLsSjXiE", "amount": "99999.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QVeQRaWcPUNfT3yPWtm6xmuy1hM4N6b1yg", "amount": "99999.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QU6Ac4sVimeehkuK8XcbpFUBVatZ2qgd47", "amount": "99999.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QSCdg6rRRm4jb3r4XNKVytyQAgiTxkAjTw", "amount": "99999.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QQMFd2LfNJCyH6jkQkrK7mdKpjWiJhhFty", "amount": "99999.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QRSeGbHVKqhbUGNzF33DhoJQiPpf9WxZWR", "amount": "99999.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QQ5jfYBm2tPH4xCdfWDccnzUxhNPHL3VS3", "amount": "99999.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QW4jfDfsVWHLeLLvdapCG23q5MGajh8mKr", "amount": "99999.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QQAiKxdyRuKT7vPTQGbTitiN4E1WVnWZFa", "amount": "99999.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QTrMWWXU6WyuBRW3WU6duNXpe7AbNog4kP", "amount": "99999.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QQszEg4sqt2o5oN4pdyjxrAwGD3TY5QNfZ", "amount": "99999.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QQsuUfQg57meYToLCx7GTH2DYfAfe4KgQx", "amount": "99999.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QMAsEHJcNAk6Vkzik2bEV6nJ7JZjWAksvi", "amount": "99999.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QP9tgb3pN6FzU3UCmHGW95s9Fmgubzm8md", "amount": "99999.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QMdEATyg41BFbWuMZx829uzBZ8fqYDDERw", "amount": "99999.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QVKtHgZutURJdqnMCRRewsi2WsFNmYGoEE", "amount": "99999.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QVfFNmHCgWFHhUwLBNBYp32JjL2uusyfsW", "amount": "99999.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QMMsPuLhww36VQAphKyo29BfhHLeekf8ST", "amount": "99999.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QTAth8ahCmt7ZchLA2N6S5jbF5wCMzyC8H", "amount": "99999.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QLzhvyT3LHzE9DqFrSwNivKMzkB6T1g2DD", "amount": "99999.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QVfru93ZBLd5XR9YzRBbFHCuYiNV1hJhEw", "amount": "99999.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QNG1SXqh2uJ4FAy2yxEzUXbRh9Z5KsTvq5", "amount": "99999.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QRMpfSZ3gcX4gZKCXHam36qpaCC49F2cEt", "amount": "99999.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QSqcH6aiqnGnP8iGG1cRxMSqtypsqgWprm", "amount": "99999.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QPJbzQTJ7T8fRFu5NbGu9goLZyeLq5Kg3i", "amount": "99999.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QVXchvzB4PaaXSwhyZcayiTTSe5cje3DE8", "amount": "99999.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QMTN4ALHmKeJkbVFQQyceEAideDTb1eXdq", "amount": "99999.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QWR8npVGu3w75p5WtgxMikcnujVHEuPBxL", "amount": "99999.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QRPAESx3b4pWw9C4Q6YLdNYUcCQsWbzEfE", "amount": "99999.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QRWVqUTDFQRRAKiWjkFCk5pdE1iDw2Cz8s", "amount": "99999.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QMrodSbGSU3zsJVi8BN5X5gPE2xvXGt1ja", "amount": "99999.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QNLsfHsFcS5xc7BR1fD6aNSZLPt8CEKLuG", "amount": "99999.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QM66cjqmF3WCbbiac6tVU1pLupv2AvU4ev", "amount": "99999.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QRPnVABwiqT3qBqC6bscfWU6R79RjhUfp9", "amount": "99999.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QR3mxAS6xdwsRGQXiMVngYmL953gdKcaoa", "amount": "99999.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QTHLtnDuCz85Xa2jkPuZKhdvyXh8yZH6NJ", "amount": "99999.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QWA1V7vrRLuhibXjRCSG54y4mkTLf3a4T7", "amount": "99999.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QLnyXdhGDVEBmByTtjTZihVE3UnReghNyd", "amount": "78296.75000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QUcPy91Rx5kgbzrZCbR1BgbCLEZx2u3Tqc", "amount": "49996.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QTHQ9pFMoraWTCS1tuFzuwWSXu7uVJ18VF", "amount": "40395.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QfNkfy7kZ2gwue4Vsoz3YfARwSCS13cwXN", "amount": "14997.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QN9QJxXAT2ceDwgXvSvjkndeUGXW84xfhQ", "amount": "8505.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QVZAQrQHPD8rePZ5hWKx4uj7Ty4GEBe15D", "amount": "2989.50000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QPZTxWtCmH6Y6zwwntjnPDfKG6zNKRivqJ", "amount": "1282.61375000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "Qgwuvmkqf1wTAxqyFpvUswDqgzWrC6towa", "amount": "0.66600000", "assetId": 1 }
{ "type": "GENESIS", "recipient": "QYgVi26jUqMzJo4ahZV9yekQNnYKHBaX8r", "amount": "637557960.49687541", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QWe1iPDudLU189BggPykbH1DrAeaFEgX6W", "amount": "400671036.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QVurebcEbe4USR4xcS3Mbk12mhxsjRX31u", "amount": "352652351.30705076", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QfhJM5CpX5MhfjCcopwfw7pgS6w1hVJ49E", "amount": "263574650.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QeSh3t1AnaRcRThkkUTvvdMEouixCADeVh", "amount": "232356835.62157121", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QM9zVbXXnfrtQ1X7zPQ5zxPYPAWTaVMXqZ", "amount": "160672815.43629771", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QZkThgFfExognAbxLjYZGVHpL7X6g3EG4A", "amount": "128506517.58266672", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QVSqUrNFR4mPTMa7UdVmNKZTSaDVAv8XXF", "amount": "126998534.24906898", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QXKmtkHHwaUQzGeHHG2dFiHUnKAp815Mzq", "amount": "122690068.33700012", "assetId": 1 },
{ "type": "GENESIS", "recipient": "Qft1ktvJ14eBFjpJaphT24ks4WRcN3K6tB", "amount": "114361793.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QcrnYL6yNwHKuEzYLXQ8LewG3m2B5k9K5f", "amount": "100000078.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "Qe9VPzQp3h4Kg3DHSHBUQ3AM3AiRBfCDfX", "amount": "100000031.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QPYfRd1uhnAgqkZNmjNCjgPhkguMnHWuc4", "amount": "100000026.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QREtYDhP4HkpeCCZroemuGXMGVFoZHH3Lp", "amount": "99999999.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QaUciVnbQDXdNygJadEY31PuDEBLi6Spmu", "amount": "99008835.47860426", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QjrC8NXwR8gFkEauvRwCPxqHroPFqAJbhK", "amount": "80000207.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QiBYApdEYRwsFYjt59UJqZV55wcwykvhsh", "amount": "62663714.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QTTrv8SWR8huV8TFYUEQhfZ1j1JmtL5p8G", "amount": "60036435.28601510", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QaPKuyyQtXJcsVhKLKgxCcYewxwaawxLrB", "amount": "49450000.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QbVGj7U4C2tQfipTss4tSzC22bbrF6fNnL", "amount": "46488501.53473695", "assetId": 1 },
{ "type": "GENESIS", "recipient": "Qd1Xw41BzN1CgASqsh2PcrrkTKyDs2MVYF", "amount": "44766868.70000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QdXe21sjY8smjVmiAUgZY8xWVzwgxMgK5A", "amount": "42447489.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QYsh2NB6TogqV1iXHmHXcVaWw25WEYA94o", "amount": "40976709.97984710", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QZiYh4m4Uh3FH52cnow8MrNyXhSH88bp2H", "amount": "38172492.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QWomBbcXNTdkyuPFUafwtBfpbxHzmUZzqi", "amount": "37291276.42323117", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QisSQZ7Et7Rfzx2SCC2o9UDSeRZWMyFKWc", "amount": "33333332.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QP8xG56L8b28h1mguSk9LuzNhxbHgAoL9b", "amount": "31465829.00562504", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QfmM8dgfikTB2FYVuJ9owzQXVm8wP7T4QT", "amount": "26593505.67231102", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QWvTdm9LU1GSX9q6Rrvgx7xjo2iuV2Gxn1", "amount": "25303386.66951814", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QYhvqnVFapMA3s74Mu3D1DwPkEkcy1oKPn", "amount": "22476856.86000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QUzUCfoakDqBaL5zBgfvTKLHcuxbUfB38Q", "amount": "21967101.30582773", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QgJDnJF1zfoFBxB1LRvi5Rxb8p345UmVos", "amount": "20555555.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QRBVE5gHumH6RUgEUxJdQ5417NUvc14k3F", "amount": "20442665.63310002", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QbH8srDKeS5VcsQsgsaF3nqCzGT1NqfsTx", "amount": "20000006.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QMuWNAJ2tbeViHtBUN3yD2KARrrzcanLAd", "amount": "19927901.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QSBJuNoCAFUTcevuCTcMi3i5nzNPhC5R4b", "amount": "19000025.40000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QdGbhtkFHUqd9nK9UegxxGXD1eSRYSoKjt", "amount": "16577946.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QakZtV3nZeRp5UEoEUNX7p8Qz5VbVXTQid", "amount": "16000000.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QZvHW7amu5DNktsBgaMrR1brHZhhhVwKLW", "amount": "14977498.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QUNYcKorTAjcFEFH2kLuGzTHDSXHbTm9n4", "amount": "13142764.62938834", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QXUWuZ2oAUodMU8EAQkAkDwkQHS1SFxpts", "amount": "12960806.75574690", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QZw7tgMttSySNMKfcMrEbdtnqHVrQ9w9fT", "amount": "12684967.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QXoW5M113C3SDUeFdjVVs2m1RT9XtzXS3z", "amount": "11583150.66666671", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QdepeWWLMD3LiDRKkKBria29rDhZP9V5RJ", "amount": "10051008.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "Qi74UrbLEsCD9D98rRQrsszNtRA4pWbXaN", "amount": "10033147.61257500", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QawB5MesBratjs2d9EMnXnrN4EC7gw7LRw", "amount": "10000000.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QYpYshfVzMMVU71KqAV4erX3NJAUCnJXgD", "amount": "10000000.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QeaDGU85fpffwsw9ngmd98QsT6NaFyFFed", "amount": "9999999.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "Qeq85FoJpxtzoDM93WiNQQCXiuiFynRQzm", "amount": "9999999.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QbwgBD6LWdk1hZsb8EwdVVmDZdvpxMzyGT", "amount": "8871800.22712502", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QaFN7DWcGF7keNACJpwCVnegePbfsAoFCw", "amount": "8739995.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QfjL32jLsxtumbfx6ufmfCFCBccVCQFkrh", "amount": "7825760.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QNMtHRjEWPgZUVCpiC2qE2LyJsxq1BC1Yj", "amount": "7810001.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QdtAQm1EGNgM7QDSaC2qvV9WdpRHwpApUT", "amount": "7593814.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QbEJ5tfeskASVnmLFeKzspuZJJb6cJVPZ4", "amount": "7239123.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QZfctgpdTkoCcLzpeWSSq7nYQwCLPsoFsL", "amount": "6185646.80750010", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QZsygC1chppsJK1cnaHG4fEsNaDYfLF7ZJ", "amount": "5931486.64530000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QXmYaDzKQdGiAMncJCr1FqXy6tX3avMRm9", "amount": "5580161.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QgpAW6uqwNR58gNYWRCVXNLm4F5TuckAw4", "amount": "5389659.68181819", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QLqeoXwo7RMJUsTv2sKbBffjDvbSSQN7vH", "amount": "5032347.69657146", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QhjovpBRoW2BJ1vRf1xmBcEqENYzexryM6", "amount": "4996995.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QQNigUeJxK9WMJoGhydKpfa5y2mQp6bX7v", "amount": "4918400.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QMfWg9oJg49izXMeRWrsErgNnBD6mJcKiX", "amount": "4418544.42625000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QZjCgcSVvSRsFZeLJz9C5dTa36s3cSKqvB", "amount": "4384750.69674618", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QXeiJa7ftN6dAYNZcdqKDBhmAFyGjX2zkm", "amount": "4135907.02294526", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QLsQg11ipfS4ZaD3QTFwncEgCBPsr1r2hj", "amount": "4128493.31116798", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QY7gQwZKiPYMHYjYASSDBhhAoyYaxmex17", "amount": "4056950.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QfPcwetW3BErP4ySTurxFJSHpNkNXPEhGk", "amount": "3220564.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QUJhiF8h6ubsXhyATKHccLwD9cz1ECDpaD", "amount": "3088411.29750001", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QPtJ6prBJ1NR5NbtuEj7kMaM1a9FALueHA", "amount": "2779141.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QhAkKZZF1ZuC9ADgpfZ8BMZRVV43BiQhuZ", "amount": "2752011.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QZxcntxfJimHus9pgfvVVPbpHs5yU7ZMhB", "amount": "2740816.93000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QXHEZ4axuNq91K5wW9zaNSvtLzsdsQ1yVz", "amount": "1999999.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QPhNJXXc97CRBP4JMu2cgiumyfKciBHHyu", "amount": "1749402.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QZu4Ka4XHKgTJMc6Y8Kr9cjF12sMKQaPQP", "amount": "1637480.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QVEfMEeEHP768c9rEyi3WcH2JQwqheVDe3", "amount": "1572208.70562500", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QhdoF3Kt3dV5DkuPgTmvH3RNzgZrSK9o6W", "amount": "1300001.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QQ5qnof5pUgJem8NPsAPgYdENL88cNqSj9", "amount": "1185185.44326193", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QdceL6F94xZCUmXgrGPKCrsvtPtKmFprDp", "amount": "1033451.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "Qf2KXaB68Ca6E3uYty14DrEVXGJ6RMg8Tw", "amount": "997720.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QiFUUj4GvfHTTuAhFseuoWZm3wYemqxSDn", "amount": "997498.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QXjRdpB4gMjixwP7cv5yeyn8RQR9BuDhet", "amount": "858153.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QWez1VBfYVJ8KFoZ6MhJDzYVLbn5mr38VT", "amount": "832163.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QPE3EGR8LTYSLtWBq52SFte66oud6cxchF", "amount": "805444.62885757", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QZh58Nw9KfPHyhTbVa37iuqAsPGDSzJtka", "amount": "676881.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QdUkv47jHcpCB5mZvqEn47XZPzBnxfr8V5", "amount": "333086.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QVCzURyACQvd5sYkmMbg35YJvtZjsr2uGC", "amount": "296438.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QfVQpcqzK6RVRWESrrdgyVNmM9sPuxjBC5", "amount": "124684.50000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QRMSN3DDrfGcBpqAUfYJb5xz5DovCL2Qy1", "amount": "102825.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QWKYjxBUt2c6BHm26c4k7U8iF9eUEEAeQy", "amount": "100745.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "Qbcy4uyMkQF2JXYqGkueDiFNZ4tHjRg8CR", "amount": "100004.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QMtm8wVPHGE3qHg2hMaj6SZ78D5eXw3VWZ", "amount": "100002.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QPqfuZpmyA6cK6WUFwcGeKH2Te1aegkHBM", "amount": "100002.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QNMDKE7XTujNQkuQorcHXw6hL7qRvyaTjr", "amount": "100002.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QTpYQqRyMekaEuECziirzy3HvCVofZS1wJ", "amount": "100002.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QWL7kZp6Pdd1bhxZ6SXPhVf5g7GParG9CC", "amount": "100002.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QgcF6KgVZ9eDAMHJdSEeAtp91t931VKZMv", "amount": "100001.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QMLjFiPiPa46YBRoushe6a227kSrXnXyKb", "amount": "100001.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "Qgp16aMcdiS2EUkxCm5NSZgB8DixGK51zT", "amount": "100001.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QQPYyoE3Bm2vh8Wr5aaBNyirC8dd3BhBGH", "amount": "100001.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QRtRELSSASzqiYy2FtNcrePH6TVnqJkv9B", "amount": "100000.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QWwrtjBL4ah965XPXHYJhymreC9jyryNLZ", "amount": "100000.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QMJwdufHY9dMoARHCUyGbMPAqUB4BcqGKm", "amount": "100000.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "Qce2Djqrk2WzG1QhMZ3BqFok9HGsz4wtM3", "amount": "100000.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QcCBVfL35rxSyQ416L2MBz14FYbNrbeNPx", "amount": "100000.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QaSL21TX9QH3NTBTXbNCysB2zWRk3vkm6z", "amount": "99999.09400001", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QUXga5K8nzd9EqYtvEesZWEYuA688h6D3d", "amount": "99999.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QYD3kXchZ86vUyJBXNCVQ4LUvTAd6PUZW3", "amount": "99999.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QY1RFZTD2ogRohf3UrdT4g1Qo9D122AZDN", "amount": "99999.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QSGB4Rd2xhd6UmA9LALTQ4f89Tfsz5VajU", "amount": "99999.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QY6ZGZdi8h5op2VrRXkG1W5Jp3feLwp7ZD", "amount": "99999.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QRKRk5HVADsN1LHygK7q2pA7dWnYKnPpCT", "amount": "99999.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QUZQPWhrxpze32vGiux6wa85kg9iwuhCDx", "amount": "99999.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QUKKwug9PNai3DBggXUXP8Ag7WmR5SVUR4", "amount": "99999.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QgCQq4cFaGrJhwvKs4XwvccKiLZ8GVMCXR", "amount": "99999.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QZ19JRpSsgvm4z6EjnbhdxJBoUYzDGvP3x", "amount": "99999.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QjEaMxcBKMsj91ytKe6GdTBJP8Mu1Ru3r4", "amount": "99999.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QgqM5bKs3tNqKNAnVeaQp4oaYMXCmX6YJr", "amount": "99999.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QYGNMWBmqWgVtMWGHypAsKhDVQw5mrFZww", "amount": "99999.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "Qd1Px9vhWuEmF2SbLx3Ez7HhGtifGMa8TJ", "amount": "99999.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QRWEbzH4niUcu9dL3Yq42X4j89aqQk3qWw", "amount": "99999.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QScBgSw74MquesXmVJxerX3YgyhtShRr4q", "amount": "99999.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QNHdGeFJmPcDdN8prPzPL4bk2dpnJ2ZZFr", "amount": "99999.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QdXdUxnyKGGo7eEfTcx85oEikNe5nYnuwa", "amount": "99999.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QgVZb632eqF1eLQm9gBGuBtyp9Dyz2FKUK", "amount": "99999.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QXYk68x2tiUrDBv8eq6wd4KtBmLHYiC4zR", "amount": "99999.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QRCtc67FTNKS5zVXM8omw8F55h9DP7herL", "amount": "99999.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QPEbvVBWDG7qgy4smY8nWiie78Vec8qiT9", "amount": "99999.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QdFZk74skMUu4rKMPEmcSVwR87LNDe6o3Y", "amount": "99999.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QRaDef6H2zYfefqLwYGmUg7T6DAqo6DDqc", "amount": "99999.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QPREQjU2defiYdgA33HDiLNGBpxtuebeqE", "amount": "99999.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QWBFK5h61ZxGfqQpEkwwKTcLAo8t9VWe4K", "amount": "99999.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QjgGeEkyiXa43pyqkXxZbvAChQpVYfUyKz", "amount": "99999.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QcNmqT8CZ6zSZwuRm5LahRZnuGBJRnPY8o", "amount": "99999.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QSq8y4ZrSbF55ZddWNcw1ett2LDtjQEvNn", "amount": "99999.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QPusqAVBVFGAAeE7RdospttA18AuyLP7sB", "amount": "99999.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QNiTnonHpXTeUrgNdyYWVDPP4ZdjkLpW72", "amount": "99999.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QNw9xAm9TUerin9QsapCPL9mV6zmoXyJrh", "amount": "99999.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QbvxC3ENqomXp11833APchdjeyCNd49nLj", "amount": "99999.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QRWEbcRnLoGccAndtLcGgpeQFH2ZBcMqHo", "amount": "99999.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QTtXS6fZGThRLq4qgkwM4ngBYkLoFyZ3bK", "amount": "99999.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QgesuKa3zwx8VAseF1oHZAFHMf29k8ergq", "amount": "99999.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QhQdzLn36SDgrgoMfvdZAkoWtTUHpB3acJ", "amount": "99999.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "Qj5ncwncQY4KPYgKHD1eYpXHbR717PeLcJ", "amount": "99999.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QjrCFCi6dqvka4UELg2SHhM2oWnQWepd1o", "amount": "99999.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QNVKrjEq5bZdiDtgo64m5kz87rTHqCwvCP", "amount": "99999.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QMozpRT9aUunfmPh7EtQ6LPoth2JFJWBXC", "amount": "99999.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QUGo9SErgc6ceB5aBzcSJDNqBkQ9eaCKZS", "amount": "99999.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QbYTowTHCr9WzfrR6b8uDfJKwL41nG1vyr", "amount": "99999.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QQaKBSjAt9RK2bqJoSriR77X4ULstGzrFQ", "amount": "99999.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QZ2gi6BhUNpGmrErgJLFuY1WHy6xK1J7qX", "amount": "99999.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QWinRb65f2g3yBoaZvTrQKQk7CW7vfBgGX", "amount": "99999.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QTyokTJrR4b2y76An3BFUEbqQy5vvg76iN", "amount": "99999.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QMsKXQAYKmR7dBH4P3kMLiKzYatK3h1CeS", "amount": "99999.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QibuD4c6gvXgS4iut7q3sXuVb23rgFJq2M", "amount": "99999.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QfbX8JJupEw5ckNtU4upQgET35oLTr5e6v", "amount": "99999.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QbxJvwrEHZs7MDE8rbqBwZAZkcywue5F3W", "amount": "99999.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QYGcPZcRhGaY1MsiDr3VtwTXmB9TAbLFSn", "amount": "99999.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QPyx2bNiAnJEjitfeAh8jZXzQVKio2B7Mi", "amount": "99999.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QYuMATkLjz7YB6s4EG1aWCmmmrAPj3W9Ce", "amount": "99999.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QcU4VhU9ohDXU4k4AUMapgJRYSzEpizjLN", "amount": "99999.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QVrvy4ac2jBTfxyCKB7MLimqJooTDBApmS", "amount": "99999.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QawSgZ7i2LLFTKyPxQptk9gN526ihy5yZi", "amount": "99999.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QdwSxr3t4hdGHjQFy6EVGR9yGMipefsTuo", "amount": "99999.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QTGeQqn3XEFdnnCqvifCFXYdKym7SaHzTd", "amount": "99999.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QXm5e16Lq6dnYwpZJ8Rn2cME3ziHZfRRnp", "amount": "78296.75000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QeAa7yawpJqQYk7PNisVD89HezskBRecH6", "amount": "49996.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QfjNGq3SQt17Wi3e1Qp3cHJVeHMUHTe3Ar", "amount": "40395.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QYogZKQbwwQV2ym46L53RYNGp3WfACCufj", "amount": "37998.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QdyyAwnpPEXtHmcz6GMpDoJZmP7rR2EwF1", "amount": "35998.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QdceyQF75uirtzG9s7ZD2EeTMUAhWsoyFz", "amount": "35998.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QSsCvzpJhynqbNceqh2AYiLRHRZnCWcR9b", "amount": "35998.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QXRzrQyhShKoVUUwzGEMJK8p5RZqps8wa4", "amount": "34998.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QSzaKcGZscNAGR476Bi5VBKqv5meNPAZQK", "amount": "23998.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QPLPWkys8cyZicdacFreT7TT3a2Jg6dzL6", "amount": "22998.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QbBfYeGxPXpNkRMF5TrHA7tccjxCBeX5Vg", "amount": "22998.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QXKaS5ydZ4VRGrEvMr94FvNzf7onEbxetE", "amount": "22998.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QPfJ3FU6jnamrxZJDH3ijH5Tze8UoEmtGu", "amount": "21998.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QPZdi91axpQ9mkPbJdhGGjssmT5jaaZNX2", "amount": "20998.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QhUWEeKX8EAxX2KomuaVmV3NvZrAV4Eznk", "amount": "14997.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QUg3gHTxunZzZMv5j9BmNoGrDzQbC3tNtN", "amount": "13997.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QNryEH4ryCCMNqdpnSG7qdj1Yr1NN4dRgU", "amount": "9998.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QhyZ1DUi2vg4JmG6aLHxqsx2TfzhqCs7L6", "amount": "8998.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "Qa9SH9rEKGDvJakQJiiNp4J1P8XnzYRCAn", "amount": "8505.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QYFxjNYARmD7d3VGwXmfcdejyZ4pvUUs13", "amount": "2989.50000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QWZwi5NUzHuJfg5fh9HzjWRpQc1fmMknAh", "amount": "1282.61375000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QTKFuqgfLCn2JxQq7A2WvLjoYGGMWmr9dj", "amount": "997.00000000", "assetId": 1 },
{ "type": "GENESIS", "recipient": "QfwU5G7SHXNaGNmK2YvwieGpG2UsoPibB8", "amount": "0.66600000", "assetId": 1 }
]
}
}
}

View File

@@ -48,7 +48,7 @@ PUBLIC_KEY_NOT_FOUND = public key not found
REPOSITORY_ISSUE = repository error
TRANSACTION_INVALID = transaction invalid: %s
TRANSACTION_INVALID = transaction invalid: %s (%s)
TRANSACTION_UNKNOWN = transaction unknown

View File

@@ -22,8 +22,10 @@ NTP_NAG_TEXT_UNIX = Install NTP service to get an accurate clock.
NTP_NAG_TEXT_WINDOWS = Select "Synchronize clock" from menu to fix.
OPEN_NODE_UI = Open Node UI
OPEN_UI = Open UI
SYNCHRONIZE_CLOCK = Synchronize clock
SYNCHRONIZING_BLOCKCHAIN = Synchronizing
SYNCHRONIZING_CLOCK = Synchronizing clock

View File

@@ -22,8 +22,10 @@ NTP_NAG_TEXT_UNIX = \u5B89\u88C5NTP\u670D\u52A1\u4EE5\u83B7\u5F97\u51C6\u786E\u7
NTP_NAG_TEXT_WINDOWS = \u4ECE\u83DC\u5355\u4E2D\u9009\u62E9\u201C\u540C\u6B65\u65F6\u949F\u201D\u8FDB\u884C\u4FEE\u590D\u3002
OPEN_NODE_UI = \u5F00\u542F\u754C\u9762
OPEN_UI = \u5F00\u542F\u754C\u9762
SYNCHRONIZE_CLOCK = \u540C\u6B65\u65F6\u949F
SYNCHRONIZING_BLOCKCHAIN = \u540C\u6B65\u533A\u5757\u94FE
SYNCHRONIZING_CLOCK = \u540C\u6B65\u7740\u65F6\u949F

View File

@@ -1,4 +1,6 @@
ACCOUNT_ALREADY_EXISTS = account already exists
ACCOUNT_CANNOT_REWARD_SHARE = account cannot reward-share
ALREADY_GROUP_ADMIN = already group admin
@@ -139,7 +141,7 @@ NOT_YET_RELEASED = NOT_YET_RELEASED
NO_BALANCE = NO_BALANCE
NO_BLOCKCHAIN_LOCK = NO_BLOCKCHAIN_LOCK
NO_BLOCKCHAIN_LOCK = node's blockchain currently busy
NO_FLAG_PERMISSION = NO_FLAG_PERMISSION

View File

@@ -1 +0,0 @@
Node UI goes here!

View File

@@ -11,6 +11,7 @@ import java.util.concurrent.TimeUnit;
import org.junit.Test;
import org.qortal.utils.ExecuteProduceConsume;
import org.qortal.utils.ExecuteProduceConsume.StatsSnapshot;
public class EPCTests {
@@ -60,13 +61,12 @@ public class EPCTests {
ScheduledExecutorService statusExecutor = Executors.newSingleThreadScheduledExecutor();
statusExecutor.scheduleAtFixedRate(() -> {
synchronized (testEPC) {
final long seconds = (System.currentTimeMillis() - start) / 1000L;
System.out.println(String.format("After %d second%s, active threads: %d, greatest thread count: %d, tasks produced: %d, tasks consumed: %d",
seconds, (seconds != 1 ? "s" : ""),
testEPC.getActiveThreadCount(), testEPC.getGreatestActiveThreadCount(),
testEPC.getTasksProduced(), testEPC.getTasksConsumed()));
}
StatsSnapshot snapshot = testEPC.getStatsSnapshot();
final long seconds = (System.currentTimeMillis() - start) / 1000L;
System.out.println(String.format("After %d second%s, active threads: %d, greatest thread count: %d, tasks produced: %d, tasks consumed: %d",
seconds, (seconds != 1 ? "s" : ""),
snapshot.activeThreadCount, snapshot.greatestActiveThreadCount,
snapshot.tasksProduced, snapshot.tasksConsumed));
}, 1L, 1L, TimeUnit.SECONDS);
// Let it run for a minute
@@ -78,10 +78,10 @@ public class EPCTests {
final long after = System.currentTimeMillis();
System.out.println(String.format("Shutdown took %d milliseconds", after - before));
System.out.println(String.format("Greatest thread count: %d", testEPC.getGreatestActiveThreadCount()));
System.out.println(String.format("Tasks produced: %d", testEPC.getTasksProduced()));
System.out.println(String.format("Tasks consumed: %d", testEPC.getTasksConsumed()));
StatsSnapshot snapshot = testEPC.getStatsSnapshot();
System.out.println(String.format("Greatest thread count: %d, tasks produced: %d, tasks consumed: %d",
snapshot.greatestActiveThreadCount, snapshot.tasksProduced, snapshot.tasksConsumed));
}
@Test

View File

@@ -5,6 +5,7 @@ import org.junit.Before;
import org.junit.Test;
import org.qortal.account.Account;
import org.qortal.account.PrivateKeyAccount;
import org.qortal.account.PublicKeyAccount;
import org.qortal.block.BlockChain;
import org.qortal.block.BlockMinter;
import org.qortal.data.account.AccountData;
@@ -19,11 +20,13 @@ import org.qortal.test.common.BlockUtils;
import org.qortal.test.common.Common;
import org.qortal.test.common.TestAccount;
import org.qortal.test.common.TransactionUtils;
import org.qortal.transform.Transformer;
import static org.junit.Assert.*;
import java.math.BigDecimal;
import java.util.List;
import java.util.Random;
public class TransferPrivsTests extends Common {
@@ -42,6 +45,27 @@ public class TransferPrivsTests extends Common {
Common.orphanCheck();
}
@Test
public void testAliceIntoNewAccountTransferPrivs() throws DataException {
try (final Repository repository = RepositoryManager.getRepository()) {
TestAccount alice = Common.getTestAccount(repository, "alice");
assertTrue(alice.canMint());
PrivateKeyAccount aliceMintingAccount = Common.getTestAccount(repository, "alice-reward-share");
byte[] randomPublicKey = new byte[Transformer.PUBLIC_KEY_LENGTH];
Random random = new Random();
random.nextBytes(randomPublicKey);
Account randomAccount = new PublicKeyAccount(repository, randomPublicKey);
combineAccounts(repository, alice, randomAccount, aliceMintingAccount);
assertFalse(alice.canMint());
assertTrue(randomAccount.canMint());
}
}
@Test
public void testAliceIntoDilbertTransferPrivs() throws DataException {
try (final Repository repository = RepositoryManager.getRepository()) {

View File

@@ -14,7 +14,8 @@ public class CheckTranslations {
private static final String[] SUPPORTED_LANGS = new String[] { "en", "de", "zh", "ru" };
private static final Set<String> SYSTRAY_KEYS = Set.of("BLOCK_HEIGHT", "CHECK_TIME_ACCURACY", "CONNECTION", "CONNECTIONS",
"EXIT", "MINTING_DISABLED", "MINTING_ENABLED", "OPEN_NODE_UI", "SYNCHRONIZE_CLOCK", "SYNCHRONIZING_CLOCK");
"EXIT", "MINTING_DISABLED", "MINTING_ENABLED", "NTP_NAG_CAPTION", "NTP_NAG_TEXT_UNIX", "NTP_NAG_TEXT_WINDOWS",
"OPEN_UI", "SYNCHRONIZE_CLOCK", "SYNCHRONIZING_BLOCKCHAIN", "SYNCHRONIZING_CLOCK");
private static String failurePrefix;

View File

@@ -4,9 +4,11 @@ import static org.junit.Assert.*;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.bitcoinj.core.Base58;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@@ -204,4 +206,55 @@ public class RewardTests extends Common {
}
}
/** Test rewards to founders, one in reward-share, the other is self-share. */
@Test
public void testFounderRewards() throws DataException {
Common.useSettings("test-settings-v2-founder-rewards.json");
BigDecimal perHundred = BigDecimal.valueOf(100L);
try (final Repository repository = RepositoryManager.getRepository()) {
BigDecimal blockReward = BlockUtils.getNextBlockReward(repository);
List<PrivateKeyAccount> mintingAndOnlineAccounts = new ArrayList<>();
// Alice to mint, therefore online
PrivateKeyAccount aliceSelfShare = Common.getTestAccount(repository, "alice-reward-share");
mintingAndOnlineAccounts.add(aliceSelfShare);
// Bob self-share NOT online
// Chloe self-share and reward-share with Dilbert both online
PrivateKeyAccount chloeSelfShare = Common.getTestAccount(repository, "chloe-reward-share");
mintingAndOnlineAccounts.add(chloeSelfShare);
PrivateKeyAccount chloeDilbertRewardShare = new PrivateKeyAccount(repository, Base58.decode("HuiyqLipUN1V9p1HZfLhyEwmEA6BTaT2qEfjgkwPViV4"));
mintingAndOnlineAccounts.add(chloeDilbertRewardShare);
BlockMinter.mintTestingBlock(repository, mintingAndOnlineAccounts.toArray(new PrivateKeyAccount[0]));
// 3 founders (online or not) so blockReward divided by 3
BigDecimal founderCount = BigDecimal.valueOf(3L);
BigDecimal perFounderReward = blockReward.divide(founderCount, RoundingMode.DOWN);
// Alice simple self-share so her reward is perFounderReward
AccountUtils.assertBalance(repository, "alice", Asset.QORT, perFounderReward);
// Bob not online so his reward is simply perFounderReward
AccountUtils.assertBalance(repository, "bob", Asset.QORT, perFounderReward);
// Chloe has two reward-shares, so her reward is divided by 2
BigDecimal chloeSharesCount = BigDecimal.valueOf(2L);
BigDecimal chloePerShareReward = perFounderReward.divide(chloeSharesCount, RoundingMode.DOWN);
// Her self-share gets chloePerShareReward
BigDecimal chloeExpectedBalance = chloePerShareReward;
// Her reward-share with Dilbert: 25% goes to Dilbert
BigDecimal dilbertSharePercent = BigDecimal.valueOf(25L);
BigDecimal dilbertExpectedBalance = chloePerShareReward.multiply(dilbertSharePercent).divide(perHundred, RoundingMode.DOWN);
// The remaining 75% goes to Chloe
BigDecimal rewardShareRemaining = chloePerShareReward.subtract(dilbertExpectedBalance);
chloeExpectedBalance = chloeExpectedBalance.add(rewardShareRemaining);
AccountUtils.assertBalance(repository, "chloe", Asset.QORT, chloeExpectedBalance);
}
}
}

View File

@@ -0,0 +1,63 @@
{
"isTestChain": true,
"blockTimestampMargin": 500,
"transactionExpiryPeriod": 86400000,
"maxBlockSize": 2097152,
"maxBytesPerUnitFee": 1024,
"unitFee": "0.1",
"requireGroupForApproval": false,
"minAccountLevelToRewardShare": 5,
"maxRewardSharesPerMintingAccount": 20,
"founderEffectiveMintingLevel": 10,
"onlineAccountSignaturesMinLifetime": 3600000,
"onlineAccountSignaturesMaxLifetime": 86400000,
"rewardsByHeight": [
{ "height": 1, "reward": 100 },
{ "height": 11, "reward": 10 },
{ "height": 21, "reward": 1 }
],
"sharesByLevel": [
{ "levels": [ 1, 2 ], "share": 0.05 },
{ "levels": [ 3, 4 ], "share": 0.10 },
{ "levels": [ 5, 6 ], "share": 0.15 },
{ "levels": [ 7, 8 ], "share": 0.20 },
{ "levels": [ 9, 10 ], "share": 0.25 }
],
"qoraHoldersShare": 0.20,
"qoraPerQortReward": 250,
"blocksNeededByLevel": [ 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 ],
"blockTimingsByHeight": [
{ "height": 1, "target": 60000, "deviation": 30000, "power": 0.2 }
],
"featureTriggers": {
"messageHeight": 0,
"atHeight": 0,
"assetsTimestamp": 0,
"votingTimestamp": 0,
"arbitraryTimestamp": 0,
"powfixTimestamp": 0,
"qortalTimestamp": 0,
"newAssetPricingTimestamp": 0,
"groupApprovalTimestamp": 0
},
"genesisInfo": {
"version": 4,
"timestamp": 0,
"transactions": [
{ "type": "ISSUE_ASSET", "owner": "QcFmNxSArv5tWEzCtTKb2Lqc5QkKuQ7RNs", "assetName": "QORT", "description": "QORT native coin", "data": "", "quantity": 0, "isDivisible": true, "fee": 0, "reference": "3Verk6ZKBJc3WTTVfxFC9icSjKdM8b92eeJEpJP8qNizG4ZszNFq8wdDYdSjJXq2iogDFR1njyhsBdVpbvDfjzU7" },
{ "type": "ISSUE_ASSET", "owner": "QUwGVHPPxJNJ2dq95abQNe79EyBN2K26zM", "assetName": "Legacy-QORA", "description": "Representative legacy QORA", "quantity": 0, "isDivisible": true, "data": "{}", "isUnspendable": true },
{ "type": "ISSUE_ASSET", "owner": "QUwGVHPPxJNJ2dq95abQNe79EyBN2K26zM", "assetName": "QORT-from-QORA", "description": "QORT gained from holding legacy QORA", "quantity": 0, "isDivisible": true, "data": "{}", "isUnspendable": true },
{ "type": "ACCOUNT_FLAGS", "target": "QgV4s3xnzLhVBEJxcYui4u4q11yhUHsd9v", "andMask": -1, "orMask": 1, "xorMask": 0 },
{ "type": "REWARD_SHARE", "minterPublicKey": "2tiMr5LTpaWCgbRvkPK8TFd7k63DyHJMMFFsz9uBf1ZP", "recipient": "QgV4s3xnzLhVBEJxcYui4u4q11yhUHsd9v", "rewardSharePublicKey": "7PpfnvLSG7y4HPh8hE7KoqAjLCkv7Ui6xw4mKAkbZtox", "sharePercent": 100 },
{ "type": "ACCOUNT_FLAGS", "target": "QixPbJUwsaHsVEofJdozU9zgVqkK6aYhrK", "andMask": -1, "orMask": 1, "xorMask": 0 },
{ "type": "REWARD_SHARE", "minterPublicKey": "C6wuddsBV3HzRrXUtezE7P5MoRXp5m3mEDokRDGZB6ry", "recipient": "QixPbJUwsaHsVEofJdozU9zgVqkK6aYhrK", "rewardSharePublicKey": "CcABzvk26TFEHG7Yok84jxyd4oBtLkx8RJdGFVz2csvp", "sharePercent": 100 },
{ "type": "ACCOUNT_FLAGS", "target": "QaUpHNhT3Ygx6avRiKobuLdusppR5biXjL", "andMask": -1, "orMask": 1, "xorMask": 0 },
{ "type": "REWARD_SHARE", "minterPublicKey": "7KNBj2MnEb6zq1vvKY1q8G2Voctcc2Z1X4avFyEH2eJC", "recipient": "QaUpHNhT3Ygx6avRiKobuLdusppR5biXjL", "rewardSharePublicKey": "6bnEKqZbsCSWryUQnbBT9Umufdu3CapFvxfAni6afhFb", "sharePercent": 100 },
{ "type": "REWARD_SHARE", "minterPublicKey": "7KNBj2MnEb6zq1vvKY1q8G2Voctcc2Z1X4avFyEH2eJC", "recipient": "Qci5m9k4rcwe4ruKrZZQKka4FzUUMut3er", "rewardSharePublicKey": "Hebh14YXUdJA66Vq8KyffNXHx3NSDUAZaNH9qbfEvf5M", "sharePercent": 25 }
]
}
}

View File

@@ -0,0 +1,6 @@
{
"restrictedApi": false,
"blockchainConfig": "src/test/resources/test-chain-v2-founder-rewards.json",
"wipeUnconfirmedOnStart": false,
"minPeers": 0
}