forked from Qortal/qortal
Merge branch 'master' into online-accounts-mempow-v2
# Conflicts: # src/main/java/org/qortal/block/BlockChain.java
This commit is contained in:
commit
0ddb7f8f17
@ -17,10 +17,10 @@
|
||||
<ROW Property="Manufacturer" Value="Qortal"/>
|
||||
<ROW Property="MsiLogging" MultiBuildValue="DefaultBuild:vp"/>
|
||||
<ROW Property="NTP_GOOD" Value="false"/>
|
||||
<ROW Property="ProductCode" Value="1033:{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="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="REMOVE_BLOCKCHAIN" Value="YES" Type="4"/>
|
||||
<ROW Property="REPAIR_BLOCKCHAIN" Value="YES" Type="4"/>
|
||||
@ -212,7 +212,7 @@
|
||||
<ROW Component="ADDITIONAL_LICENSE_INFO_71" ComponentId="{12A3ADBE-BB7A-496C-8869-410681E6232F}" Directory_="jdk.zipfs_Dir" Attributes="0" KeyPath="ADDITIONAL_LICENSE_INFO_71" Type="0"/>
|
||||
<ROW Component="ADDITIONAL_LICENSE_INFO_8" ComponentId="{D53AD95E-CF96-4999-80FC-5812277A7456}" Directory_="java.naming_Dir" Attributes="0" KeyPath="ADDITIONAL_LICENSE_INFO_8" Type="0"/>
|
||||
<ROW Component="ADDITIONAL_LICENSE_INFO_9" ComponentId="{6B7EA9B0-5D17-47A8-B78C-FACE86D15E01}" Directory_="java.net.http_Dir" Attributes="0" KeyPath="ADDITIONAL_LICENSE_INFO_9" Type="0"/>
|
||||
<ROW Component="AI_CustomARPName" ComponentId="{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="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"/>
|
||||
|
2
pom.xml
2
pom.xml
@ -3,7 +3,7 @@
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>org.qortal</groupId>
|
||||
<artifactId>qortal</artifactId>
|
||||
<version>3.4.1</version>
|
||||
<version>3.4.2</version>
|
||||
<packaging>jar</packaging>
|
||||
<properties>
|
||||
<skipTests>true</skipTests>
|
||||
|
@ -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) {
|
||||
if (this.isRecipientAlsoMinter) {
|
||||
// minter & recipient the same - simpler case
|
||||
@ -1891,12 +1896,67 @@ public class Block {
|
||||
final boolean haveFounders = !onlineFounderAccounts.isEmpty();
|
||||
|
||||
// Determine reward candidates based on account level
|
||||
List<AccountLevelShareBin> accountLevelShareBins = BlockChain.getInstance().getAccountLevelShareBins();
|
||||
for (int binIndex = 0; binIndex < accountLevelShareBins.size(); ++binIndex) {
|
||||
// Find all accounts in share bin. getShareBin() returns null for minter accounts that are also founders, so they are effectively filtered out.
|
||||
// This needs a deep copy, so the shares can be modified when tiers aren't activated yet
|
||||
List<AccountLevelShareBin> accountLevelShareBins = new ArrayList<>();
|
||||
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);
|
||||
// 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
|
||||
if (binnedAccounts.isEmpty())
|
||||
|
@ -104,10 +104,23 @@ public class BlockChain {
|
||||
private List<RewardByHeight> rewardsByHeight;
|
||||
|
||||
/** 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;
|
||||
@XmlJavaTypeAdapter(value = org.qortal.api.AmountTypeAdapter.class)
|
||||
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;
|
||||
/** Generated lookup of share-bin by account level */
|
||||
@ -121,6 +134,12 @@ public class BlockChain {
|
||||
@XmlJavaTypeAdapter(value = org.qortal.api.AmountTypeAdapter.class)
|
||||
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.
|
||||
* <p>
|
||||
@ -379,6 +398,14 @@ public class BlockChain {
|
||||
return this.qoraPerQortReward;
|
||||
}
|
||||
|
||||
public int getMinAccountsToActivateShareBin() {
|
||||
return this.minAccountsToActivateShareBin;
|
||||
}
|
||||
|
||||
public int getShareBinActivationMinLevel() {
|
||||
return this.shareBinActivationMinLevel;
|
||||
}
|
||||
|
||||
public int getMinAccountLevelToMint() {
|
||||
return this.minAccountLevelToMint;
|
||||
}
|
||||
|
@ -90,37 +90,40 @@ public class BlockMinter extends Thread {
|
||||
|
||||
List<Block> newBlocks = new ArrayList<>();
|
||||
|
||||
// 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 (final Repository repository = RepositoryManager.getRepository()) {
|
||||
// Going to need this a lot...
|
||||
BlockRepository blockRepository = repository.getBlockRepository();
|
||||
|
||||
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 {
|
||||
// Sleep for a while
|
||||
Thread.sleep(1000);
|
||||
wasMintingPossible = isMintingPossible;
|
||||
|
||||
isMintingPossible = false;
|
||||
try {
|
||||
// Free up any repository locks
|
||||
repository.discardChanges();
|
||||
|
||||
final Long now = NTP.getTime();
|
||||
if (now == null)
|
||||
continue;
|
||||
// Sleep for a while
|
||||
Thread.sleep(1000);
|
||||
|
||||
final Long minLatestBlockTimestamp = Controller.getMinimumLatestBlockTimestamp();
|
||||
if (minLatestBlockTimestamp == null)
|
||||
continue;
|
||||
isMintingPossible = false;
|
||||
|
||||
// No online accounts for current timestamp? (e.g. during startup)
|
||||
if (!OnlineAccountsManager.getInstance().hasOnlineAccounts())
|
||||
continue;
|
||||
final Long now = NTP.getTime();
|
||||
if (now == null)
|
||||
continue;
|
||||
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
// Going to need this a lot...
|
||||
BlockRepository blockRepository = repository.getBlockRepository();
|
||||
final Long minLatestBlockTimestamp = Controller.getMinimumLatestBlockTimestamp();
|
||||
if (minLatestBlockTimestamp == null)
|
||||
continue;
|
||||
|
||||
// No online accounts for current timestamp? (e.g. during startup)
|
||||
if (!OnlineAccountsManager.getInstance().hasOnlineAccounts())
|
||||
continue;
|
||||
|
||||
List<MintingAccountData> mintingAccountsData = repository.getAccountRepository().getMintingAccounts();
|
||||
// No minting accounts?
|
||||
@ -198,10 +201,6 @@ public class BlockMinter extends Thread {
|
||||
// so go ahead and mint a block if possible.
|
||||
isMintingPossible = true;
|
||||
|
||||
// Reattach newBlocks to new repository handle
|
||||
for (Block newBlock : newBlocks)
|
||||
newBlock.setRepository(repository);
|
||||
|
||||
// Check blockchain hasn't changed
|
||||
if (previousBlockData == null || !Arrays.equals(previousBlockData.getSignature(), lastBlockData.getSignature())) {
|
||||
previousBlockData = lastBlockData;
|
||||
@ -439,13 +438,13 @@ public class BlockMinter extends Thread {
|
||||
Network network = Network.getInstance();
|
||||
network.broadcast(broadcastPeer -> network.buildHeightMessage(broadcastPeer, newBlockData));
|
||||
}
|
||||
} catch (DataException e) {
|
||||
LOGGER.warn("Repository issue while running block minter", e);
|
||||
} catch (InterruptedException 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -278,6 +278,12 @@ public class OnlineAccountsManager {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check timestamp is a multiple of online timestamp modulus
|
||||
if (onlineAccountTimestamp % getOnlineTimestampModulus() != 0) {
|
||||
LOGGER.trace(() -> String.format("Rejecting online account %s with invalid timestamp %d", Base58.encode(rewardSharePublicKey), onlineAccountTimestamp));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Verify signature
|
||||
byte[] data = Longs.toByteArray(onlineAccountData.getTimestamp());
|
||||
boolean isSignatureValid = onlineAccountTimestamp >= BlockChain.getInstance().getAggregateSignatureTimestamp()
|
||||
|
@ -29,6 +29,7 @@ import org.bitcoinj.wallet.SendRequest;
|
||||
import org.bitcoinj.wallet.Wallet;
|
||||
import org.qortal.api.model.SimpleForeignTransaction;
|
||||
import org.qortal.crypto.Crypto;
|
||||
import org.qortal.settings.Settings;
|
||||
import org.qortal.utils.Amounts;
|
||||
import org.qortal.utils.BitTwiddling;
|
||||
|
||||
@ -61,11 +62,6 @@ public abstract class Bitcoiny implements ForeignBlockchain {
|
||||
/** How many wallet keys to generate in each batch. */
|
||||
private static final int WALLET_KEY_LOOKAHEAD_INCREMENT = 3;
|
||||
|
||||
/** How many wallet keys to generate when using bitcoinj as the data provider.
|
||||
* We must use a higher value here since we are unable to request multiple batches of keys.
|
||||
* Without this, the bitcoinj state can be missing transactions, causing errors such as "insufficient balance". */
|
||||
private static final int WALLET_KEY_LOOKAHEAD_INCREMENT_BITCOINJ = 50;
|
||||
|
||||
/** Byte offset into raw block headers to block timestamp. */
|
||||
private static final int TIMESTAMP_OFFSET = 4 + 32 + 32;
|
||||
|
||||
@ -409,9 +405,6 @@ public abstract class Bitcoiny implements ForeignBlockchain {
|
||||
Set<BitcoinyTransaction> walletTransactions = new HashSet<>();
|
||||
Set<String> keySet = new HashSet<>();
|
||||
|
||||
// Set the number of consecutive empty batches required before giving up
|
||||
final int numberOfAdditionalBatchesToSearch = 7;
|
||||
|
||||
int unusedCounter = 0;
|
||||
int ki = 0;
|
||||
do {
|
||||
@ -438,12 +431,12 @@ public abstract class Bitcoiny implements ForeignBlockchain {
|
||||
|
||||
if (areAllKeysUnused) {
|
||||
// No transactions
|
||||
if (unusedCounter >= numberOfAdditionalBatchesToSearch) {
|
||||
if (unusedCounter >= Settings.getInstance().getGapLimit()) {
|
||||
// ... and we've hit our search limit
|
||||
break;
|
||||
}
|
||||
// We haven't hit our search limit yet so increment the counter and keep looking
|
||||
unusedCounter++;
|
||||
unusedCounter += WALLET_KEY_LOOKAHEAD_INCREMENT;
|
||||
} else {
|
||||
// Some keys in this batch were used, so reset the counter
|
||||
unusedCounter = 0;
|
||||
@ -630,7 +623,7 @@ public abstract class Bitcoiny implements ForeignBlockchain {
|
||||
this.keyChain = this.wallet.getActiveKeyChain();
|
||||
|
||||
// Set up wallet's key chain
|
||||
this.keyChain.setLookaheadSize(Bitcoiny.WALLET_KEY_LOOKAHEAD_INCREMENT_BITCOINJ);
|
||||
this.keyChain.setLookaheadSize(Settings.getInstance().getBitcoinjLookaheadSize());
|
||||
this.keyChain.maybeLookAhead();
|
||||
}
|
||||
|
||||
|
@ -289,6 +289,16 @@ public class Settings {
|
||||
private boolean onlineAccountsMemPoWEnabled = false;
|
||||
|
||||
|
||||
/* Foreign chains */
|
||||
|
||||
/** The number of consecutive empty addresses required before treating a wallet's transaction set as complete */
|
||||
private int gapLimit = 24;
|
||||
|
||||
/** How many wallet keys to generate when using bitcoinj as the blockchain interface (e.g. when sending coins) */
|
||||
private int bitcoinjLookaheadSize = 50;
|
||||
|
||||
|
||||
|
||||
// Data storage (QDN)
|
||||
|
||||
/** Data storage enabled/disabled*/
|
||||
@ -881,6 +891,15 @@ public class Settings {
|
||||
}
|
||||
|
||||
|
||||
public int getGapLimit() {
|
||||
return this.gapLimit;
|
||||
}
|
||||
|
||||
public int getBitcoinjLookaheadSize() {
|
||||
return bitcoinjLookaheadSize;
|
||||
}
|
||||
|
||||
|
||||
public boolean isQdnEnabled() {
|
||||
return this.qdnEnabled;
|
||||
}
|
||||
|
@ -41,14 +41,16 @@
|
||||
{ "height": 3110401, "reward": 2.00 }
|
||||
],
|
||||
"sharesByLevel": [
|
||||
{ "levels": [ 1, 2 ], "share": 0.05 },
|
||||
{ "levels": [ 3, 4 ], "share": 0.10 },
|
||||
{ "levels": [ 5, 6 ], "share": 0.15 },
|
||||
{ "levels": [ 7, 8 ], "share": 0.20 },
|
||||
{ "levels": [ 9, 10 ], "share": 0.25 }
|
||||
{ "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": [ 7200, 64800, 129600, 172800, 244000, 345600, 518400, 691200, 864000, 1036800 ],
|
||||
"blockTimingsByHeight": [
|
||||
{ "height": 1, "target": 60000, "deviation": 30000, "power": 0.2 }
|
||||
|
@ -3,10 +3,9 @@ package org.qortal.test.minting;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.*;
|
||||
|
||||
import org.apache.commons.lang3.reflect.FieldUtils;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.junit.After;
|
||||
@ -702,6 +701,15 @@ public class RewardTests extends Common {
|
||||
AccountUtils.assertBalance(repository, "chloe", Asset.QORT, chloeInitialBalance+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, "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 }
|
||||
],
|
||||
"sharesByLevel": [
|
||||
{ "levels": [ 1, 2 ], "share": 0.05 },
|
||||
{ "levels": [ 3, 4 ], "share": 0.10 },
|
||||
{ "levels": [ 5, 6 ], "share": 0.15 },
|
||||
{ "levels": [ 7, 8 ], "share": 0.20 },
|
||||
{ "levels": [ 9, 10 ], "share": 0.25 }
|
||||
{ "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 },
|
||||
|
@ -24,14 +24,16 @@
|
||||
{ "height": 21, "reward": 1 }
|
||||
],
|
||||
"sharesByLevel": [
|
||||
{ "levels": [ 1, 2 ], "share": 0.05 },
|
||||
{ "levels": [ 3, 4 ], "share": 0.10 },
|
||||
{ "levels": [ 5, 6 ], "share": 0.15 },
|
||||
{ "levels": [ 7, 8 ], "share": 0.20 },
|
||||
{ "levels": [ 9, 10 ], "share": 0.25 }
|
||||
{ "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 }
|
||||
|
@ -25,14 +25,16 @@
|
||||
{ "height": 21, "reward": 1 }
|
||||
],
|
||||
"sharesByLevel": [
|
||||
{ "levels": [ 1, 2 ], "share": 0.05 },
|
||||
{ "levels": [ 3, 4 ], "share": 0.10 },
|
||||
{ "levels": [ 5, 6 ], "share": 0.15 },
|
||||
{ "levels": [ 7, 8 ], "share": 0.20 },
|
||||
{ "levels": [ 9, 10 ], "share": 0.25 }
|
||||
{ "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 }
|
||||
|
@ -25,14 +25,16 @@
|
||||
{ "height": 21, "reward": 1 }
|
||||
],
|
||||
"sharesByLevel": [
|
||||
{ "levels": [ 1, 2 ], "share": 0.05 },
|
||||
{ "levels": [ 3, 4 ], "share": 0.10 },
|
||||
{ "levels": [ 5, 6 ], "share": 0.15 },
|
||||
{ "levels": [ 7, 8 ], "share": 0.20 },
|
||||
{ "levels": [ 9, 10 ], "share": 0.25 }
|
||||
{ "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 }
|
||||
|
@ -25,14 +25,16 @@
|
||||
{ "height": 21, "reward": 1 }
|
||||
],
|
||||
"sharesByLevel": [
|
||||
{ "levels": [ 1, 2 ], "share": 0.05 },
|
||||
{ "levels": [ 3, 4 ], "share": 0.10 },
|
||||
{ "levels": [ 5, 6 ], "share": 0.15 },
|
||||
{ "levels": [ 7, 8 ], "share": 0.20 },
|
||||
{ "levels": [ 9, 10 ], "share": 0.25 }
|
||||
{ "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 }
|
||||
|
@ -25,14 +25,16 @@
|
||||
{ "height": 21, "reward": 1 }
|
||||
],
|
||||
"sharesByLevel": [
|
||||
{ "levels": [ 1, 2 ], "share": 0.05 },
|
||||
{ "levels": [ 3, 4 ], "share": 0.10 },
|
||||
{ "levels": [ 5, 6 ], "share": 0.15 },
|
||||
{ "levels": [ 7, 8 ], "share": 0.20 },
|
||||
{ "levels": [ 9, 10 ], "share": 0.25 }
|
||||
{ "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 }
|
||||
|
@ -25,14 +25,16 @@
|
||||
{ "height": 21, "reward": 1 }
|
||||
],
|
||||
"sharesByLevel": [
|
||||
{ "levels": [ 1, 2 ], "share": 0.05 },
|
||||
{ "levels": [ 3, 4 ], "share": 0.10 },
|
||||
{ "levels": [ 5, 6 ], "share": 0.15 },
|
||||
{ "levels": [ 7, 8 ], "share": 0.20 },
|
||||
{ "levels": [ 9, 10 ], "share": 0.25 }
|
||||
{ "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 }
|
||||
|
@ -25,14 +25,16 @@
|
||||
{ "height": 21, "reward": 1 }
|
||||
],
|
||||
"sharesByLevel": [
|
||||
{ "levels": [ 1, 2 ], "share": 0.05 },
|
||||
{ "levels": [ 3, 4 ], "share": 0.10 },
|
||||
{ "levels": [ 5, 6 ], "share": 0.15 },
|
||||
{ "levels": [ 7, 8 ], "share": 0.20 },
|
||||
{ "levels": [ 9, 10 ], "share": 0.25 }
|
||||
{ "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": 1,
|
||||
"shareBinActivationMinLevel": 7,
|
||||
"blocksNeededByLevel": [ 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 ],
|
||||
"blockTimingsByHeight": [
|
||||
{ "height": 1, "target": 60000, "deviation": 30000, "power": 0.2 }
|
||||
|
@ -25,14 +25,16 @@
|
||||
{ "height": 21, "reward": 1 }
|
||||
],
|
||||
"sharesByLevel": [
|
||||
{ "levels": [ 1, 2 ], "share": 0.05 },
|
||||
{ "levels": [ 3, 4 ], "share": 0.10 },
|
||||
{ "levels": [ 5, 6 ], "share": 0.15 },
|
||||
{ "levels": [ 7, 8 ], "share": 0.20 },
|
||||
{ "levels": [ 9, 10 ], "share": 0.25 }
|
||||
{ "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 }
|
||||
|
@ -24,14 +24,16 @@
|
||||
{ "height": 21, "reward": 1 }
|
||||
],
|
||||
"sharesByLevel": [
|
||||
{ "levels": [ 1, 2 ], "share": 0.05 },
|
||||
{ "levels": [ 3, 4 ], "share": 0.10 },
|
||||
{ "levels": [ 5, 6 ], "share": 0.15 },
|
||||
{ "levels": [ 7, 8 ], "share": 0.20 },
|
||||
{ "levels": [ 9, 10 ], "share": 0.25 }
|
||||
{ "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 }
|
||||
|
@ -25,14 +25,16 @@
|
||||
{ "height": 21, "reward": 1 }
|
||||
],
|
||||
"sharesByLevel": [
|
||||
{ "levels": [ 1, 2 ], "share": 0.05 },
|
||||
{ "levels": [ 3, 4 ], "share": 0.10 },
|
||||
{ "levels": [ 5, 6 ], "share": 0.15 },
|
||||
{ "levels": [ 7, 8 ], "share": 0.20 },
|
||||
{ "levels": [ 9, 10 ], "share": 0.25 }
|
||||
{ "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 }
|
||||
|
Loading…
x
Reference in New Issue
Block a user