forked from Qortal/qortal
Merge branch 'master' into increase-online-timestamp-modulus
# Conflicts: # src/main/java/org/qortal/block/BlockChain.java
This commit is contained in:
commit
410fa59430
@ -17,10 +17,10 @@
|
|||||||
<ROW Property="Manufacturer" Value="Qortal"/>
|
<ROW Property="Manufacturer" Value="Qortal"/>
|
||||||
<ROW Property="MsiLogging" MultiBuildValue="DefaultBuild:vp"/>
|
<ROW Property="MsiLogging" MultiBuildValue="DefaultBuild:vp"/>
|
||||||
<ROW Property="NTP_GOOD" Value="false"/>
|
<ROW Property="NTP_GOOD" Value="false"/>
|
||||||
<ROW Property="ProductCode" Value="1033:{B786B6C1-86FA-4917-BAF9-7C9D10959D66} 1049:{60881A63-53FC-4DBE-AF3B-0568F55D2150} 2052:{108D1268-8111-49B9-B768-CC0A0A0CEDE1} 2057:{46DB692E-D942-40D5-B32E-FB94458478BF} " Type="16"/>
|
<ROW Property="ProductCode" Value="1033:{BB0A4CEC-D437-4D1B-9569-44CD644D073A} 1049:{A3131099-E63A-4926-A449-6A394E2B895A} 2052:{39D2F92F-C2EE-43EA-BEC9-870333921BC0} 2057:{FE9EFD1A-E4DF-4F02-A049-6A0FBFA2AAEF} " Type="16"/>
|
||||||
<ROW Property="ProductLanguage" Value="2057"/>
|
<ROW Property="ProductLanguage" Value="2057"/>
|
||||||
<ROW Property="ProductName" Value="Qortal"/>
|
<ROW Property="ProductName" Value="Qortal"/>
|
||||||
<ROW Property="ProductVersion" Value="3.4.0" Type="32"/>
|
<ROW Property="ProductVersion" Value="3.4.2" Type="32"/>
|
||||||
<ROW Property="RECONFIG_NTP" Value="true"/>
|
<ROW Property="RECONFIG_NTP" Value="true"/>
|
||||||
<ROW Property="REMOVE_BLOCKCHAIN" Value="YES" Type="4"/>
|
<ROW Property="REMOVE_BLOCKCHAIN" Value="YES" Type="4"/>
|
||||||
<ROW Property="REPAIR_BLOCKCHAIN" Value="YES" Type="4"/>
|
<ROW Property="REPAIR_BLOCKCHAIN" Value="YES" Type="4"/>
|
||||||
@ -212,7 +212,7 @@
|
|||||||
<ROW Component="ADDITIONAL_LICENSE_INFO_71" ComponentId="{12A3ADBE-BB7A-496C-8869-410681E6232F}" Directory_="jdk.zipfs_Dir" Attributes="0" KeyPath="ADDITIONAL_LICENSE_INFO_71" Type="0"/>
|
<ROW Component="ADDITIONAL_LICENSE_INFO_71" ComponentId="{12A3ADBE-BB7A-496C-8869-410681E6232F}" Directory_="jdk.zipfs_Dir" Attributes="0" KeyPath="ADDITIONAL_LICENSE_INFO_71" Type="0"/>
|
||||||
<ROW Component="ADDITIONAL_LICENSE_INFO_8" ComponentId="{D53AD95E-CF96-4999-80FC-5812277A7456}" Directory_="java.naming_Dir" Attributes="0" KeyPath="ADDITIONAL_LICENSE_INFO_8" Type="0"/>
|
<ROW Component="ADDITIONAL_LICENSE_INFO_8" ComponentId="{D53AD95E-CF96-4999-80FC-5812277A7456}" Directory_="java.naming_Dir" Attributes="0" KeyPath="ADDITIONAL_LICENSE_INFO_8" Type="0"/>
|
||||||
<ROW Component="ADDITIONAL_LICENSE_INFO_9" ComponentId="{6B7EA9B0-5D17-47A8-B78C-FACE86D15E01}" Directory_="java.net.http_Dir" Attributes="0" KeyPath="ADDITIONAL_LICENSE_INFO_9" Type="0"/>
|
<ROW Component="ADDITIONAL_LICENSE_INFO_9" ComponentId="{6B7EA9B0-5D17-47A8-B78C-FACE86D15E01}" Directory_="java.net.http_Dir" Attributes="0" KeyPath="ADDITIONAL_LICENSE_INFO_9" Type="0"/>
|
||||||
<ROW Component="AI_CustomARPName" ComponentId="{D57E945C-0FFB-447C-ADF7-2253CEBF4C0C}" Directory_="APPDIR" Attributes="260" KeyPath="DisplayName" Options="1"/>
|
<ROW Component="AI_CustomARPName" ComponentId="{9CE2B8C0-C0A1-481A-8A34-48830F2785EE}" Directory_="APPDIR" Attributes="260" KeyPath="DisplayName" Options="1"/>
|
||||||
<ROW Component="AI_ExePath" ComponentId="{3644948D-AE0B-41BB-9FAF-A79E70490A08}" Directory_="APPDIR" Attributes="260" KeyPath="AI_ExePath"/>
|
<ROW Component="AI_ExePath" ComponentId="{3644948D-AE0B-41BB-9FAF-A79E70490A08}" Directory_="APPDIR" Attributes="260" KeyPath="AI_ExePath"/>
|
||||||
<ROW Component="APPDIR" ComponentId="{680DFDDE-3FB4-47A5-8FF5-934F576C6F91}" Directory_="APPDIR" Attributes="0"/>
|
<ROW Component="APPDIR" ComponentId="{680DFDDE-3FB4-47A5-8FF5-934F576C6F91}" Directory_="APPDIR" Attributes="0"/>
|
||||||
<ROW Component="AccessBridgeCallbacks.h" ComponentId="{288055D1-1062-47A3-AA44-5601B4E38AED}" Directory_="bridge_Dir" Attributes="0" KeyPath="AccessBridgeCallbacks.h" Type="0"/>
|
<ROW Component="AccessBridgeCallbacks.h" ComponentId="{288055D1-1062-47A3-AA44-5601B4E38AED}" Directory_="bridge_Dir" Attributes="0" KeyPath="AccessBridgeCallbacks.h" Type="0"/>
|
||||||
|
2
pom.xml
2
pom.xml
@ -3,7 +3,7 @@
|
|||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
<groupId>org.qortal</groupId>
|
<groupId>org.qortal</groupId>
|
||||||
<artifactId>qortal</artifactId>
|
<artifactId>qortal</artifactId>
|
||||||
<version>3.4.0</version>
|
<version>3.4.2</version>
|
||||||
<packaging>jar</packaging>
|
<packaging>jar</packaging>
|
||||||
<properties>
|
<properties>
|
||||||
<skipTests>true</skipTests>
|
<skipTests>true</skipTests>
|
||||||
|
@ -8,6 +8,7 @@ import java.nio.file.Paths;
|
|||||||
import java.nio.file.StandardCopyOption;
|
import java.nio.file.StandardCopyOption;
|
||||||
import java.security.Security;
|
import java.security.Security;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import org.apache.logging.log4j.LogManager;
|
import org.apache.logging.log4j.LogManager;
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
@ -18,6 +19,8 @@ import org.qortal.api.ApiRequest;
|
|||||||
import org.qortal.controller.AutoUpdate;
|
import org.qortal.controller.AutoUpdate;
|
||||||
import org.qortal.settings.Settings;
|
import org.qortal.settings.Settings;
|
||||||
|
|
||||||
|
import static org.qortal.controller.AutoUpdate.AGENTLIB_JVM_HOLDER_ARG;
|
||||||
|
|
||||||
public class ApplyUpdate {
|
public class ApplyUpdate {
|
||||||
|
|
||||||
static {
|
static {
|
||||||
@ -197,6 +200,11 @@ public class ApplyUpdate {
|
|||||||
// JVM arguments
|
// JVM arguments
|
||||||
javaCmd.addAll(ManagementFactory.getRuntimeMXBean().getInputArguments());
|
javaCmd.addAll(ManagementFactory.getRuntimeMXBean().getInputArguments());
|
||||||
|
|
||||||
|
// Reapply any retained, but disabled, -agentlib JVM arg
|
||||||
|
javaCmd = javaCmd.stream()
|
||||||
|
.map(arg -> arg.replace(AGENTLIB_JVM_HOLDER_ARG, "-agentlib"))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
// Call mainClass in JAR
|
// Call mainClass in JAR
|
||||||
javaCmd.addAll(Arrays.asList("-jar", JAR_FILENAME));
|
javaCmd.addAll(Arrays.asList("-jar", JAR_FILENAME));
|
||||||
|
|
||||||
@ -205,7 +213,7 @@ public class ApplyUpdate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
LOGGER.info(() -> String.format("Restarting node with: %s", String.join(" ", javaCmd)));
|
LOGGER.info(String.format("Restarting node with: %s", String.join(" ", javaCmd)));
|
||||||
|
|
||||||
ProcessBuilder processBuilder = new ProcessBuilder(javaCmd);
|
ProcessBuilder processBuilder = new ProcessBuilder(javaCmd);
|
||||||
|
|
||||||
@ -214,8 +222,15 @@ public class ApplyUpdate {
|
|||||||
processBuilder.environment().put(JAVA_TOOL_OPTIONS_NAME, JAVA_TOOL_OPTIONS_VALUE);
|
processBuilder.environment().put(JAVA_TOOL_OPTIONS_NAME, JAVA_TOOL_OPTIONS_VALUE);
|
||||||
}
|
}
|
||||||
|
|
||||||
processBuilder.start();
|
// New process will inherit our stdout and stderr
|
||||||
} catch (IOException e) {
|
processBuilder.redirectOutput(ProcessBuilder.Redirect.INHERIT);
|
||||||
|
processBuilder.redirectError(ProcessBuilder.Redirect.INHERIT);
|
||||||
|
|
||||||
|
Process process = processBuilder.start();
|
||||||
|
|
||||||
|
// Nothing to pipe to new process, so close output stream (process's stdin)
|
||||||
|
process.getOutputStream().close();
|
||||||
|
} catch (Exception e) {
|
||||||
LOGGER.error(String.format("Failed to restart node (BAD): %s", e.getMessage()));
|
LOGGER.error(String.format("Failed to restart node (BAD): %s", e.getMessage()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -114,7 +114,7 @@ public class BlocksResource {
|
|||||||
@Path("/signature/{signature}/data")
|
@Path("/signature/{signature}/data")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Fetch serialized, base58 encoded block data using base58 signature",
|
summary = "Fetch serialized, base58 encoded block data using base58 signature",
|
||||||
description = "Returns serialized data for the block that matches the given signature",
|
description = "Returns serialized data for the block that matches the given signature, and an optional block serialization version",
|
||||||
responses = {
|
responses = {
|
||||||
@ApiResponse(
|
@ApiResponse(
|
||||||
description = "the block data",
|
description = "the block data",
|
||||||
@ -125,7 +125,7 @@ public class BlocksResource {
|
|||||||
@ApiErrors({
|
@ApiErrors({
|
||||||
ApiError.INVALID_SIGNATURE, ApiError.BLOCK_UNKNOWN, ApiError.INVALID_DATA, ApiError.REPOSITORY_ISSUE
|
ApiError.INVALID_SIGNATURE, ApiError.BLOCK_UNKNOWN, ApiError.INVALID_DATA, ApiError.REPOSITORY_ISSUE
|
||||||
})
|
})
|
||||||
public String getSerializedBlockData(@PathParam("signature") String signature58) {
|
public String getSerializedBlockData(@PathParam("signature") String signature58, @QueryParam("version") Integer version) {
|
||||||
// Decode signature
|
// Decode signature
|
||||||
byte[] signature;
|
byte[] signature;
|
||||||
try {
|
try {
|
||||||
@ -136,20 +136,41 @@ public class BlocksResource {
|
|||||||
|
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
|
|
||||||
|
// Default to version 1
|
||||||
|
if (version == null) {
|
||||||
|
version = 1;
|
||||||
|
}
|
||||||
|
|
||||||
// Check the database first
|
// Check the database first
|
||||||
BlockData blockData = repository.getBlockRepository().fromSignature(signature);
|
BlockData blockData = repository.getBlockRepository().fromSignature(signature);
|
||||||
if (blockData != null) {
|
if (blockData != null) {
|
||||||
Block block = new Block(repository, blockData);
|
Block block = new Block(repository, blockData);
|
||||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
|
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
|
||||||
bytes.write(Ints.toByteArray(block.getBlockData().getHeight()));
|
bytes.write(Ints.toByteArray(block.getBlockData().getHeight()));
|
||||||
bytes.write(BlockTransformer.toBytes(block));
|
|
||||||
|
switch (version) {
|
||||||
|
case 1:
|
||||||
|
bytes.write(BlockTransformer.toBytes(block));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 2:
|
||||||
|
bytes.write(BlockTransformer.toBytesV2(block));
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_CRITERIA);
|
||||||
|
}
|
||||||
|
|
||||||
return Base58.encode(bytes.toByteArray());
|
return Base58.encode(bytes.toByteArray());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Not found, so try the block archive
|
// Not found, so try the block archive
|
||||||
byte[] bytes = BlockArchiveReader.getInstance().fetchSerializedBlockBytesForSignature(signature, false, repository);
|
byte[] bytes = BlockArchiveReader.getInstance().fetchSerializedBlockBytesForSignature(signature, false, repository);
|
||||||
if (bytes != null) {
|
if (bytes != null) {
|
||||||
return Base58.encode(bytes);
|
if (version != 1) {
|
||||||
|
throw ApiExceptionFactory.INSTANCE.createCustomException(request, ApiError.INVALID_CRITERIA, "Archived blocks require version 1");
|
||||||
|
}
|
||||||
|
return Base58.encode(bytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.BLOCK_UNKNOWN);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.BLOCK_UNKNOWN);
|
||||||
|
@ -199,6 +199,11 @@ public class Block {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean hasShareBin(AccountLevelShareBin shareBin, int blockHeight) {
|
||||||
|
AccountLevelShareBin ourShareBin = this.getShareBin(blockHeight);
|
||||||
|
return ourShareBin != null && shareBin.id == ourShareBin.id;
|
||||||
|
}
|
||||||
|
|
||||||
public long distribute(long accountAmount, Map<String, Long> balanceChanges) {
|
public long distribute(long accountAmount, Map<String, Long> balanceChanges) {
|
||||||
if (this.isRecipientAlsoMinter) {
|
if (this.isRecipientAlsoMinter) {
|
||||||
// minter & recipient the same - simpler case
|
// minter & recipient the same - simpler case
|
||||||
@ -1891,12 +1896,67 @@ public class Block {
|
|||||||
final boolean haveFounders = !onlineFounderAccounts.isEmpty();
|
final boolean haveFounders = !onlineFounderAccounts.isEmpty();
|
||||||
|
|
||||||
// Determine reward candidates based on account level
|
// Determine reward candidates based on account level
|
||||||
List<AccountLevelShareBin> accountLevelShareBins = BlockChain.getInstance().getAccountLevelShareBins();
|
// This needs a deep copy, so the shares can be modified when tiers aren't activated yet
|
||||||
for (int binIndex = 0; binIndex < accountLevelShareBins.size(); ++binIndex) {
|
List<AccountLevelShareBin> accountLevelShareBins = new ArrayList<>();
|
||||||
// Find all accounts in share bin. getShareBin() returns null for minter accounts that are also founders, so they are effectively filtered out.
|
for (AccountLevelShareBin accountLevelShareBin : BlockChain.getInstance().getAccountLevelShareBins()) {
|
||||||
|
accountLevelShareBins.add((AccountLevelShareBin) accountLevelShareBin.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<Integer, List<ExpandedAccount>> accountsForShareBin = new HashMap<>();
|
||||||
|
|
||||||
|
// We might need to combine some share bins if they haven't reached the minimum number of minters yet
|
||||||
|
for (int binIndex = accountLevelShareBins.size()-1; binIndex >= 0; --binIndex) {
|
||||||
AccountLevelShareBin accountLevelShareBin = accountLevelShareBins.get(binIndex);
|
AccountLevelShareBin accountLevelShareBin = accountLevelShareBins.get(binIndex);
|
||||||
// Object reference compare is OK as all references are read-only from blockchain config.
|
|
||||||
List<ExpandedAccount> binnedAccounts = expandedAccounts.stream().filter(accountInfo -> accountInfo.getShareBin(this.blockData.getHeight()) == accountLevelShareBin).collect(Collectors.toList());
|
// Find all accounts in share bin. getShareBin() returns null for minter accounts that are also founders, so they are effectively filtered out.
|
||||||
|
List<ExpandedAccount> binnedAccounts = expandedAccounts.stream().filter(accountInfo -> accountInfo.hasShareBin(accountLevelShareBin, this.blockData.getHeight())).collect(Collectors.toList());
|
||||||
|
// Add any accounts that have been moved down from a higher tier
|
||||||
|
List<ExpandedAccount> existingBinnedAccounts = accountsForShareBin.get(binIndex);
|
||||||
|
if (existingBinnedAccounts != null)
|
||||||
|
binnedAccounts.addAll(existingBinnedAccounts);
|
||||||
|
|
||||||
|
// Logic below may only apply to higher levels, and only for share bins with a specific range of online accounts
|
||||||
|
if (accountLevelShareBin.levels.get(0) < BlockChain.getInstance().getShareBinActivationMinLevel() ||
|
||||||
|
binnedAccounts.isEmpty() || binnedAccounts.size() >= BlockChain.getInstance().getMinAccountsToActivateShareBin()) {
|
||||||
|
// Add all accounts for this share bin to the accountsForShareBin list
|
||||||
|
accountsForShareBin.put(binIndex, binnedAccounts);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Share bin contains more than one, but less than the minimum number of minters. We treat this share bin
|
||||||
|
// as not activated yet. In these cases, the rewards and minters are combined and paid out to the previous
|
||||||
|
// share bin, to prevent a single or handful of accounts receiving the entire rewards for a share bin.
|
||||||
|
//
|
||||||
|
// Example:
|
||||||
|
//
|
||||||
|
// - Share bin for levels 5 and 6 has 100 minters
|
||||||
|
// - Share bin for levels 7 and 8 has 10 minters
|
||||||
|
//
|
||||||
|
// This is below the minimum of 30, so share bins are reconstructed as follows:
|
||||||
|
//
|
||||||
|
// - Share bin for levels 5 and 6 now contains 110 minters
|
||||||
|
// - Share bin for levels 7 and 8 now contains 0 minters
|
||||||
|
// - Share bin for levels 5 and 6 now pays out rewards for levels 5, 6, 7, and 8
|
||||||
|
// - Share bin for levels 7 and 8 pays zero rewards
|
||||||
|
//
|
||||||
|
// This process is iterative, so will combine several tiers if needed.
|
||||||
|
|
||||||
|
// Designate this share bin as empty
|
||||||
|
accountsForShareBin.put(binIndex, new ArrayList<>());
|
||||||
|
|
||||||
|
// Move the accounts originally intended for this share bin to the previous one
|
||||||
|
accountsForShareBin.put(binIndex - 1, binnedAccounts);
|
||||||
|
|
||||||
|
// Move the block reward from this share bin to the previous one
|
||||||
|
AccountLevelShareBin previousShareBin = accountLevelShareBins.get(binIndex - 1);
|
||||||
|
previousShareBin.share += accountLevelShareBin.share;
|
||||||
|
accountLevelShareBin.share = 0L;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now loop through (potentially modified) share bins and determine the reward candidates
|
||||||
|
for (int binIndex = 0; binIndex < accountLevelShareBins.size(); ++binIndex) {
|
||||||
|
AccountLevelShareBin accountLevelShareBin = accountLevelShareBins.get(binIndex);
|
||||||
|
List<ExpandedAccount> binnedAccounts = accountsForShareBin.get(binIndex);
|
||||||
|
|
||||||
// No online accounts in this bin? Skip to next one
|
// No online accounts in this bin? Skip to next one
|
||||||
if (binnedAccounts.isEmpty())
|
if (binnedAccounts.isEmpty())
|
||||||
|
@ -68,6 +68,7 @@ public class BlockChain {
|
|||||||
atFindNextTransactionFix,
|
atFindNextTransactionFix,
|
||||||
newBlockSigHeight,
|
newBlockSigHeight,
|
||||||
shareBinFix,
|
shareBinFix,
|
||||||
|
rewardShareLimitTimestamp,
|
||||||
calcChainWeightTimestamp,
|
calcChainWeightTimestamp,
|
||||||
transactionV5Timestamp,
|
transactionV5Timestamp,
|
||||||
transactionV6Timestamp,
|
transactionV6Timestamp,
|
||||||
@ -103,10 +104,23 @@ public class BlockChain {
|
|||||||
private List<RewardByHeight> rewardsByHeight;
|
private List<RewardByHeight> rewardsByHeight;
|
||||||
|
|
||||||
/** Share of block reward/fees by account level */
|
/** Share of block reward/fees by account level */
|
||||||
public static class AccountLevelShareBin {
|
public static class AccountLevelShareBin implements Cloneable {
|
||||||
|
public int id;
|
||||||
public List<Integer> levels;
|
public List<Integer> levels;
|
||||||
@XmlJavaTypeAdapter(value = org.qortal.api.AmountTypeAdapter.class)
|
@XmlJavaTypeAdapter(value = org.qortal.api.AmountTypeAdapter.class)
|
||||||
public long share;
|
public long share;
|
||||||
|
|
||||||
|
public Object clone() {
|
||||||
|
AccountLevelShareBin shareBinCopy = new AccountLevelShareBin();
|
||||||
|
List<Integer> levelsCopy = new ArrayList<>();
|
||||||
|
for (Integer level : this.levels) {
|
||||||
|
levelsCopy.add(level);
|
||||||
|
}
|
||||||
|
shareBinCopy.id = this.id;
|
||||||
|
shareBinCopy.levels = levelsCopy;
|
||||||
|
shareBinCopy.share = this.share;
|
||||||
|
return shareBinCopy;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
private List<AccountLevelShareBin> sharesByLevel;
|
private List<AccountLevelShareBin> sharesByLevel;
|
||||||
/** Generated lookup of share-bin by account level */
|
/** Generated lookup of share-bin by account level */
|
||||||
@ -120,6 +134,12 @@ public class BlockChain {
|
|||||||
@XmlJavaTypeAdapter(value = org.qortal.api.AmountTypeAdapter.class)
|
@XmlJavaTypeAdapter(value = org.qortal.api.AmountTypeAdapter.class)
|
||||||
private Long qoraPerQortReward;
|
private Long qoraPerQortReward;
|
||||||
|
|
||||||
|
/** Minimum number of accounts before a share bin is considered activated */
|
||||||
|
private int minAccountsToActivateShareBin;
|
||||||
|
|
||||||
|
/** Min level at which share bin activation takes place; lower levels allow less than minAccountsPerShareBin */
|
||||||
|
private int shareBinActivationMinLevel;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Number of minted blocks required to reach next level from previous.
|
* Number of minted blocks required to reach next level from previous.
|
||||||
* <p>
|
* <p>
|
||||||
@ -157,7 +177,7 @@ public class BlockChain {
|
|||||||
private int minAccountLevelToMint;
|
private int minAccountLevelToMint;
|
||||||
private int minAccountLevelForBlockSubmissions;
|
private int minAccountLevelForBlockSubmissions;
|
||||||
private int minAccountLevelToRewardShare;
|
private int minAccountLevelToRewardShare;
|
||||||
private int maxRewardSharesPerMintingAccount;
|
private int maxRewardSharesPerFounderMintingAccount;
|
||||||
private int founderEffectiveMintingLevel;
|
private int founderEffectiveMintingLevel;
|
||||||
|
|
||||||
/** Minimum time to retain online account signatures (ms) for block validity checks. */
|
/** Minimum time to retain online account signatures (ms) for block validity checks. */
|
||||||
@ -169,6 +189,13 @@ public class BlockChain {
|
|||||||
* featureTriggers because unit tests need to set this value via Reflection. */
|
* featureTriggers because unit tests need to set this value via Reflection. */
|
||||||
private long onlineAccountsModulusV2Timestamp;
|
private long onlineAccountsModulusV2Timestamp;
|
||||||
|
|
||||||
|
/** Max reward shares by block height */
|
||||||
|
public static class MaxRewardSharesByTimestamp {
|
||||||
|
public long timestamp;
|
||||||
|
public int maxShares;
|
||||||
|
}
|
||||||
|
private List<MaxRewardSharesByTimestamp> maxRewardSharesByTimestamp;
|
||||||
|
|
||||||
/** Settings relating to CIYAM AT feature. */
|
/** Settings relating to CIYAM AT feature. */
|
||||||
public static class CiyamAtSettings {
|
public static class CiyamAtSettings {
|
||||||
/** Fee per step/op-code executed. */
|
/** Fee per step/op-code executed. */
|
||||||
@ -363,6 +390,14 @@ public class BlockChain {
|
|||||||
return this.qoraPerQortReward;
|
return this.qoraPerQortReward;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getMinAccountsToActivateShareBin() {
|
||||||
|
return this.minAccountsToActivateShareBin;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getShareBinActivationMinLevel() {
|
||||||
|
return this.shareBinActivationMinLevel;
|
||||||
|
}
|
||||||
|
|
||||||
public int getMinAccountLevelToMint() {
|
public int getMinAccountLevelToMint() {
|
||||||
return this.minAccountLevelToMint;
|
return this.minAccountLevelToMint;
|
||||||
}
|
}
|
||||||
@ -375,8 +410,8 @@ public class BlockChain {
|
|||||||
return this.minAccountLevelToRewardShare;
|
return this.minAccountLevelToRewardShare;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getMaxRewardSharesPerMintingAccount() {
|
public int getMaxRewardSharesPerFounderMintingAccount() {
|
||||||
return this.maxRewardSharesPerMintingAccount;
|
return this.maxRewardSharesPerFounderMintingAccount;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getFounderEffectiveMintingLevel() {
|
public int getFounderEffectiveMintingLevel() {
|
||||||
@ -409,6 +444,10 @@ public class BlockChain {
|
|||||||
return this.featureTriggers.get(FeatureTrigger.shareBinFix.name()).intValue();
|
return this.featureTriggers.get(FeatureTrigger.shareBinFix.name()).intValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public long getRewardShareLimitTimestamp() {
|
||||||
|
return this.featureTriggers.get(FeatureTrigger.rewardShareLimitTimestamp.name()).longValue();
|
||||||
|
}
|
||||||
|
|
||||||
public long getCalcChainWeightTimestamp() {
|
public long getCalcChainWeightTimestamp() {
|
||||||
return this.featureTriggers.get(FeatureTrigger.calcChainWeightTimestamp.name()).longValue();
|
return this.featureTriggers.get(FeatureTrigger.calcChainWeightTimestamp.name()).longValue();
|
||||||
}
|
}
|
||||||
@ -457,6 +496,14 @@ public class BlockChain {
|
|||||||
return this.getUnitFee();
|
return this.getUnitFee();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getMaxRewardSharesAtTimestamp(long ourTimestamp) {
|
||||||
|
for (int i = maxRewardSharesByTimestamp.size() - 1; i >= 0; --i)
|
||||||
|
if (maxRewardSharesByTimestamp.get(i).timestamp <= ourTimestamp)
|
||||||
|
return maxRewardSharesByTimestamp.get(i).maxShares;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
/** Validate blockchain config read from JSON */
|
/** Validate blockchain config read from JSON */
|
||||||
private void validateConfig() {
|
private void validateConfig() {
|
||||||
if (this.genesisInfo == null)
|
if (this.genesisInfo == null)
|
||||||
|
@ -15,6 +15,7 @@ import java.util.ArrayList;
|
|||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.TimeoutException;
|
import java.util.concurrent.TimeoutException;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import org.apache.logging.log4j.LogManager;
|
import org.apache.logging.log4j.LogManager;
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
@ -40,6 +41,7 @@ public class AutoUpdate extends Thread {
|
|||||||
|
|
||||||
public static final String JAR_FILENAME = "qortal.jar";
|
public static final String JAR_FILENAME = "qortal.jar";
|
||||||
public static final String NEW_JAR_FILENAME = "new-" + JAR_FILENAME;
|
public static final String NEW_JAR_FILENAME = "new-" + JAR_FILENAME;
|
||||||
|
public static final String AGENTLIB_JVM_HOLDER_ARG = "-DQORTAL_agentlib=";
|
||||||
|
|
||||||
private static final Logger LOGGER = LogManager.getLogger(AutoUpdate.class);
|
private static final Logger LOGGER = LogManager.getLogger(AutoUpdate.class);
|
||||||
private static final long CHECK_INTERVAL = 20 * 60 * 1000L; // ms
|
private static final long CHECK_INTERVAL = 20 * 60 * 1000L; // ms
|
||||||
@ -243,6 +245,11 @@ public class AutoUpdate extends Thread {
|
|||||||
// JVM arguments
|
// JVM arguments
|
||||||
javaCmd.addAll(ManagementFactory.getRuntimeMXBean().getInputArguments());
|
javaCmd.addAll(ManagementFactory.getRuntimeMXBean().getInputArguments());
|
||||||
|
|
||||||
|
// Disable, but retain, any -agentlib JVM arg as sub-process might fail if it tries to reuse same port
|
||||||
|
javaCmd = javaCmd.stream()
|
||||||
|
.map(arg -> arg.replace("-agentlib", AGENTLIB_JVM_HOLDER_ARG))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
// Remove JNI options as they won't be supported by command-line 'java'
|
// Remove JNI options as they won't be supported by command-line 'java'
|
||||||
// These are typically added by the AdvancedInstaller Java launcher EXE
|
// These are typically added by the AdvancedInstaller Java launcher EXE
|
||||||
javaCmd.removeAll(Arrays.asList("abort", "exit", "vfprintf"));
|
javaCmd.removeAll(Arrays.asList("abort", "exit", "vfprintf"));
|
||||||
@ -261,10 +268,19 @@ public class AutoUpdate extends Thread {
|
|||||||
Translator.INSTANCE.translate("SysTray", "APPLYING_UPDATE_AND_RESTARTING"),
|
Translator.INSTANCE.translate("SysTray", "APPLYING_UPDATE_AND_RESTARTING"),
|
||||||
MessageType.INFO);
|
MessageType.INFO);
|
||||||
|
|
||||||
new ProcessBuilder(javaCmd).start();
|
ProcessBuilder processBuilder = new ProcessBuilder(javaCmd);
|
||||||
|
|
||||||
|
// New process will inherit our stdout and stderr
|
||||||
|
processBuilder.redirectOutput(ProcessBuilder.Redirect.INHERIT);
|
||||||
|
processBuilder.redirectError(ProcessBuilder.Redirect.INHERIT);
|
||||||
|
|
||||||
|
Process process = processBuilder.start();
|
||||||
|
|
||||||
|
// Nothing to pipe to new process, so close output stream (process's stdin)
|
||||||
|
process.getOutputStream().close();
|
||||||
|
|
||||||
return true; // applying update OK
|
return true; // applying update OK
|
||||||
} catch (IOException e) {
|
} catch (Exception e) {
|
||||||
LOGGER.error(String.format("Failed to apply update: %s", e.getMessage()));
|
LOGGER.error(String.format("Failed to apply update: %s", e.getMessage()));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -90,37 +90,40 @@ public class BlockMinter extends Thread {
|
|||||||
|
|
||||||
List<Block> newBlocks = new ArrayList<>();
|
List<Block> newBlocks = new ArrayList<>();
|
||||||
|
|
||||||
// Flags for tracking change in whether minting is possible,
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
// so we can notify Controller, and further update SysTray, etc.
|
// Going to need this a lot...
|
||||||
boolean isMintingPossible = false;
|
BlockRepository blockRepository = repository.getBlockRepository();
|
||||||
boolean wasMintingPossible = isMintingPossible;
|
|
||||||
while (running) {
|
|
||||||
if (isMintingPossible != wasMintingPossible)
|
|
||||||
Controller.getInstance().onMintingPossibleChange(isMintingPossible);
|
|
||||||
|
|
||||||
wasMintingPossible = isMintingPossible;
|
// Flags for tracking change in whether minting is possible,
|
||||||
|
// so we can notify Controller, and further update SysTray, etc.
|
||||||
|
boolean isMintingPossible = false;
|
||||||
|
boolean wasMintingPossible = isMintingPossible;
|
||||||
|
while (running) {
|
||||||
|
if (isMintingPossible != wasMintingPossible)
|
||||||
|
Controller.getInstance().onMintingPossibleChange(isMintingPossible);
|
||||||
|
|
||||||
try {
|
wasMintingPossible = isMintingPossible;
|
||||||
// Sleep for a while
|
|
||||||
Thread.sleep(1000);
|
|
||||||
|
|
||||||
isMintingPossible = false;
|
try {
|
||||||
|
// Free up any repository locks
|
||||||
|
repository.discardChanges();
|
||||||
|
|
||||||
final Long now = NTP.getTime();
|
// Sleep for a while
|
||||||
if (now == null)
|
Thread.sleep(1000);
|
||||||
continue;
|
|
||||||
|
|
||||||
final Long minLatestBlockTimestamp = Controller.getMinimumLatestBlockTimestamp();
|
isMintingPossible = false;
|
||||||
if (minLatestBlockTimestamp == null)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
// No online accounts for current timestamp? (e.g. during startup)
|
final Long now = NTP.getTime();
|
||||||
if (!OnlineAccountsManager.getInstance().hasOnlineAccounts())
|
if (now == null)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
final Long minLatestBlockTimestamp = Controller.getMinimumLatestBlockTimestamp();
|
||||||
// Going to need this a lot...
|
if (minLatestBlockTimestamp == null)
|
||||||
BlockRepository blockRepository = repository.getBlockRepository();
|
continue;
|
||||||
|
|
||||||
|
// No online accounts for current timestamp? (e.g. during startup)
|
||||||
|
if (!OnlineAccountsManager.getInstance().hasOnlineAccounts())
|
||||||
|
continue;
|
||||||
|
|
||||||
List<MintingAccountData> mintingAccountsData = repository.getAccountRepository().getMintingAccounts();
|
List<MintingAccountData> mintingAccountsData = repository.getAccountRepository().getMintingAccounts();
|
||||||
// No minting accounts?
|
// No minting accounts?
|
||||||
@ -198,10 +201,6 @@ public class BlockMinter extends Thread {
|
|||||||
// so go ahead and mint a block if possible.
|
// so go ahead and mint a block if possible.
|
||||||
isMintingPossible = true;
|
isMintingPossible = true;
|
||||||
|
|
||||||
// Reattach newBlocks to new repository handle
|
|
||||||
for (Block newBlock : newBlocks)
|
|
||||||
newBlock.setRepository(repository);
|
|
||||||
|
|
||||||
// Check blockchain hasn't changed
|
// Check blockchain hasn't changed
|
||||||
if (previousBlockData == null || !Arrays.equals(previousBlockData.getSignature(), lastBlockData.getSignature())) {
|
if (previousBlockData == null || !Arrays.equals(previousBlockData.getSignature(), lastBlockData.getSignature())) {
|
||||||
previousBlockData = lastBlockData;
|
previousBlockData = lastBlockData;
|
||||||
@ -439,13 +438,13 @@ public class BlockMinter extends Thread {
|
|||||||
Network network = Network.getInstance();
|
Network network = Network.getInstance();
|
||||||
network.broadcast(broadcastPeer -> network.buildHeightMessage(broadcastPeer, newBlockData));
|
network.broadcast(broadcastPeer -> network.buildHeightMessage(broadcastPeer, newBlockData));
|
||||||
}
|
}
|
||||||
} catch (DataException e) {
|
} catch (InterruptedException e) {
|
||||||
LOGGER.warn("Repository issue while running block minter", e);
|
// We've been interrupted - time to exit
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
} catch (InterruptedException e) {
|
|
||||||
// We've been interrupted - time to exit
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
} catch (DataException e) {
|
||||||
|
LOGGER.warn("Repository issue while running block minter - NO LONGER MINTING", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -819,7 +819,7 @@ public class Controller extends Thread {
|
|||||||
actionText = String.format("%s", Translator.INSTANCE.translate("SysTray", "SYNCHRONIZING_BLOCKCHAIN"));
|
actionText = String.format("%s", Translator.INSTANCE.translate("SysTray", "SYNCHRONIZING_BLOCKCHAIN"));
|
||||||
SysTray.getInstance().setTrayIcon(3);
|
SysTray.getInstance().setTrayIcon(3);
|
||||||
}
|
}
|
||||||
else if (OnlineAccountsManager.getInstance().hasOnlineAccounts()) {
|
else if (OnlineAccountsManager.getInstance().hasActiveOnlineAccountSignatures()) {
|
||||||
actionText = Translator.INSTANCE.translate("SysTray", "MINTING_ENABLED");
|
actionText = Translator.INSTANCE.translate("SysTray", "MINTING_ENABLED");
|
||||||
SysTray.getInstance().setTrayIcon(2);
|
SysTray.getInstance().setTrayIcon(2);
|
||||||
}
|
}
|
||||||
|
@ -480,7 +480,7 @@ public class OnlineAccountsManager {
|
|||||||
final Long minLatestBlockTimestamp = NTP.getTime() - (2 * 60 * 60 * 1000L);
|
final Long minLatestBlockTimestamp = NTP.getTime() - (2 * 60 * 60 * 1000L);
|
||||||
boolean isUpToDate = Controller.getInstance().isUpToDate(minLatestBlockTimestamp);
|
boolean isUpToDate = Controller.getInstance().isUpToDate(minLatestBlockTimestamp);
|
||||||
|
|
||||||
return isUpToDate && hasOnlineAccounts();
|
return isUpToDate && hasOurOnlineAccounts();
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean hasOurOnlineAccounts() {
|
public boolean hasOurOnlineAccounts() {
|
||||||
|
@ -159,6 +159,9 @@ public interface AccountRepository {
|
|||||||
/** Returns number of active reward-shares involving passed public key as the minting account only. */
|
/** Returns number of active reward-shares involving passed public key as the minting account only. */
|
||||||
public int countRewardShares(byte[] mintingAccountPublicKey) throws DataException;
|
public int countRewardShares(byte[] mintingAccountPublicKey) throws DataException;
|
||||||
|
|
||||||
|
/** Returns number of active self-shares involving passed public key as the minting account only. */
|
||||||
|
public int countSelfShares(byte[] mintingAccountPublicKey) throws DataException;
|
||||||
|
|
||||||
public List<RewardShareData> getRewardShares() throws DataException;
|
public List<RewardShareData> getRewardShares() throws DataException;
|
||||||
|
|
||||||
public List<RewardShareData> findRewardShares(List<String> mintingAccounts, List<String> recipientAccounts, List<String> involvedAddresses, Integer limit, Integer offset, Boolean reverse) throws DataException;
|
public List<RewardShareData> findRewardShares(List<String> mintingAccounts, List<String> recipientAccounts, List<String> involvedAddresses, Integer limit, Integer offset, Boolean reverse) throws DataException;
|
||||||
|
@ -688,6 +688,17 @@ public class HSQLDBAccountRepository implements AccountRepository {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int countSelfShares(byte[] minterPublicKey) throws DataException {
|
||||||
|
String sql = "SELECT COUNT(*) FROM RewardShares WHERE minter_public_key = ? AND minter = recipient";
|
||||||
|
|
||||||
|
try (ResultSet resultSet = this.repository.checkedExecute(sql, minterPublicKey)) {
|
||||||
|
return resultSet.getInt(1);
|
||||||
|
} catch (SQLException e) {
|
||||||
|
throw new DataException("Unable to count self-shares in repository", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<RewardShareData> getRewardShares() throws DataException {
|
public List<RewardShareData> getRewardShares() throws DataException {
|
||||||
String sql = "SELECT minter_public_key, minter, recipient, share_percent, reward_share_public_key FROM RewardShares";
|
String sql = "SELECT minter_public_key, minter, recipient, share_percent, reward_share_public_key FROM RewardShares";
|
||||||
|
@ -140,8 +140,21 @@ public class RewardShareTransaction extends Transaction {
|
|||||||
|
|
||||||
// Check the minting account hasn't reach maximum number of reward-shares
|
// Check the minting account hasn't reach maximum number of reward-shares
|
||||||
int rewardShareCount = this.repository.getAccountRepository().countRewardShares(creator.getPublicKey());
|
int rewardShareCount = this.repository.getAccountRepository().countRewardShares(creator.getPublicKey());
|
||||||
if (rewardShareCount >= BlockChain.getInstance().getMaxRewardSharesPerMintingAccount())
|
int selfShareCount = this.repository.getAccountRepository().countSelfShares(creator.getPublicKey());
|
||||||
|
|
||||||
|
int maxRewardShares = BlockChain.getInstance().getMaxRewardSharesAtTimestamp(this.rewardShareTransactionData.getTimestamp());
|
||||||
|
if (creator.isFounder())
|
||||||
|
// Founders have a different limit
|
||||||
|
maxRewardShares = BlockChain.getInstance().getMaxRewardSharesPerFounderMintingAccount();
|
||||||
|
|
||||||
|
if (rewardShareCount >= maxRewardShares)
|
||||||
return ValidationResult.MAXIMUM_REWARD_SHARES;
|
return ValidationResult.MAXIMUM_REWARD_SHARES;
|
||||||
|
|
||||||
|
// When filling all reward share slots, one must be a self share (after feature trigger timestamp)
|
||||||
|
if (this.rewardShareTransactionData.getTimestamp() >= BlockChain.getInstance().getRewardShareLimitTimestamp())
|
||||||
|
if (!isRecipientAlsoMinter && rewardShareCount == maxRewardShares-1 && selfShareCount == 0)
|
||||||
|
return ValidationResult.MAXIMUM_REWARD_SHARES;
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// This transaction intends to modify/terminate an existing reward-share
|
// This transaction intends to modify/terminate an existing reward-share
|
||||||
|
|
||||||
|
@ -15,7 +15,11 @@
|
|||||||
"minAccountLevelToMint": 1,
|
"minAccountLevelToMint": 1,
|
||||||
"minAccountLevelForBlockSubmissions": 5,
|
"minAccountLevelForBlockSubmissions": 5,
|
||||||
"minAccountLevelToRewardShare": 5,
|
"minAccountLevelToRewardShare": 5,
|
||||||
"maxRewardSharesPerMintingAccount": 6,
|
"maxRewardSharesPerFounderMintingAccount": 6,
|
||||||
|
"maxRewardSharesByTimestamp": [
|
||||||
|
{ "timestamp": 0, "maxShares": 6 },
|
||||||
|
{ "timestamp": 1657382400000, "maxShares": 3 }
|
||||||
|
],
|
||||||
"founderEffectiveMintingLevel": 10,
|
"founderEffectiveMintingLevel": 10,
|
||||||
"onlineAccountSignaturesMinLifetime": 43200000,
|
"onlineAccountSignaturesMinLifetime": 43200000,
|
||||||
"onlineAccountSignaturesMaxLifetime": 86400000,
|
"onlineAccountSignaturesMaxLifetime": 86400000,
|
||||||
@ -36,14 +40,16 @@
|
|||||||
{ "height": 3110401, "reward": 2.00 }
|
{ "height": 3110401, "reward": 2.00 }
|
||||||
],
|
],
|
||||||
"sharesByLevel": [
|
"sharesByLevel": [
|
||||||
{ "levels": [ 1, 2 ], "share": 0.05 },
|
{ "id": 1, "levels": [ 1, 2 ], "share": 0.05 },
|
||||||
{ "levels": [ 3, 4 ], "share": 0.10 },
|
{ "id": 2, "levels": [ 3, 4 ], "share": 0.10 },
|
||||||
{ "levels": [ 5, 6 ], "share": 0.15 },
|
{ "id": 3, "levels": [ 5, 6 ], "share": 0.15 },
|
||||||
{ "levels": [ 7, 8 ], "share": 0.20 },
|
{ "id": 4, "levels": [ 7, 8 ], "share": 0.20 },
|
||||||
{ "levels": [ 9, 10 ], "share": 0.25 }
|
{ "id": 5, "levels": [ 9, 10 ], "share": 0.25 }
|
||||||
],
|
],
|
||||||
"qoraHoldersShare": 0.20,
|
"qoraHoldersShare": 0.20,
|
||||||
"qoraPerQortReward": 250,
|
"qoraPerQortReward": 250,
|
||||||
|
"minAccountsToActivateShareBin": 30,
|
||||||
|
"shareBinActivationMinLevel": 7,
|
||||||
"blocksNeededByLevel": [ 7200, 64800, 129600, 172800, 244000, 345600, 518400, 691200, 864000, 1036800 ],
|
"blocksNeededByLevel": [ 7200, 64800, 129600, 172800, 244000, 345600, 518400, 691200, 864000, 1036800 ],
|
||||||
"blockTimingsByHeight": [
|
"blockTimingsByHeight": [
|
||||||
{ "height": 1, "target": 60000, "deviation": 30000, "power": 0.2 }
|
{ "height": 1, "target": 60000, "deviation": 30000, "power": 0.2 }
|
||||||
@ -58,6 +64,7 @@
|
|||||||
"atFindNextTransactionFix": 275000,
|
"atFindNextTransactionFix": 275000,
|
||||||
"newBlockSigHeight": 320000,
|
"newBlockSigHeight": 320000,
|
||||||
"shareBinFix": 399000,
|
"shareBinFix": 399000,
|
||||||
|
"rewardShareLimitTimestamp": 1657382400000,
|
||||||
"calcChainWeightTimestamp": 1620579600000,
|
"calcChainWeightTimestamp": 1620579600000,
|
||||||
"transactionV5Timestamp": 1642176000000,
|
"transactionV5Timestamp": 1642176000000,
|
||||||
"transactionV6Timestamp": 9999999999999,
|
"transactionV6Timestamp": 9999999999999,
|
||||||
|
@ -41,7 +41,10 @@ public class AccountUtils {
|
|||||||
public static TransactionData createRewardShare(Repository repository, String minter, String recipient, int sharePercent) throws DataException {
|
public static TransactionData createRewardShare(Repository repository, String minter, String recipient, int sharePercent) throws DataException {
|
||||||
PrivateKeyAccount mintingAccount = Common.getTestAccount(repository, minter);
|
PrivateKeyAccount mintingAccount = Common.getTestAccount(repository, minter);
|
||||||
PrivateKeyAccount recipientAccount = Common.getTestAccount(repository, recipient);
|
PrivateKeyAccount recipientAccount = Common.getTestAccount(repository, recipient);
|
||||||
|
return createRewardShare(repository, mintingAccount, recipientAccount, sharePercent);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static TransactionData createRewardShare(Repository repository, PrivateKeyAccount mintingAccount, PrivateKeyAccount recipientAccount, int sharePercent) throws DataException {
|
||||||
byte[] reference = mintingAccount.getLastReference();
|
byte[] reference = mintingAccount.getLastReference();
|
||||||
long timestamp = repository.getTransactionRepository().fromSignature(reference).getTimestamp() + 1;
|
long timestamp = repository.getTransactionRepository().fromSignature(reference).getTimestamp() + 1;
|
||||||
|
|
||||||
@ -66,6 +69,15 @@ public class AccountUtils {
|
|||||||
return rewardSharePrivateKey;
|
return rewardSharePrivateKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static byte[] rewardShare(Repository repository, PrivateKeyAccount minterAccount, PrivateKeyAccount recipientAccount, int sharePercent) throws DataException {
|
||||||
|
TransactionData transactionData = createRewardShare(repository, minterAccount, recipientAccount, sharePercent);
|
||||||
|
|
||||||
|
TransactionUtils.signAndMint(repository, transactionData, minterAccount);
|
||||||
|
byte[] rewardSharePrivateKey = minterAccount.getRewardSharePrivateKey(recipientAccount.getPublicKey());
|
||||||
|
|
||||||
|
return rewardSharePrivateKey;
|
||||||
|
}
|
||||||
|
|
||||||
public static Map<String, Map<Long, Long>> getBalances(Repository repository, long... assetIds) throws DataException {
|
public static Map<String, Map<Long, Long>> getBalances(Repository repository, long... assetIds) throws DataException {
|
||||||
Map<String, Map<Long, Long>> balances = new HashMap<>();
|
Map<String, Map<Long, Long>> balances = new HashMap<>();
|
||||||
|
|
||||||
|
@ -7,6 +7,7 @@ import java.math.BigDecimal;
|
|||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
|
import java.security.SecureRandom;
|
||||||
import java.security.Security;
|
import java.security.Security;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
@ -25,6 +26,7 @@ import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
|||||||
import org.bouncycastle.jsse.provider.BouncyCastleJsseProvider;
|
import org.bouncycastle.jsse.provider.BouncyCastleJsseProvider;
|
||||||
import org.junit.AfterClass;
|
import org.junit.AfterClass;
|
||||||
import org.junit.BeforeClass;
|
import org.junit.BeforeClass;
|
||||||
|
import org.qortal.account.PrivateKeyAccount;
|
||||||
import org.qortal.block.BlockChain;
|
import org.qortal.block.BlockChain;
|
||||||
import org.qortal.data.account.AccountBalanceData;
|
import org.qortal.data.account.AccountBalanceData;
|
||||||
import org.qortal.data.asset.AssetData;
|
import org.qortal.data.asset.AssetData;
|
||||||
@ -111,6 +113,12 @@ public class Common {
|
|||||||
return testAccountsByName.values().stream().map(account -> new TestAccount(repository, account)).collect(Collectors.toList());
|
return testAccountsByName.values().stream().map(account -> new TestAccount(repository, account)).collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static PrivateKeyAccount generateRandomSeedAccount(Repository repository) {
|
||||||
|
byte[] seed = new byte[32];
|
||||||
|
new SecureRandom().nextBytes(seed);
|
||||||
|
return new PrivateKeyAccount(repository, seed);
|
||||||
|
}
|
||||||
|
|
||||||
public static void useSettingsAndDb(String settingsFilename, boolean dbInMemory) throws DataException {
|
public static void useSettingsAndDb(String settingsFilename, boolean dbInMemory) throws DataException {
|
||||||
closeRepository();
|
closeRepository();
|
||||||
|
|
||||||
|
@ -177,4 +177,143 @@ public class RewardShareTests extends Common {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCreateRewardSharesBeforeReduction() throws DataException {
|
||||||
|
final int sharePercent = 0;
|
||||||
|
|
||||||
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
|
PrivateKeyAccount dilbertAccount = Common.getTestAccount(repository, "dilbert");
|
||||||
|
|
||||||
|
// Create 6 reward shares
|
||||||
|
for (int i=0; i<6; i++) {
|
||||||
|
AccountUtils.rewardShare(repository, dilbertAccount, Common.generateRandomSeedAccount(repository), sharePercent);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 7th reward share should fail because we've reached the limit (and we're not yet requiring a self share)
|
||||||
|
AssertionError assertionError = null;
|
||||||
|
try {
|
||||||
|
AccountUtils.rewardShare(repository, dilbertAccount, Common.generateRandomSeedAccount(repository), sharePercent);
|
||||||
|
} catch (AssertionError e) {
|
||||||
|
assertionError = e;
|
||||||
|
}
|
||||||
|
assertNotNull("Transaction should be invalid", assertionError);
|
||||||
|
assertTrue("Transaction should be invalid due to reaching maximum reward shares", assertionError.getMessage().contains("MAXIMUM_REWARD_SHARES"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCreateRewardSharesAfterReduction() throws DataException {
|
||||||
|
Common.useSettings("test-settings-v2-reward-shares.json");
|
||||||
|
|
||||||
|
final int sharePercent = 0;
|
||||||
|
|
||||||
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
|
PrivateKeyAccount dilbertAccount = Common.getTestAccount(repository, "dilbert");
|
||||||
|
|
||||||
|
// Create 2 reward shares
|
||||||
|
for (int i=0; i<2; i++) {
|
||||||
|
AccountUtils.rewardShare(repository, dilbertAccount, Common.generateRandomSeedAccount(repository), sharePercent);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3rd reward share should fail because we've reached the limit (and we haven't got a self share)
|
||||||
|
AssertionError assertionError = null;
|
||||||
|
try {
|
||||||
|
AccountUtils.rewardShare(repository, dilbertAccount, Common.generateRandomSeedAccount(repository), sharePercent);
|
||||||
|
} catch (AssertionError e) {
|
||||||
|
assertionError = e;
|
||||||
|
}
|
||||||
|
assertNotNull("Transaction should be invalid", assertionError);
|
||||||
|
assertTrue("Transaction should be invalid due to reaching maximum reward shares", assertionError.getMessage().contains("MAXIMUM_REWARD_SHARES"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCreateSelfAndRewardSharesAfterReduction() throws DataException {
|
||||||
|
Common.useSettings("test-settings-v2-reward-shares.json");
|
||||||
|
|
||||||
|
final int sharePercent = 0;
|
||||||
|
|
||||||
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
|
PrivateKeyAccount dilbertAccount = Common.getTestAccount(repository, "dilbert");
|
||||||
|
|
||||||
|
// Create 2 reward shares
|
||||||
|
for (int i=0; i<2; i++) {
|
||||||
|
AccountUtils.rewardShare(repository, dilbertAccount, Common.generateRandomSeedAccount(repository), sharePercent);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3rd reward share should fail because we've reached the limit (and we haven't got a self share)
|
||||||
|
AssertionError assertionError = null;
|
||||||
|
try {
|
||||||
|
AccountUtils.rewardShare(repository, dilbertAccount, Common.generateRandomSeedAccount(repository), sharePercent);
|
||||||
|
} catch (AssertionError e) {
|
||||||
|
assertionError = e;
|
||||||
|
}
|
||||||
|
assertNotNull("Transaction should be invalid", assertionError);
|
||||||
|
assertTrue("Transaction should be invalid due to reaching maximum reward shares", assertionError.getMessage().contains("MAXIMUM_REWARD_SHARES"));
|
||||||
|
|
||||||
|
// Now create a self share, which should succeed as we have space for it
|
||||||
|
AccountUtils.rewardShare(repository, dilbertAccount, dilbertAccount, sharePercent);
|
||||||
|
|
||||||
|
// 4th reward share should fail because we've reached the limit (including the self share)
|
||||||
|
assertionError = null;
|
||||||
|
try {
|
||||||
|
AccountUtils.rewardShare(repository, dilbertAccount, Common.generateRandomSeedAccount(repository), sharePercent);
|
||||||
|
} catch (AssertionError e) {
|
||||||
|
assertionError = e;
|
||||||
|
}
|
||||||
|
assertNotNull("Transaction should be invalid", assertionError);
|
||||||
|
assertTrue("Transaction should be invalid due to reaching maximum reward shares", assertionError.getMessage().contains("MAXIMUM_REWARD_SHARES"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCreateFounderRewardSharesBeforeReduction() throws DataException {
|
||||||
|
final int sharePercent = 0;
|
||||||
|
|
||||||
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
|
PrivateKeyAccount aliceFounderAccount = Common.getTestAccount(repository, "alice");
|
||||||
|
|
||||||
|
// Create 5 reward shares (not 6, because alice already starts with a self reward share in the genesis block)
|
||||||
|
for (int i=0; i<5; i++) {
|
||||||
|
AccountUtils.rewardShare(repository, aliceFounderAccount, Common.generateRandomSeedAccount(repository), sharePercent);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 6th reward share should fail
|
||||||
|
AssertionError assertionError = null;
|
||||||
|
try {
|
||||||
|
AccountUtils.rewardShare(repository, aliceFounderAccount, Common.generateRandomSeedAccount(repository), sharePercent);
|
||||||
|
} catch (AssertionError e) {
|
||||||
|
assertionError = e;
|
||||||
|
}
|
||||||
|
assertNotNull("Transaction should be invalid", assertionError);
|
||||||
|
assertTrue("Transaction should be invalid due to reaching maximum reward shares", assertionError.getMessage().contains("MAXIMUM_REWARD_SHARES"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCreateFounderRewardSharesAfterReduction() throws DataException {
|
||||||
|
Common.useSettings("test-settings-v2-reward-shares.json");
|
||||||
|
|
||||||
|
final int sharePercent = 0;
|
||||||
|
|
||||||
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
|
PrivateKeyAccount aliceFounderAccount = Common.getTestAccount(repository, "alice");
|
||||||
|
|
||||||
|
// Create 5 reward shares (not 6, because alice already starts with a self reward share in the genesis block)
|
||||||
|
for (int i=0; i<5; i++) {
|
||||||
|
AccountUtils.rewardShare(repository, aliceFounderAccount, Common.generateRandomSeedAccount(repository), sharePercent);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 6th reward share should fail
|
||||||
|
AssertionError assertionError = null;
|
||||||
|
try {
|
||||||
|
AccountUtils.rewardShare(repository, aliceFounderAccount, Common.generateRandomSeedAccount(repository), sharePercent);
|
||||||
|
} catch (AssertionError e) {
|
||||||
|
assertionError = e;
|
||||||
|
}
|
||||||
|
assertNotNull("Transaction should be invalid", assertionError);
|
||||||
|
assertTrue("Transaction should be invalid due to reaching maximum reward shares", assertionError.getMessage().contains("MAXIMUM_REWARD_SHARES"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -3,10 +3,9 @@ package org.qortal.test.minting;
|
|||||||
import static org.junit.Assert.*;
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
import java.util.ArrayList;
|
import java.util.*;
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.reflect.FieldUtils;
|
||||||
import org.apache.logging.log4j.LogManager;
|
import org.apache.logging.log4j.LogManager;
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
import org.junit.After;
|
import org.junit.After;
|
||||||
@ -702,6 +701,15 @@ public class RewardTests extends Common {
|
|||||||
AccountUtils.assertBalance(repository, "chloe", Asset.QORT, chloeInitialBalance+expectedLevel7And8Reward);
|
AccountUtils.assertBalance(repository, "chloe", Asset.QORT, chloeInitialBalance+expectedLevel7And8Reward);
|
||||||
AccountUtils.assertBalance(repository, "dilbert", Asset.QORT, dilbertInitialBalance+expectedLevel7And8Reward);
|
AccountUtils.assertBalance(repository, "dilbert", Asset.QORT, dilbertInitialBalance+expectedLevel7And8Reward);
|
||||||
|
|
||||||
|
// Orphan and ensure balances return to their previous values
|
||||||
|
BlockUtils.orphanBlocks(repository, 1);
|
||||||
|
|
||||||
|
// Validate the balances
|
||||||
|
AccountUtils.assertBalance(repository, "alice", Asset.QORT, aliceInitialBalance);
|
||||||
|
AccountUtils.assertBalance(repository, "bob", Asset.QORT, bobInitialBalance);
|
||||||
|
AccountUtils.assertBalance(repository, "chloe", Asset.QORT, chloeInitialBalance);
|
||||||
|
AccountUtils.assertBalance(repository, "dilbert", Asset.QORT, dilbertInitialBalance);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -787,6 +795,348 @@ public class RewardTests extends Common {
|
|||||||
AccountUtils.assertBalance(repository, "chloe", Asset.QORT, chloeInitialBalance+expectedLevel9And10Reward);
|
AccountUtils.assertBalance(repository, "chloe", Asset.QORT, chloeInitialBalance+expectedLevel9And10Reward);
|
||||||
AccountUtils.assertBalance(repository, "dilbert", Asset.QORT, dilbertInitialBalance+expectedLevel9And10Reward);
|
AccountUtils.assertBalance(repository, "dilbert", Asset.QORT, dilbertInitialBalance+expectedLevel9And10Reward);
|
||||||
|
|
||||||
|
// Orphan and ensure balances return to their previous values
|
||||||
|
BlockUtils.orphanBlocks(repository, 1);
|
||||||
|
|
||||||
|
// Validate the balances
|
||||||
|
AccountUtils.assertBalance(repository, "alice", Asset.QORT, aliceInitialBalance);
|
||||||
|
AccountUtils.assertBalance(repository, "bob", Asset.QORT, bobInitialBalance);
|
||||||
|
AccountUtils.assertBalance(repository, "chloe", Asset.QORT, chloeInitialBalance);
|
||||||
|
AccountUtils.assertBalance(repository, "dilbert", Asset.QORT, dilbertInitialBalance);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Test rewards for level 7 and 8 accounts, when the tier doesn't yet have enough minters in it */
|
||||||
|
@Test
|
||||||
|
public void testLevel7And8RewardsPreActivation() throws DataException, IllegalAccessException {
|
||||||
|
Common.useSettings("test-settings-v2-reward-levels.json");
|
||||||
|
|
||||||
|
// Set minAccountsToActivateShareBin to 3 so that share bins 7-8 and 9-10 are considered inactive
|
||||||
|
FieldUtils.writeField(BlockChain.getInstance(), "minAccountsToActivateShareBin", 3, true);
|
||||||
|
|
||||||
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
|
|
||||||
|
List<Integer> cumulativeBlocksByLevel = BlockChain.getInstance().getCumulativeBlocksByLevel();
|
||||||
|
List<PrivateKeyAccount> mintingAndOnlineAccounts = new ArrayList<>();
|
||||||
|
|
||||||
|
// Alice self share online
|
||||||
|
PrivateKeyAccount aliceSelfShare = Common.getTestAccount(repository, "alice-reward-share");
|
||||||
|
mintingAndOnlineAccounts.add(aliceSelfShare);
|
||||||
|
|
||||||
|
// Bob self-share NOT online
|
||||||
|
|
||||||
|
// Chloe self share online
|
||||||
|
byte[] chloeRewardSharePrivateKey = AccountUtils.rewardShare(repository, "chloe", "chloe", 0);
|
||||||
|
PrivateKeyAccount chloeRewardShareAccount = new PrivateKeyAccount(repository, chloeRewardSharePrivateKey);
|
||||||
|
mintingAndOnlineAccounts.add(chloeRewardShareAccount);
|
||||||
|
|
||||||
|
// Dilbert self share online
|
||||||
|
byte[] dilbertRewardSharePrivateKey = AccountUtils.rewardShare(repository, "dilbert", "dilbert", 0);
|
||||||
|
PrivateKeyAccount dilbertRewardShareAccount = new PrivateKeyAccount(repository, dilbertRewardSharePrivateKey);
|
||||||
|
mintingAndOnlineAccounts.add(dilbertRewardShareAccount);
|
||||||
|
|
||||||
|
// Mint enough blocks to bump testAccount levels to 7 and 8
|
||||||
|
final int minterBlocksNeeded = cumulativeBlocksByLevel.get(8) - 20; // 20 blocks before level 8, so that the test accounts reach the correct levels
|
||||||
|
for (int bc = 0; bc < minterBlocksNeeded; ++bc)
|
||||||
|
BlockMinter.mintTestingBlock(repository, mintingAndOnlineAccounts.toArray(new PrivateKeyAccount[0]));
|
||||||
|
|
||||||
|
// Ensure that the levels are as we expect
|
||||||
|
assertEquals(7, (int) Common.getTestAccount(repository, "alice").getLevel());
|
||||||
|
assertEquals(1, (int) Common.getTestAccount(repository, "bob").getLevel());
|
||||||
|
assertEquals(7, (int) Common.getTestAccount(repository, "chloe").getLevel());
|
||||||
|
assertEquals(8, (int) Common.getTestAccount(repository, "dilbert").getLevel());
|
||||||
|
|
||||||
|
// Now that everyone is at level 7 or 8 (except Bob who has only just started minting, so is at level 1), we can capture initial balances
|
||||||
|
Map<String, Map<Long, Long>> initialBalances = AccountUtils.getBalances(repository, Asset.QORT, Asset.LEGACY_QORA, Asset.QORT_FROM_QORA);
|
||||||
|
final long aliceInitialBalance = initialBalances.get("alice").get(Asset.QORT);
|
||||||
|
final long bobInitialBalance = initialBalances.get("bob").get(Asset.QORT);
|
||||||
|
final long chloeInitialBalance = initialBalances.get("chloe").get(Asset.QORT);
|
||||||
|
final long dilbertInitialBalance = initialBalances.get("dilbert").get(Asset.QORT);
|
||||||
|
|
||||||
|
// Mint a block
|
||||||
|
final long blockReward = BlockUtils.getNextBlockReward(repository);
|
||||||
|
BlockMinter.mintTestingBlock(repository, mintingAndOnlineAccounts.toArray(new PrivateKeyAccount[0]));
|
||||||
|
|
||||||
|
// Ensure we are using the correct block reward value
|
||||||
|
assertEquals(100000000L, blockReward);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Alice, Chloe, and Dilbert are 'online'.
|
||||||
|
* Chloe is level 7; Dilbert is level 8.
|
||||||
|
* One founder online (Alice, who is also level 7).
|
||||||
|
* No legacy QORA holders.
|
||||||
|
*
|
||||||
|
* Level 7 and 8 is not yet activated, so its rewards are added to the level 5 and 6 share bin.
|
||||||
|
* There are no level 5 and 6 online.
|
||||||
|
* Chloe and Dilbert should receive equal shares of the 35% block reward for levels 5 to 8.
|
||||||
|
* Alice should receive the remainder (65%).
|
||||||
|
*/
|
||||||
|
|
||||||
|
final int level5To8SharePercent = 35_00; // 35% (combined 15% and 20%)
|
||||||
|
final long level5To8ShareAmount = (blockReward * level5To8SharePercent) / 100L / 100L;
|
||||||
|
final long expectedLevel5To8Reward = level5To8ShareAmount / 2; // The reward is split between Chloe and Dilbert
|
||||||
|
final long expectedFounderReward = blockReward - level5To8ShareAmount; // Alice should receive the remainder
|
||||||
|
|
||||||
|
// Validate the balances
|
||||||
|
AccountUtils.assertBalance(repository, "alice", Asset.QORT, aliceInitialBalance+expectedFounderReward);
|
||||||
|
AccountUtils.assertBalance(repository, "bob", Asset.QORT, bobInitialBalance); // Bob not online so his balance remains the same
|
||||||
|
AccountUtils.assertBalance(repository, "chloe", Asset.QORT, chloeInitialBalance+expectedLevel5To8Reward);
|
||||||
|
AccountUtils.assertBalance(repository, "dilbert", Asset.QORT, dilbertInitialBalance+expectedLevel5To8Reward);
|
||||||
|
|
||||||
|
// Orphan and ensure balances return to their previous values
|
||||||
|
BlockUtils.orphanBlocks(repository, 1);
|
||||||
|
|
||||||
|
// Validate the balances
|
||||||
|
AccountUtils.assertBalance(repository, "alice", Asset.QORT, aliceInitialBalance);
|
||||||
|
AccountUtils.assertBalance(repository, "bob", Asset.QORT, bobInitialBalance);
|
||||||
|
AccountUtils.assertBalance(repository, "chloe", Asset.QORT, chloeInitialBalance);
|
||||||
|
AccountUtils.assertBalance(repository, "dilbert", Asset.QORT, dilbertInitialBalance);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Test rewards for level 9 and 10 accounts, when the tier doesn't yet have enough minters in it.
|
||||||
|
* Tier 7-8 isn't activated either, so the rewards and minters are all moved to tier 5-6. */
|
||||||
|
@Test
|
||||||
|
public void testLevel9And10RewardsPreActivation() throws DataException, IllegalAccessException {
|
||||||
|
Common.useSettings("test-settings-v2-reward-levels.json");
|
||||||
|
|
||||||
|
// Set minAccountsToActivateShareBin to 3 so that share bins 7-8 and 9-10 are considered inactive
|
||||||
|
FieldUtils.writeField(BlockChain.getInstance(), "minAccountsToActivateShareBin", 3, true);
|
||||||
|
|
||||||
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
|
|
||||||
|
List<Integer> cumulativeBlocksByLevel = BlockChain.getInstance().getCumulativeBlocksByLevel();
|
||||||
|
List<PrivateKeyAccount> mintingAndOnlineAccounts = new ArrayList<>();
|
||||||
|
|
||||||
|
// Alice self share online
|
||||||
|
PrivateKeyAccount aliceSelfShare = Common.getTestAccount(repository, "alice-reward-share");
|
||||||
|
mintingAndOnlineAccounts.add(aliceSelfShare);
|
||||||
|
|
||||||
|
// Bob self-share not initially online
|
||||||
|
|
||||||
|
// Chloe self share online
|
||||||
|
byte[] chloeRewardSharePrivateKey = AccountUtils.rewardShare(repository, "chloe", "chloe", 0);
|
||||||
|
PrivateKeyAccount chloeRewardShareAccount = new PrivateKeyAccount(repository, chloeRewardSharePrivateKey);
|
||||||
|
mintingAndOnlineAccounts.add(chloeRewardShareAccount);
|
||||||
|
|
||||||
|
// Dilbert self share online
|
||||||
|
byte[] dilbertRewardSharePrivateKey = AccountUtils.rewardShare(repository, "dilbert", "dilbert", 0);
|
||||||
|
PrivateKeyAccount dilbertRewardShareAccount = new PrivateKeyAccount(repository, dilbertRewardSharePrivateKey);
|
||||||
|
mintingAndOnlineAccounts.add(dilbertRewardShareAccount);
|
||||||
|
|
||||||
|
// Mint enough blocks to bump testAccount levels to 9 and 10
|
||||||
|
final int minterBlocksNeeded = cumulativeBlocksByLevel.get(10) - 20; // 20 blocks before level 10, so that the test accounts reach the correct levels
|
||||||
|
for (int bc = 0; bc < minterBlocksNeeded; ++bc)
|
||||||
|
BlockMinter.mintTestingBlock(repository, mintingAndOnlineAccounts.toArray(new PrivateKeyAccount[0]));
|
||||||
|
|
||||||
|
// Bob self-share now comes online
|
||||||
|
byte[] bobRewardSharePrivateKey = AccountUtils.rewardShare(repository, "bob", "bob", 0);
|
||||||
|
PrivateKeyAccount bobRewardShareAccount = new PrivateKeyAccount(repository, bobRewardSharePrivateKey);
|
||||||
|
mintingAndOnlineAccounts.add(bobRewardShareAccount);
|
||||||
|
|
||||||
|
// Ensure that the levels are as we expect
|
||||||
|
assertEquals(9, (int) Common.getTestAccount(repository, "alice").getLevel());
|
||||||
|
assertEquals(1, (int) Common.getTestAccount(repository, "bob").getLevel());
|
||||||
|
assertEquals(9, (int) Common.getTestAccount(repository, "chloe").getLevel());
|
||||||
|
assertEquals(10, (int) Common.getTestAccount(repository, "dilbert").getLevel());
|
||||||
|
|
||||||
|
// Now that everyone is at level 7 or 8 (except Bob who has only just started minting, so is at level 1), we can capture initial balances
|
||||||
|
Map<String, Map<Long, Long>> initialBalances = AccountUtils.getBalances(repository, Asset.QORT, Asset.LEGACY_QORA, Asset.QORT_FROM_QORA);
|
||||||
|
final long aliceInitialBalance = initialBalances.get("alice").get(Asset.QORT);
|
||||||
|
final long bobInitialBalance = initialBalances.get("bob").get(Asset.QORT);
|
||||||
|
final long chloeInitialBalance = initialBalances.get("chloe").get(Asset.QORT);
|
||||||
|
final long dilbertInitialBalance = initialBalances.get("dilbert").get(Asset.QORT);
|
||||||
|
|
||||||
|
// Mint a block
|
||||||
|
final long blockReward = BlockUtils.getNextBlockReward(repository);
|
||||||
|
BlockMinter.mintTestingBlock(repository, mintingAndOnlineAccounts.toArray(new PrivateKeyAccount[0]));
|
||||||
|
|
||||||
|
// Ensure we are using the correct block reward value
|
||||||
|
assertEquals(100000000L, blockReward);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Alice, Bob, Chloe, and Dilbert are 'online'.
|
||||||
|
* Bob is level 1; Chloe is level 9; Dilbert is level 10.
|
||||||
|
* One founder online (Alice, who is also level 9).
|
||||||
|
* No legacy QORA holders.
|
||||||
|
*
|
||||||
|
* Levels 7+8, and 9+10 are not yet activated, so their rewards are added to the level 5 and 6 share bin.
|
||||||
|
* There are no levels 5-8 online.
|
||||||
|
* Chloe and Dilbert should receive equal shares of the 60% block reward for levels 5 to 10.
|
||||||
|
* Alice should receive the remainder (40%).
|
||||||
|
*/
|
||||||
|
|
||||||
|
final int level1And2SharePercent = 5_00; // 5%
|
||||||
|
final int level5To10SharePercent = 60_00; // 60% (combined 15%, 20%, and 25%)
|
||||||
|
final long level1And2ShareAmount = (blockReward * level1And2SharePercent) / 100L / 100L;
|
||||||
|
final long level5To10ShareAmount = (blockReward * level5To10SharePercent) / 100L / 100L;
|
||||||
|
final long expectedLevel1And2Reward = level1And2ShareAmount; // The reward is given entirely to Bob
|
||||||
|
final long expectedLevel5To10Reward = level5To10ShareAmount / 2; // The reward is split between Chloe and Dilbert
|
||||||
|
final long expectedFounderReward = blockReward - level1And2ShareAmount - level5To10ShareAmount; // Alice should receive the remainder
|
||||||
|
|
||||||
|
// Validate the balances
|
||||||
|
AccountUtils.assertBalance(repository, "alice", Asset.QORT, aliceInitialBalance+expectedFounderReward);
|
||||||
|
AccountUtils.assertBalance(repository, "bob", Asset.QORT, bobInitialBalance+expectedLevel1And2Reward);
|
||||||
|
AccountUtils.assertBalance(repository, "chloe", Asset.QORT, chloeInitialBalance+expectedLevel5To10Reward);
|
||||||
|
AccountUtils.assertBalance(repository, "dilbert", Asset.QORT, dilbertInitialBalance+expectedLevel5To10Reward);
|
||||||
|
|
||||||
|
// Orphan and ensure balances return to their previous values
|
||||||
|
BlockUtils.orphanBlocks(repository, 1);
|
||||||
|
|
||||||
|
// Validate the balances
|
||||||
|
AccountUtils.assertBalance(repository, "alice", Asset.QORT, aliceInitialBalance);
|
||||||
|
AccountUtils.assertBalance(repository, "bob", Asset.QORT, bobInitialBalance);
|
||||||
|
AccountUtils.assertBalance(repository, "chloe", Asset.QORT, chloeInitialBalance);
|
||||||
|
AccountUtils.assertBalance(repository, "dilbert", Asset.QORT, dilbertInitialBalance);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Test rewards for level 7 and 8 accounts, when the tier reaches the minimum number of accounts */
|
||||||
|
@Test
|
||||||
|
public void testLevel7And8RewardsPreAndPostActivation() throws DataException, IllegalAccessException {
|
||||||
|
Common.useSettings("test-settings-v2-reward-levels.json");
|
||||||
|
|
||||||
|
// Set minAccountsToActivateShareBin to 2 so that share bins 7-8 and 9-10 are considered inactive at first
|
||||||
|
FieldUtils.writeField(BlockChain.getInstance(), "minAccountsToActivateShareBin", 2, true);
|
||||||
|
|
||||||
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
|
|
||||||
|
List<Integer> cumulativeBlocksByLevel = BlockChain.getInstance().getCumulativeBlocksByLevel();
|
||||||
|
List<PrivateKeyAccount> mintingAndOnlineAccounts = new ArrayList<>();
|
||||||
|
|
||||||
|
// Alice self share online
|
||||||
|
PrivateKeyAccount aliceSelfShare = Common.getTestAccount(repository, "alice-reward-share");
|
||||||
|
mintingAndOnlineAccounts.add(aliceSelfShare);
|
||||||
|
|
||||||
|
// Bob self-share NOT online
|
||||||
|
|
||||||
|
// Chloe self share online
|
||||||
|
byte[] chloeRewardSharePrivateKey = AccountUtils.rewardShare(repository, "chloe", "chloe", 0);
|
||||||
|
PrivateKeyAccount chloeRewardShareAccount = new PrivateKeyAccount(repository, chloeRewardSharePrivateKey);
|
||||||
|
mintingAndOnlineAccounts.add(chloeRewardShareAccount);
|
||||||
|
|
||||||
|
// Dilbert self share online
|
||||||
|
byte[] dilbertRewardSharePrivateKey = AccountUtils.rewardShare(repository, "dilbert", "dilbert", 0);
|
||||||
|
PrivateKeyAccount dilbertRewardShareAccount = new PrivateKeyAccount(repository, dilbertRewardSharePrivateKey);
|
||||||
|
mintingAndOnlineAccounts.add(dilbertRewardShareAccount);
|
||||||
|
|
||||||
|
// Mint enough blocks to bump two of the testAccount levels to 7
|
||||||
|
final int minterBlocksNeeded = cumulativeBlocksByLevel.get(7) - 12; // 12 blocks before level 7, so that dilbert and alice have reached level 7, but chloe will reach it in the next 2 blocks
|
||||||
|
for (int bc = 0; bc < minterBlocksNeeded; ++bc)
|
||||||
|
BlockMinter.mintTestingBlock(repository, mintingAndOnlineAccounts.toArray(new PrivateKeyAccount[0]));
|
||||||
|
|
||||||
|
// Ensure that the levels are as we expect
|
||||||
|
assertEquals(7, (int) Common.getTestAccount(repository, "alice").getLevel());
|
||||||
|
assertEquals(1, (int) Common.getTestAccount(repository, "bob").getLevel());
|
||||||
|
assertEquals(6, (int) Common.getTestAccount(repository, "chloe").getLevel());
|
||||||
|
assertEquals(7, (int) Common.getTestAccount(repository, "dilbert").getLevel());
|
||||||
|
|
||||||
|
// Now that dilbert has reached level 7, we can capture initial balances
|
||||||
|
Map<String, Map<Long, Long>> initialBalances = AccountUtils.getBalances(repository, Asset.QORT, Asset.LEGACY_QORA, Asset.QORT_FROM_QORA);
|
||||||
|
final long aliceInitialBalance = initialBalances.get("alice").get(Asset.QORT);
|
||||||
|
final long bobInitialBalance = initialBalances.get("bob").get(Asset.QORT);
|
||||||
|
final long chloeInitialBalance = initialBalances.get("chloe").get(Asset.QORT);
|
||||||
|
final long dilbertInitialBalance = initialBalances.get("dilbert").get(Asset.QORT);
|
||||||
|
|
||||||
|
// Mint a block
|
||||||
|
long blockReward = BlockUtils.getNextBlockReward(repository);
|
||||||
|
BlockMinter.mintTestingBlock(repository, mintingAndOnlineAccounts.toArray(new PrivateKeyAccount[0]));
|
||||||
|
|
||||||
|
// Ensure we are using the correct block reward value
|
||||||
|
assertEquals(100000000L, blockReward);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Alice, Chloe, and Dilbert are 'online'.
|
||||||
|
* Chloe is level 6; Dilbert is level 7.
|
||||||
|
* One founder online (Alice, who is also level 7).
|
||||||
|
* No legacy QORA holders.
|
||||||
|
*
|
||||||
|
* Level 7 and 8 is not yet activated, so its rewards are added to the level 5 and 6 share bin.
|
||||||
|
* There are no level 5 and 6 online.
|
||||||
|
* Chloe and Dilbert should receive equal shares of the 35% block reward for levels 5 to 8.
|
||||||
|
* Alice should receive the remainder (65%).
|
||||||
|
*/
|
||||||
|
|
||||||
|
final int level5To8SharePercent = 35_00; // 35% (combined 15% and 20%)
|
||||||
|
final long level5To8ShareAmount = (blockReward * level5To8SharePercent) / 100L / 100L;
|
||||||
|
final long expectedLevel5To8Reward = level5To8ShareAmount / 2; // The reward is split between Chloe and Dilbert
|
||||||
|
final long expectedFounderReward = blockReward - level5To8ShareAmount; // Alice should receive the remainder
|
||||||
|
|
||||||
|
// Validate the balances
|
||||||
|
AccountUtils.assertBalance(repository, "alice", Asset.QORT, aliceInitialBalance+expectedFounderReward);
|
||||||
|
AccountUtils.assertBalance(repository, "bob", Asset.QORT, bobInitialBalance); // Bob not online so his balance remains the same
|
||||||
|
AccountUtils.assertBalance(repository, "chloe", Asset.QORT, chloeInitialBalance+expectedLevel5To8Reward);
|
||||||
|
AccountUtils.assertBalance(repository, "dilbert", Asset.QORT, dilbertInitialBalance+expectedLevel5To8Reward);
|
||||||
|
|
||||||
|
// Ensure that the levels are as we expect
|
||||||
|
assertEquals(7, (int) Common.getTestAccount(repository, "alice").getLevel());
|
||||||
|
assertEquals(1, (int) Common.getTestAccount(repository, "bob").getLevel());
|
||||||
|
assertEquals(6, (int) Common.getTestAccount(repository, "chloe").getLevel());
|
||||||
|
assertEquals(7, (int) Common.getTestAccount(repository, "dilbert").getLevel());
|
||||||
|
|
||||||
|
// Capture pre-activation balances
|
||||||
|
Map<String, Map<Long, Long>> preActivationBalances = AccountUtils.getBalances(repository, Asset.QORT, Asset.LEGACY_QORA, Asset.QORT_FROM_QORA);
|
||||||
|
final long alicePreActivationBalance = preActivationBalances.get("alice").get(Asset.QORT);
|
||||||
|
final long bobPreActivationBalance = preActivationBalances.get("bob").get(Asset.QORT);
|
||||||
|
final long chloePreActivationBalance = preActivationBalances.get("chloe").get(Asset.QORT);
|
||||||
|
final long dilbertPreActivationBalance = preActivationBalances.get("dilbert").get(Asset.QORT);
|
||||||
|
|
||||||
|
// Mint another block
|
||||||
|
blockReward = BlockUtils.getNextBlockReward(repository);
|
||||||
|
BlockMinter.mintTestingBlock(repository, mintingAndOnlineAccounts.toArray(new PrivateKeyAccount[0]));
|
||||||
|
|
||||||
|
// Ensure that the levels are as we expect (chloe has now increased to level 7; level 7-8 is now activated)
|
||||||
|
assertEquals(7, (int) Common.getTestAccount(repository, "alice").getLevel());
|
||||||
|
assertEquals(1, (int) Common.getTestAccount(repository, "bob").getLevel());
|
||||||
|
assertEquals(7, (int) Common.getTestAccount(repository, "chloe").getLevel());
|
||||||
|
assertEquals(7, (int) Common.getTestAccount(repository, "dilbert").getLevel());
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Alice, Chloe, and Dilbert are 'online'.
|
||||||
|
* Chloe and Dilbert are level 7.
|
||||||
|
* One founder online (Alice, who is also level 7).
|
||||||
|
* No legacy QORA holders.
|
||||||
|
*
|
||||||
|
* Level 7 and 8 is now activated, so its rewards are paid out in the normal way.
|
||||||
|
* There are no level 5 and 6 online.
|
||||||
|
* Chloe and Dilbert should receive equal shares of the 20% block reward for levels 7 to 8.
|
||||||
|
* Alice should receive the remainder (80%).
|
||||||
|
*/
|
||||||
|
|
||||||
|
final int level7To8SharePercent = 20_00; // 20%
|
||||||
|
final long level7To8ShareAmount = (blockReward * level7To8SharePercent) / 100L / 100L;
|
||||||
|
final long expectedLevel7To8Reward = level7To8ShareAmount / 2; // The reward is split between Chloe and Dilbert
|
||||||
|
final long newExpectedFounderReward = blockReward - level7To8ShareAmount; // Alice should receive the remainder
|
||||||
|
|
||||||
|
// Validate the balances
|
||||||
|
AccountUtils.assertBalance(repository, "alice", Asset.QORT, alicePreActivationBalance+newExpectedFounderReward);
|
||||||
|
AccountUtils.assertBalance(repository, "bob", Asset.QORT, bobPreActivationBalance); // Bob not online so his balance remains the same
|
||||||
|
AccountUtils.assertBalance(repository, "chloe", Asset.QORT, chloePreActivationBalance+expectedLevel7To8Reward);
|
||||||
|
AccountUtils.assertBalance(repository, "dilbert", Asset.QORT, dilbertPreActivationBalance+expectedLevel7To8Reward);
|
||||||
|
|
||||||
|
|
||||||
|
// Orphan and ensure balances return to their pre-activation values
|
||||||
|
BlockUtils.orphanBlocks(repository, 1);
|
||||||
|
|
||||||
|
// Validate the balances
|
||||||
|
AccountUtils.assertBalance(repository, "alice", Asset.QORT, alicePreActivationBalance);
|
||||||
|
AccountUtils.assertBalance(repository, "bob", Asset.QORT, bobPreActivationBalance);
|
||||||
|
AccountUtils.assertBalance(repository, "chloe", Asset.QORT, chloePreActivationBalance);
|
||||||
|
AccountUtils.assertBalance(repository, "dilbert", Asset.QORT, dilbertPreActivationBalance);
|
||||||
|
|
||||||
|
|
||||||
|
// Orphan again and ensure balances return to their initial values
|
||||||
|
BlockUtils.orphanBlocks(repository, 1);
|
||||||
|
|
||||||
|
// Validate the balances
|
||||||
|
AccountUtils.assertBalance(repository, "alice", Asset.QORT, aliceInitialBalance);
|
||||||
|
AccountUtils.assertBalance(repository, "bob", Asset.QORT, bobInitialBalance);
|
||||||
|
AccountUtils.assertBalance(repository, "chloe", Asset.QORT, chloeInitialBalance);
|
||||||
|
AccountUtils.assertBalance(repository, "dilbert", Asset.QORT, dilbertInitialBalance);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,14 +20,16 @@
|
|||||||
{ "height": 21, "reward": 1 }
|
{ "height": 21, "reward": 1 }
|
||||||
],
|
],
|
||||||
"sharesByLevel": [
|
"sharesByLevel": [
|
||||||
{ "levels": [ 1, 2 ], "share": 0.05 },
|
{ "id": 1, "levels": [ 1, 2 ], "share": 0.05 },
|
||||||
{ "levels": [ 3, 4 ], "share": 0.10 },
|
{ "id": 2, "levels": [ 3, 4 ], "share": 0.10 },
|
||||||
{ "levels": [ 5, 6 ], "share": 0.15 },
|
{ "id": 3, "levels": [ 5, 6 ], "share": 0.15 },
|
||||||
{ "levels": [ 7, 8 ], "share": 0.20 },
|
{ "id": 4, "levels": [ 7, 8 ], "share": 0.20 },
|
||||||
{ "levels": [ 9, 10 ], "share": 0.25 }
|
{ "id": 5, "levels": [ 9, 10 ], "share": 0.25 }
|
||||||
],
|
],
|
||||||
"qoraHoldersShare": 0.20,
|
"qoraHoldersShare": 0.20,
|
||||||
"qoraPerQortReward": 250,
|
"qoraPerQortReward": 250,
|
||||||
|
"minAccountsToActivateShareBin": 30,
|
||||||
|
"shareBinActivationMinLevel": 7,
|
||||||
"blocksNeededByLevel": [ 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 ],
|
"blocksNeededByLevel": [ 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 ],
|
||||||
"blockTimingsByHeight": [
|
"blockTimingsByHeight": [
|
||||||
{ "height": 1, "target": 60000, "deviation": 30000, "power": 0.2 },
|
{ "height": 1, "target": 60000, "deviation": 30000, "power": 0.2 },
|
||||||
@ -52,6 +54,7 @@
|
|||||||
"atFindNextTransactionFix": 0,
|
"atFindNextTransactionFix": 0,
|
||||||
"newBlockSigHeight": 999999,
|
"newBlockSigHeight": 999999,
|
||||||
"shareBinFix": 999999,
|
"shareBinFix": 999999,
|
||||||
|
"rewardShareLimitTimestamp": 9999999999999,
|
||||||
"calcChainWeightTimestamp": 0,
|
"calcChainWeightTimestamp": 0,
|
||||||
"transactionV5Timestamp": 0,
|
"transactionV5Timestamp": 0,
|
||||||
"transactionV6Timestamp": 9999999999999,
|
"transactionV6Timestamp": 9999999999999,
|
||||||
|
@ -10,7 +10,11 @@
|
|||||||
],
|
],
|
||||||
"requireGroupForApproval": false,
|
"requireGroupForApproval": false,
|
||||||
"minAccountLevelToRewardShare": 5,
|
"minAccountLevelToRewardShare": 5,
|
||||||
"maxRewardSharesPerMintingAccount": 20,
|
"maxRewardSharesPerFounderMintingAccount": 6,
|
||||||
|
"maxRewardSharesByTimestamp": [
|
||||||
|
{ "timestamp": 0, "maxShares": 6 },
|
||||||
|
{ "timestamp": 9999999999999, "maxShares": 3 }
|
||||||
|
],
|
||||||
"founderEffectiveMintingLevel": 10,
|
"founderEffectiveMintingLevel": 10,
|
||||||
"onlineAccountSignaturesMinLifetime": 3600000,
|
"onlineAccountSignaturesMinLifetime": 3600000,
|
||||||
"onlineAccountSignaturesMaxLifetime": 86400000,
|
"onlineAccountSignaturesMaxLifetime": 86400000,
|
||||||
@ -20,14 +24,16 @@
|
|||||||
{ "height": 21, "reward": 1 }
|
{ "height": 21, "reward": 1 }
|
||||||
],
|
],
|
||||||
"sharesByLevel": [
|
"sharesByLevel": [
|
||||||
{ "levels": [ 1, 2 ], "share": 0.05 },
|
{ "id": 1, "levels": [ 1, 2 ], "share": 0.05 },
|
||||||
{ "levels": [ 3, 4 ], "share": 0.10 },
|
{ "id": 2, "levels": [ 3, 4 ], "share": 0.10 },
|
||||||
{ "levels": [ 5, 6 ], "share": 0.15 },
|
{ "id": 3, "levels": [ 5, 6 ], "share": 0.15 },
|
||||||
{ "levels": [ 7, 8 ], "share": 0.20 },
|
{ "id": 4, "levels": [ 7, 8 ], "share": 0.20 },
|
||||||
{ "levels": [ 9, 10 ], "share": 0.25 }
|
{ "id": 5, "levels": [ 9, 10 ], "share": 0.25 }
|
||||||
],
|
],
|
||||||
"qoraHoldersShare": 0.20,
|
"qoraHoldersShare": 0.20,
|
||||||
"qoraPerQortReward": 250,
|
"qoraPerQortReward": 250,
|
||||||
|
"minAccountsToActivateShareBin": 30,
|
||||||
|
"shareBinActivationMinLevel": 7,
|
||||||
"blocksNeededByLevel": [ 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 ],
|
"blocksNeededByLevel": [ 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 ],
|
||||||
"blockTimingsByHeight": [
|
"blockTimingsByHeight": [
|
||||||
{ "height": 1, "target": 60000, "deviation": 30000, "power": 0.2 }
|
{ "height": 1, "target": 60000, "deviation": 30000, "power": 0.2 }
|
||||||
@ -51,6 +57,7 @@
|
|||||||
"atFindNextTransactionFix": 0,
|
"atFindNextTransactionFix": 0,
|
||||||
"newBlockSigHeight": 999999,
|
"newBlockSigHeight": 999999,
|
||||||
"shareBinFix": 999999,
|
"shareBinFix": 999999,
|
||||||
|
"rewardShareLimitTimestamp": 9999999999999,
|
||||||
"calcChainWeightTimestamp": 0,
|
"calcChainWeightTimestamp": 0,
|
||||||
"transactionV5Timestamp": 0,
|
"transactionV5Timestamp": 0,
|
||||||
"transactionV6Timestamp": 0,
|
"transactionV6Timestamp": 0,
|
||||||
|
@ -10,7 +10,11 @@
|
|||||||
],
|
],
|
||||||
"requireGroupForApproval": false,
|
"requireGroupForApproval": false,
|
||||||
"minAccountLevelToRewardShare": 5,
|
"minAccountLevelToRewardShare": 5,
|
||||||
"maxRewardSharesPerMintingAccount": 20,
|
"maxRewardSharesPerFounderMintingAccount": 6,
|
||||||
|
"maxRewardSharesByTimestamp": [
|
||||||
|
{ "timestamp": 0, "maxShares": 6 },
|
||||||
|
{ "timestamp": 9999999999999, "maxShares": 3 }
|
||||||
|
],
|
||||||
"founderEffectiveMintingLevel": 10,
|
"founderEffectiveMintingLevel": 10,
|
||||||
"onlineAccountSignaturesMinLifetime": 3600000,
|
"onlineAccountSignaturesMinLifetime": 3600000,
|
||||||
"onlineAccountSignaturesMaxLifetime": 86400000,
|
"onlineAccountSignaturesMaxLifetime": 86400000,
|
||||||
@ -21,14 +25,16 @@
|
|||||||
{ "height": 21, "reward": 1 }
|
{ "height": 21, "reward": 1 }
|
||||||
],
|
],
|
||||||
"sharesByLevel": [
|
"sharesByLevel": [
|
||||||
{ "levels": [ 1, 2 ], "share": 0.05 },
|
{ "id": 1, "levels": [ 1, 2 ], "share": 0.05 },
|
||||||
{ "levels": [ 3, 4 ], "share": 0.10 },
|
{ "id": 2, "levels": [ 3, 4 ], "share": 0.10 },
|
||||||
{ "levels": [ 5, 6 ], "share": 0.15 },
|
{ "id": 3, "levels": [ 5, 6 ], "share": 0.15 },
|
||||||
{ "levels": [ 7, 8 ], "share": 0.20 },
|
{ "id": 4, "levels": [ 7, 8 ], "share": 0.20 },
|
||||||
{ "levels": [ 9, 10 ], "share": 0.25 }
|
{ "id": 5, "levels": [ 9, 10 ], "share": 0.25 }
|
||||||
],
|
],
|
||||||
"qoraHoldersShare": 0.20,
|
"qoraHoldersShare": 0.20,
|
||||||
"qoraPerQortReward": 250,
|
"qoraPerQortReward": 250,
|
||||||
|
"minAccountsToActivateShareBin": 30,
|
||||||
|
"shareBinActivationMinLevel": 7,
|
||||||
"blocksNeededByLevel": [ 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 ],
|
"blocksNeededByLevel": [ 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 ],
|
||||||
"blockTimingsByHeight": [
|
"blockTimingsByHeight": [
|
||||||
{ "height": 1, "target": 60000, "deviation": 30000, "power": 0.2 }
|
{ "height": 1, "target": 60000, "deviation": 30000, "power": 0.2 }
|
||||||
@ -52,6 +58,7 @@
|
|||||||
"atFindNextTransactionFix": 0,
|
"atFindNextTransactionFix": 0,
|
||||||
"newBlockSigHeight": 999999,
|
"newBlockSigHeight": 999999,
|
||||||
"shareBinFix": 999999,
|
"shareBinFix": 999999,
|
||||||
|
"rewardShareLimitTimestamp": 9999999999999,
|
||||||
"calcChainWeightTimestamp": 0,
|
"calcChainWeightTimestamp": 0,
|
||||||
"transactionV5Timestamp": 0,
|
"transactionV5Timestamp": 0,
|
||||||
"transactionV6Timestamp": 0,
|
"transactionV6Timestamp": 0,
|
||||||
|
@ -10,7 +10,11 @@
|
|||||||
],
|
],
|
||||||
"requireGroupForApproval": false,
|
"requireGroupForApproval": false,
|
||||||
"minAccountLevelToRewardShare": 5,
|
"minAccountLevelToRewardShare": 5,
|
||||||
"maxRewardSharesPerMintingAccount": 20,
|
"maxRewardSharesPerFounderMintingAccount": 6,
|
||||||
|
"maxRewardSharesByTimestamp": [
|
||||||
|
{ "timestamp": 0, "maxShares": 6 },
|
||||||
|
{ "timestamp": 9999999999999, "maxShares": 3 }
|
||||||
|
],
|
||||||
"founderEffectiveMintingLevel": 10,
|
"founderEffectiveMintingLevel": 10,
|
||||||
"onlineAccountSignaturesMinLifetime": 3600000,
|
"onlineAccountSignaturesMinLifetime": 3600000,
|
||||||
"onlineAccountSignaturesMaxLifetime": 86400000,
|
"onlineAccountSignaturesMaxLifetime": 86400000,
|
||||||
@ -21,14 +25,16 @@
|
|||||||
{ "height": 21, "reward": 1 }
|
{ "height": 21, "reward": 1 }
|
||||||
],
|
],
|
||||||
"sharesByLevel": [
|
"sharesByLevel": [
|
||||||
{ "levels": [ 1, 2 ], "share": 0.05 },
|
{ "id": 1, "levels": [ 1, 2 ], "share": 0.05 },
|
||||||
{ "levels": [ 3, 4 ], "share": 0.10 },
|
{ "id": 2, "levels": [ 3, 4 ], "share": 0.10 },
|
||||||
{ "levels": [ 5, 6 ], "share": 0.15 },
|
{ "id": 3, "levels": [ 5, 6 ], "share": 0.15 },
|
||||||
{ "levels": [ 7, 8 ], "share": 0.20 },
|
{ "id": 4, "levels": [ 7, 8 ], "share": 0.20 },
|
||||||
{ "levels": [ 9, 10 ], "share": 0.25 }
|
{ "id": 5, "levels": [ 9, 10 ], "share": 0.25 }
|
||||||
],
|
],
|
||||||
"qoraHoldersShare": 0.20,
|
"qoraHoldersShare": 0.20,
|
||||||
"qoraPerQortReward": 250,
|
"qoraPerQortReward": 250,
|
||||||
|
"minAccountsToActivateShareBin": 30,
|
||||||
|
"shareBinActivationMinLevel": 7,
|
||||||
"blocksNeededByLevel": [ 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 ],
|
"blocksNeededByLevel": [ 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 ],
|
||||||
"blockTimingsByHeight": [
|
"blockTimingsByHeight": [
|
||||||
{ "height": 1, "target": 60000, "deviation": 30000, "power": 0.2 }
|
{ "height": 1, "target": 60000, "deviation": 30000, "power": 0.2 }
|
||||||
@ -52,6 +58,7 @@
|
|||||||
"atFindNextTransactionFix": 0,
|
"atFindNextTransactionFix": 0,
|
||||||
"newBlockSigHeight": 999999,
|
"newBlockSigHeight": 999999,
|
||||||
"shareBinFix": 999999,
|
"shareBinFix": 999999,
|
||||||
|
"rewardShareLimitTimestamp": 9999999999999,
|
||||||
"calcChainWeightTimestamp": 0,
|
"calcChainWeightTimestamp": 0,
|
||||||
"transactionV5Timestamp": 0,
|
"transactionV5Timestamp": 0,
|
||||||
"transactionV6Timestamp": 0,
|
"transactionV6Timestamp": 0,
|
||||||
|
@ -10,7 +10,11 @@
|
|||||||
],
|
],
|
||||||
"requireGroupForApproval": false,
|
"requireGroupForApproval": false,
|
||||||
"minAccountLevelToRewardShare": 5,
|
"minAccountLevelToRewardShare": 5,
|
||||||
"maxRewardSharesPerMintingAccount": 20,
|
"maxRewardSharesPerFounderMintingAccount": 6,
|
||||||
|
"maxRewardSharesByTimestamp": [
|
||||||
|
{ "timestamp": 0, "maxShares": 6 },
|
||||||
|
{ "timestamp": 9999999999999, "maxShares": 3 }
|
||||||
|
],
|
||||||
"founderEffectiveMintingLevel": 10,
|
"founderEffectiveMintingLevel": 10,
|
||||||
"onlineAccountSignaturesMinLifetime": 3600000,
|
"onlineAccountSignaturesMinLifetime": 3600000,
|
||||||
"onlineAccountSignaturesMaxLifetime": 86400000,
|
"onlineAccountSignaturesMaxLifetime": 86400000,
|
||||||
@ -21,14 +25,16 @@
|
|||||||
{ "height": 21, "reward": 1 }
|
{ "height": 21, "reward": 1 }
|
||||||
],
|
],
|
||||||
"sharesByLevel": [
|
"sharesByLevel": [
|
||||||
{ "levels": [ 1, 2 ], "share": 0.05 },
|
{ "id": 1, "levels": [ 1, 2 ], "share": 0.05 },
|
||||||
{ "levels": [ 3, 4 ], "share": 0.10 },
|
{ "id": 2, "levels": [ 3, 4 ], "share": 0.10 },
|
||||||
{ "levels": [ 5, 6 ], "share": 0.15 },
|
{ "id": 3, "levels": [ 5, 6 ], "share": 0.15 },
|
||||||
{ "levels": [ 7, 8 ], "share": 0.20 },
|
{ "id": 4, "levels": [ 7, 8 ], "share": 0.20 },
|
||||||
{ "levels": [ 9, 10 ], "share": 0.25 }
|
{ "id": 5, "levels": [ 9, 10 ], "share": 0.25 }
|
||||||
],
|
],
|
||||||
"qoraHoldersShare": 0.20,
|
"qoraHoldersShare": 0.20,
|
||||||
"qoraPerQortReward": 250,
|
"qoraPerQortReward": 250,
|
||||||
|
"minAccountsToActivateShareBin": 30,
|
||||||
|
"shareBinActivationMinLevel": 7,
|
||||||
"blocksNeededByLevel": [ 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 ],
|
"blocksNeededByLevel": [ 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 ],
|
||||||
"blockTimingsByHeight": [
|
"blockTimingsByHeight": [
|
||||||
{ "height": 1, "target": 60000, "deviation": 30000, "power": 0.2 }
|
{ "height": 1, "target": 60000, "deviation": 30000, "power": 0.2 }
|
||||||
@ -52,6 +58,7 @@
|
|||||||
"atFindNextTransactionFix": 0,
|
"atFindNextTransactionFix": 0,
|
||||||
"newBlockSigHeight": 999999,
|
"newBlockSigHeight": 999999,
|
||||||
"shareBinFix": 999999,
|
"shareBinFix": 999999,
|
||||||
|
"rewardShareLimitTimestamp": 9999999999999,
|
||||||
"calcChainWeightTimestamp": 0,
|
"calcChainWeightTimestamp": 0,
|
||||||
"transactionV5Timestamp": 0,
|
"transactionV5Timestamp": 0,
|
||||||
"transactionV6Timestamp": 0,
|
"transactionV6Timestamp": 0,
|
||||||
|
@ -10,7 +10,11 @@
|
|||||||
],
|
],
|
||||||
"requireGroupForApproval": false,
|
"requireGroupForApproval": false,
|
||||||
"minAccountLevelToRewardShare": 5,
|
"minAccountLevelToRewardShare": 5,
|
||||||
"maxRewardSharesPerMintingAccount": 20,
|
"maxRewardSharesPerFounderMintingAccount": 6,
|
||||||
|
"maxRewardSharesByTimestamp": [
|
||||||
|
{ "timestamp": 0, "maxShares": 6 },
|
||||||
|
{ "timestamp": 9999999999999, "maxShares": 3 }
|
||||||
|
],
|
||||||
"founderEffectiveMintingLevel": 10,
|
"founderEffectiveMintingLevel": 10,
|
||||||
"onlineAccountSignaturesMinLifetime": 3600000,
|
"onlineAccountSignaturesMinLifetime": 3600000,
|
||||||
"onlineAccountSignaturesMaxLifetime": 86400000,
|
"onlineAccountSignaturesMaxLifetime": 86400000,
|
||||||
@ -21,14 +25,16 @@
|
|||||||
{ "height": 21, "reward": 1 }
|
{ "height": 21, "reward": 1 }
|
||||||
],
|
],
|
||||||
"sharesByLevel": [
|
"sharesByLevel": [
|
||||||
{ "levels": [ 1, 2 ], "share": 0.05 },
|
{ "id": 1, "levels": [ 1, 2 ], "share": 0.05 },
|
||||||
{ "levels": [ 3, 4 ], "share": 0.10 },
|
{ "id": 2, "levels": [ 3, 4 ], "share": 0.10 },
|
||||||
{ "levels": [ 5, 6 ], "share": 0.15 },
|
{ "id": 3, "levels": [ 5, 6 ], "share": 0.15 },
|
||||||
{ "levels": [ 7, 8 ], "share": 0.20 },
|
{ "id": 4, "levels": [ 7, 8 ], "share": 0.20 },
|
||||||
{ "levels": [ 9, 10 ], "share": 0.25 }
|
{ "id": 5, "levels": [ 9, 10 ], "share": 0.25 }
|
||||||
],
|
],
|
||||||
"qoraHoldersShare": 0.20,
|
"qoraHoldersShare": 0.20,
|
||||||
"qoraPerQortReward": 250,
|
"qoraPerQortReward": 250,
|
||||||
|
"minAccountsToActivateShareBin": 30,
|
||||||
|
"shareBinActivationMinLevel": 7,
|
||||||
"blocksNeededByLevel": [ 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 ],
|
"blocksNeededByLevel": [ 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 ],
|
||||||
"blockTimingsByHeight": [
|
"blockTimingsByHeight": [
|
||||||
{ "height": 1, "target": 60000, "deviation": 30000, "power": 0.2 }
|
{ "height": 1, "target": 60000, "deviation": 30000, "power": 0.2 }
|
||||||
@ -52,6 +58,7 @@
|
|||||||
"atFindNextTransactionFix": 0,
|
"atFindNextTransactionFix": 0,
|
||||||
"newBlockSigHeight": 999999,
|
"newBlockSigHeight": 999999,
|
||||||
"shareBinFix": 999999,
|
"shareBinFix": 999999,
|
||||||
|
"rewardShareLimitTimestamp": 9999999999999,
|
||||||
"calcChainWeightTimestamp": 0,
|
"calcChainWeightTimestamp": 0,
|
||||||
"transactionV5Timestamp": 0,
|
"transactionV5Timestamp": 0,
|
||||||
"transactionV6Timestamp": 0,
|
"transactionV6Timestamp": 0,
|
||||||
|
@ -10,7 +10,11 @@
|
|||||||
],
|
],
|
||||||
"requireGroupForApproval": false,
|
"requireGroupForApproval": false,
|
||||||
"minAccountLevelToRewardShare": 5,
|
"minAccountLevelToRewardShare": 5,
|
||||||
"maxRewardSharesPerMintingAccount": 20,
|
"maxRewardSharesPerFounderMintingAccount": 6,
|
||||||
|
"maxRewardSharesByTimestamp": [
|
||||||
|
{ "timestamp": 0, "maxShares": 6 },
|
||||||
|
{ "timestamp": 9999999999999, "maxShares": 3 }
|
||||||
|
],
|
||||||
"founderEffectiveMintingLevel": 10,
|
"founderEffectiveMintingLevel": 10,
|
||||||
"onlineAccountSignaturesMinLifetime": 3600000,
|
"onlineAccountSignaturesMinLifetime": 3600000,
|
||||||
"onlineAccountSignaturesMaxLifetime": 86400000,
|
"onlineAccountSignaturesMaxLifetime": 86400000,
|
||||||
@ -21,14 +25,16 @@
|
|||||||
{ "height": 21, "reward": 1 }
|
{ "height": 21, "reward": 1 }
|
||||||
],
|
],
|
||||||
"sharesByLevel": [
|
"sharesByLevel": [
|
||||||
{ "levels": [ 1, 2 ], "share": 0.05 },
|
{ "id": 1, "levels": [ 1, 2 ], "share": 0.05 },
|
||||||
{ "levels": [ 3, 4 ], "share": 0.10 },
|
{ "id": 2, "levels": [ 3, 4 ], "share": 0.10 },
|
||||||
{ "levels": [ 5, 6 ], "share": 0.15 },
|
{ "id": 3, "levels": [ 5, 6 ], "share": 0.15 },
|
||||||
{ "levels": [ 7, 8 ], "share": 0.20 },
|
{ "id": 4, "levels": [ 7, 8 ], "share": 0.20 },
|
||||||
{ "levels": [ 9, 10 ], "share": 0.25 }
|
{ "id": 5, "levels": [ 9, 10 ], "share": 0.25 }
|
||||||
],
|
],
|
||||||
"qoraHoldersShare": 0.20,
|
"qoraHoldersShare": 0.20,
|
||||||
"qoraPerQortReward": 250,
|
"qoraPerQortReward": 250,
|
||||||
|
"minAccountsToActivateShareBin": 30,
|
||||||
|
"shareBinActivationMinLevel": 7,
|
||||||
"blocksNeededByLevel": [ 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 ],
|
"blocksNeededByLevel": [ 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 ],
|
||||||
"blockTimingsByHeight": [
|
"blockTimingsByHeight": [
|
||||||
{ "height": 1, "target": 60000, "deviation": 30000, "power": 0.2 }
|
{ "height": 1, "target": 60000, "deviation": 30000, "power": 0.2 }
|
||||||
@ -52,6 +58,7 @@
|
|||||||
"atFindNextTransactionFix": 0,
|
"atFindNextTransactionFix": 0,
|
||||||
"newBlockSigHeight": 999999,
|
"newBlockSigHeight": 999999,
|
||||||
"shareBinFix": 999999,
|
"shareBinFix": 999999,
|
||||||
|
"rewardShareLimitTimestamp": 9999999999999,
|
||||||
"calcChainWeightTimestamp": 0,
|
"calcChainWeightTimestamp": 0,
|
||||||
"transactionV5Timestamp": 0,
|
"transactionV5Timestamp": 0,
|
||||||
"transactionV6Timestamp": 0,
|
"transactionV6Timestamp": 0,
|
||||||
|
@ -10,7 +10,11 @@
|
|||||||
],
|
],
|
||||||
"requireGroupForApproval": false,
|
"requireGroupForApproval": false,
|
||||||
"minAccountLevelToRewardShare": 5,
|
"minAccountLevelToRewardShare": 5,
|
||||||
"maxRewardSharesPerMintingAccount": 20,
|
"maxRewardSharesPerFounderMintingAccount": 6,
|
||||||
|
"maxRewardSharesByTimestamp": [
|
||||||
|
{ "timestamp": 0, "maxShares": 20 },
|
||||||
|
{ "timestamp": 9999999999999, "maxShares": 3 }
|
||||||
|
],
|
||||||
"founderEffectiveMintingLevel": 10,
|
"founderEffectiveMintingLevel": 10,
|
||||||
"onlineAccountSignaturesMinLifetime": 3600000,
|
"onlineAccountSignaturesMinLifetime": 3600000,
|
||||||
"onlineAccountSignaturesMaxLifetime": 86400000,
|
"onlineAccountSignaturesMaxLifetime": 86400000,
|
||||||
@ -21,14 +25,16 @@
|
|||||||
{ "height": 21, "reward": 1 }
|
{ "height": 21, "reward": 1 }
|
||||||
],
|
],
|
||||||
"sharesByLevel": [
|
"sharesByLevel": [
|
||||||
{ "levels": [ 1, 2 ], "share": 0.05 },
|
{ "id": 1, "levels": [ 1, 2 ], "share": 0.05 },
|
||||||
{ "levels": [ 3, 4 ], "share": 0.10 },
|
{ "id": 2, "levels": [ 3, 4 ], "share": 0.10 },
|
||||||
{ "levels": [ 5, 6 ], "share": 0.15 },
|
{ "id": 3, "levels": [ 5, 6 ], "share": 0.15 },
|
||||||
{ "levels": [ 7, 8 ], "share": 0.20 },
|
{ "id": 4, "levels": [ 7, 8 ], "share": 0.20 },
|
||||||
{ "levels": [ 9, 10 ], "share": 0.25 }
|
{ "id": 5, "levels": [ 9, 10 ], "share": 0.25 }
|
||||||
],
|
],
|
||||||
"qoraHoldersShare": 0.20,
|
"qoraHoldersShare": 0.20,
|
||||||
"qoraPerQortReward": 250,
|
"qoraPerQortReward": 250,
|
||||||
|
"minAccountsToActivateShareBin": 1,
|
||||||
|
"shareBinActivationMinLevel": 7,
|
||||||
"blocksNeededByLevel": [ 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 ],
|
"blocksNeededByLevel": [ 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 ],
|
||||||
"blockTimingsByHeight": [
|
"blockTimingsByHeight": [
|
||||||
{ "height": 1, "target": 60000, "deviation": 30000, "power": 0.2 }
|
{ "height": 1, "target": 60000, "deviation": 30000, "power": 0.2 }
|
||||||
@ -52,6 +58,7 @@
|
|||||||
"atFindNextTransactionFix": 0,
|
"atFindNextTransactionFix": 0,
|
||||||
"newBlockSigHeight": 999999,
|
"newBlockSigHeight": 999999,
|
||||||
"shareBinFix": 6,
|
"shareBinFix": 6,
|
||||||
|
"rewardShareLimitTimestamp": 9999999999999,
|
||||||
"calcChainWeightTimestamp": 0,
|
"calcChainWeightTimestamp": 0,
|
||||||
"transactionV5Timestamp": 0,
|
"transactionV5Timestamp": 0,
|
||||||
"transactionV6Timestamp": 0,
|
"transactionV6Timestamp": 0,
|
||||||
|
@ -10,7 +10,11 @@
|
|||||||
],
|
],
|
||||||
"requireGroupForApproval": false,
|
"requireGroupForApproval": false,
|
||||||
"minAccountLevelToRewardShare": 5,
|
"minAccountLevelToRewardShare": 5,
|
||||||
"maxRewardSharesPerMintingAccount": 20,
|
"maxRewardSharesPerFounderMintingAccount": 6,
|
||||||
|
"maxRewardSharesByTimestamp": [
|
||||||
|
{ "timestamp": 0, "maxShares": 20 },
|
||||||
|
{ "timestamp": 9999999999999, "maxShares": 3 }
|
||||||
|
],
|
||||||
"founderEffectiveMintingLevel": 10,
|
"founderEffectiveMintingLevel": 10,
|
||||||
"onlineAccountSignaturesMinLifetime": 3600000,
|
"onlineAccountSignaturesMinLifetime": 3600000,
|
||||||
"onlineAccountSignaturesMaxLifetime": 86400000,
|
"onlineAccountSignaturesMaxLifetime": 86400000,
|
||||||
@ -21,14 +25,16 @@
|
|||||||
{ "height": 21, "reward": 1 }
|
{ "height": 21, "reward": 1 }
|
||||||
],
|
],
|
||||||
"sharesByLevel": [
|
"sharesByLevel": [
|
||||||
{ "levels": [ 1, 2 ], "share": 0.05 },
|
{ "id": 1, "levels": [ 1, 2 ], "share": 0.05 },
|
||||||
{ "levels": [ 3, 4 ], "share": 0.10 },
|
{ "id": 2, "levels": [ 3, 4 ], "share": 0.10 },
|
||||||
{ "levels": [ 5, 6 ], "share": 0.15 },
|
{ "id": 3, "levels": [ 5, 6 ], "share": 0.15 },
|
||||||
{ "levels": [ 7, 8 ], "share": 0.20 },
|
{ "id": 4, "levels": [ 7, 8 ], "share": 0.20 },
|
||||||
{ "levels": [ 9, 10 ], "share": 0.25 }
|
{ "id": 5, "levels": [ 9, 10 ], "share": 0.25 }
|
||||||
],
|
],
|
||||||
"qoraHoldersShare": 0.20,
|
"qoraHoldersShare": 0.20,
|
||||||
"qoraPerQortReward": 250,
|
"qoraPerQortReward": 250,
|
||||||
|
"minAccountsToActivateShareBin": 30,
|
||||||
|
"shareBinActivationMinLevel": 7,
|
||||||
"blocksNeededByLevel": [ 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 ],
|
"blocksNeededByLevel": [ 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 ],
|
||||||
"blockTimingsByHeight": [
|
"blockTimingsByHeight": [
|
||||||
{ "height": 1, "target": 60000, "deviation": 30000, "power": 0.2 }
|
{ "height": 1, "target": 60000, "deviation": 30000, "power": 0.2 }
|
||||||
@ -52,6 +58,7 @@
|
|||||||
"atFindNextTransactionFix": 0,
|
"atFindNextTransactionFix": 0,
|
||||||
"newBlockSigHeight": 999999,
|
"newBlockSigHeight": 999999,
|
||||||
"shareBinFix": 999999,
|
"shareBinFix": 999999,
|
||||||
|
"rewardShareLimitTimestamp": 9999999999999,
|
||||||
"calcChainWeightTimestamp": 0,
|
"calcChainWeightTimestamp": 0,
|
||||||
"transactionV5Timestamp": 0,
|
"transactionV5Timestamp": 0,
|
||||||
"transactionV6Timestamp": 0,
|
"transactionV6Timestamp": 0,
|
||||||
|
93
src/test/resources/test-chain-v2-reward-shares.json
Normal file
93
src/test/resources/test-chain-v2-reward-shares.json
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
{
|
||||||
|
"isTestChain": true,
|
||||||
|
"blockTimestampMargin": 500,
|
||||||
|
"transactionExpiryPeriod": 86400000,
|
||||||
|
"maxBlockSize": 2097152,
|
||||||
|
"maxBytesPerUnitFee": 1024,
|
||||||
|
"unitFee": "0.1",
|
||||||
|
"nameRegistrationUnitFees": [
|
||||||
|
{ "timestamp": 1645372800000, "fee": "5" }
|
||||||
|
],
|
||||||
|
"requireGroupForApproval": false,
|
||||||
|
"minAccountLevelToRewardShare": 5,
|
||||||
|
"maxRewardSharesPerFounderMintingAccount": 6,
|
||||||
|
"maxRewardSharesByTimestamp": [
|
||||||
|
{ "timestamp": 0, "maxShares": 6 },
|
||||||
|
{ "timestamp": 1655460000000, "maxShares": 3 }
|
||||||
|
],
|
||||||
|
"founderEffectiveMintingLevel": 10,
|
||||||
|
"onlineAccountSignaturesMinLifetime": 3600000,
|
||||||
|
"onlineAccountSignaturesMaxLifetime": 86400000,
|
||||||
|
"rewardsByHeight": [
|
||||||
|
{ "height": 1, "reward": 100 },
|
||||||
|
{ "height": 11, "reward": 10 },
|
||||||
|
{ "height": 21, "reward": 1 }
|
||||||
|
],
|
||||||
|
"sharesByLevel": [
|
||||||
|
{ "id": 1, "levels": [ 1, 2 ], "share": 0.05 },
|
||||||
|
{ "id": 2, "levels": [ 3, 4 ], "share": 0.10 },
|
||||||
|
{ "id": 3, "levels": [ 5, 6 ], "share": 0.15 },
|
||||||
|
{ "id": 4, "levels": [ 7, 8 ], "share": 0.20 },
|
||||||
|
{ "id": 5, "levels": [ 9, 10 ], "share": 0.25 }
|
||||||
|
],
|
||||||
|
"qoraHoldersShare": 0.20,
|
||||||
|
"qoraPerQortReward": 250,
|
||||||
|
"minAccountsToActivateShareBin": 30,
|
||||||
|
"shareBinActivationMinLevel": 7,
|
||||||
|
"blocksNeededByLevel": [ 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 ],
|
||||||
|
"blockTimingsByHeight": [
|
||||||
|
{ "height": 1, "target": 60000, "deviation": 30000, "power": 0.2 }
|
||||||
|
],
|
||||||
|
"ciyamAtSettings": {
|
||||||
|
"feePerStep": "0.0001",
|
||||||
|
"maxStepsPerRound": 500,
|
||||||
|
"stepsPerFunctionCall": 10,
|
||||||
|
"minutesPerBlock": 1
|
||||||
|
},
|
||||||
|
"featureTriggers": {
|
||||||
|
"messageHeight": 0,
|
||||||
|
"atHeight": 0,
|
||||||
|
"assetsTimestamp": 0,
|
||||||
|
"votingTimestamp": 0,
|
||||||
|
"arbitraryTimestamp": 0,
|
||||||
|
"powfixTimestamp": 0,
|
||||||
|
"qortalTimestamp": 0,
|
||||||
|
"newAssetPricingTimestamp": 0,
|
||||||
|
"groupApprovalTimestamp": 0,
|
||||||
|
"atFindNextTransactionFix": 0,
|
||||||
|
"newBlockSigHeight": 999999,
|
||||||
|
"shareBinFix": 999999,
|
||||||
|
"rewardShareLimitTimestamp": 0,
|
||||||
|
"calcChainWeightTimestamp": 0,
|
||||||
|
"newConsensusTimestamp": 0,
|
||||||
|
"transactionV5Timestamp": 0,
|
||||||
|
"transactionV6Timestamp": 0,
|
||||||
|
"disableReferenceTimestamp": 9999999999999,
|
||||||
|
"aggregateSignatureTimestamp": 0
|
||||||
|
},
|
||||||
|
"genesisInfo": {
|
||||||
|
"version": 4,
|
||||||
|
"timestamp": 0,
|
||||||
|
"transactions": [
|
||||||
|
{ "type": "ISSUE_ASSET", "assetName": "QORT", "description": "QORT native coin", "data": "", "quantity": 0, "isDivisible": true, "fee": 0 },
|
||||||
|
{ "type": "ISSUE_ASSET", "assetName": "Legacy-QORA", "description": "Representative legacy QORA", "quantity": 0, "isDivisible": true, "data": "{}", "isUnspendable": true },
|
||||||
|
{ "type": "ISSUE_ASSET", "assetName": "QORT-from-QORA", "description": "QORT gained from holding legacy QORA", "quantity": 0, "isDivisible": true, "data": "{}", "isUnspendable": true },
|
||||||
|
|
||||||
|
{ "type": "GENESIS", "recipient": "QgV4s3xnzLhVBEJxcYui4u4q11yhUHsd9v", "amount": "1000000000" },
|
||||||
|
{ "type": "GENESIS", "recipient": "QixPbJUwsaHsVEofJdozU9zgVqkK6aYhrK", "amount": "1000000" },
|
||||||
|
{ "type": "GENESIS", "recipient": "QaUpHNhT3Ygx6avRiKobuLdusppR5biXjL", "amount": "1000000" },
|
||||||
|
{ "type": "GENESIS", "recipient": "Qci5m9k4rcwe4ruKrZZQKka4FzUUMut3er", "amount": "1000000" },
|
||||||
|
|
||||||
|
{ "type": "CREATE_GROUP", "creatorPublicKey": "2tiMr5LTpaWCgbRvkPK8TFd7k63DyHJMMFFsz9uBf1ZP", "groupName": "dev-group", "description": "developer group", "isOpen": false, "approvalThreshold": "PCT100", "minimumBlockDelay": 0, "maximumBlockDelay": 1440 },
|
||||||
|
|
||||||
|
{ "type": "ISSUE_ASSET", "issuerPublicKey": "2tiMr5LTpaWCgbRvkPK8TFd7k63DyHJMMFFsz9uBf1ZP", "assetName": "TEST", "description": "test asset", "data": "", "quantity": "1000000", "isDivisible": true, "fee": 0 },
|
||||||
|
{ "type": "ISSUE_ASSET", "issuerPublicKey": "C6wuddsBV3HzRrXUtezE7P5MoRXp5m3mEDokRDGZB6ry", "assetName": "OTHER", "description": "other test asset", "data": "", "quantity": "1000000", "isDivisible": true, "fee": 0 },
|
||||||
|
{ "type": "ISSUE_ASSET", "issuerPublicKey": "2tiMr5LTpaWCgbRvkPK8TFd7k63DyHJMMFFsz9uBf1ZP", "assetName": "GOLD", "description": "gold test asset", "data": "", "quantity": "1000000", "isDivisible": true, "fee": 0 },
|
||||||
|
|
||||||
|
{ "type": "ACCOUNT_FLAGS", "target": "QgV4s3xnzLhVBEJxcYui4u4q11yhUHsd9v", "andMask": -1, "orMask": 1, "xorMask": 0 },
|
||||||
|
{ "type": "REWARD_SHARE", "minterPublicKey": "2tiMr5LTpaWCgbRvkPK8TFd7k63DyHJMMFFsz9uBf1ZP", "recipient": "QgV4s3xnzLhVBEJxcYui4u4q11yhUHsd9v", "rewardSharePublicKey": "7PpfnvLSG7y4HPh8hE7KoqAjLCkv7Ui6xw4mKAkbZtox", "sharePercent": "100" },
|
||||||
|
|
||||||
|
{ "type": "ACCOUNT_LEVEL", "target": "Qci5m9k4rcwe4ruKrZZQKka4FzUUMut3er", "level": 5 }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
@ -10,7 +10,11 @@
|
|||||||
],
|
],
|
||||||
"requireGroupForApproval": false,
|
"requireGroupForApproval": false,
|
||||||
"minAccountLevelToRewardShare": 5,
|
"minAccountLevelToRewardShare": 5,
|
||||||
"maxRewardSharesPerMintingAccount": 20,
|
"maxRewardSharesPerFounderMintingAccount": 6,
|
||||||
|
"maxRewardSharesByTimestamp": [
|
||||||
|
{ "timestamp": 0, "maxShares": 6 },
|
||||||
|
{ "timestamp": 9999999999999, "maxShares": 3 }
|
||||||
|
],
|
||||||
"founderEffectiveMintingLevel": 10,
|
"founderEffectiveMintingLevel": 10,
|
||||||
"onlineAccountSignaturesMinLifetime": 3600000,
|
"onlineAccountSignaturesMinLifetime": 3600000,
|
||||||
"onlineAccountSignaturesMaxLifetime": 86400000,
|
"onlineAccountSignaturesMaxLifetime": 86400000,
|
||||||
@ -21,14 +25,16 @@
|
|||||||
{ "height": 21, "reward": 1 }
|
{ "height": 21, "reward": 1 }
|
||||||
],
|
],
|
||||||
"sharesByLevel": [
|
"sharesByLevel": [
|
||||||
{ "levels": [ 1, 2 ], "share": 0.05 },
|
{ "id": 1, "levels": [ 1, 2 ], "share": 0.05 },
|
||||||
{ "levels": [ 3, 4 ], "share": 0.10 },
|
{ "id": 2, "levels": [ 3, 4 ], "share": 0.10 },
|
||||||
{ "levels": [ 5, 6 ], "share": 0.15 },
|
{ "id": 3, "levels": [ 5, 6 ], "share": 0.15 },
|
||||||
{ "levels": [ 7, 8 ], "share": 0.20 },
|
{ "id": 4, "levels": [ 7, 8 ], "share": 0.20 },
|
||||||
{ "levels": [ 9, 10 ], "share": 0.25 }
|
{ "id": 5, "levels": [ 9, 10 ], "share": 0.25 }
|
||||||
],
|
],
|
||||||
"qoraHoldersShare": 0.20,
|
"qoraHoldersShare": 0.20,
|
||||||
"qoraPerQortReward": 250,
|
"qoraPerQortReward": 250,
|
||||||
|
"minAccountsToActivateShareBin": 30,
|
||||||
|
"shareBinActivationMinLevel": 7,
|
||||||
"blocksNeededByLevel": [ 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 ],
|
"blocksNeededByLevel": [ 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 ],
|
||||||
"blockTimingsByHeight": [
|
"blockTimingsByHeight": [
|
||||||
{ "height": 1, "target": 60000, "deviation": 30000, "power": 0.2 }
|
{ "height": 1, "target": 60000, "deviation": 30000, "power": 0.2 }
|
||||||
@ -52,6 +58,7 @@
|
|||||||
"atFindNextTransactionFix": 0,
|
"atFindNextTransactionFix": 0,
|
||||||
"newBlockSigHeight": 999999,
|
"newBlockSigHeight": 999999,
|
||||||
"shareBinFix": 999999,
|
"shareBinFix": 999999,
|
||||||
|
"rewardShareLimitTimestamp": 9999999999999,
|
||||||
"calcChainWeightTimestamp": 0,
|
"calcChainWeightTimestamp": 0,
|
||||||
"transactionV5Timestamp": 0,
|
"transactionV5Timestamp": 0,
|
||||||
"transactionV6Timestamp": 0,
|
"transactionV6Timestamp": 0,
|
||||||
|
19
src/test/resources/test-settings-v2-reward-shares.json
Normal file
19
src/test/resources/test-settings-v2-reward-shares.json
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"repositoryPath": "testdb",
|
||||||
|
"bitcoinNet": "TEST3",
|
||||||
|
"litecoinNet": "TEST3",
|
||||||
|
"restrictedApi": false,
|
||||||
|
"blockchainConfig": "src/test/resources/test-chain-v2-reward-shares.json",
|
||||||
|
"exportPath": "qortal-backup-test",
|
||||||
|
"bootstrap": false,
|
||||||
|
"wipeUnconfirmedOnStart": false,
|
||||||
|
"testNtpOffset": 0,
|
||||||
|
"minPeers": 0,
|
||||||
|
"pruneBlockLimit": 100,
|
||||||
|
"bootstrapFilenamePrefix": "test-",
|
||||||
|
"dataPath": "data-test",
|
||||||
|
"tempDataPath": "data-test/_temp",
|
||||||
|
"listsPath": "lists-test",
|
||||||
|
"storagePolicy": "FOLLOWED_OR_VIEWED",
|
||||||
|
"maxStorageCapacity": 104857600
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user