forked from Qortal/qortal
Compare commits
23 Commits
prioritize
...
chain-weig
Author | SHA1 | Date | |
---|---|---|---|
|
6e9a61c4e5 | ||
|
8e244fd956 | ||
|
2eb6771963 | ||
|
db77108054 | ||
|
241e2bef85 | ||
|
fac02dbc7d | ||
|
9ebcd55ff5 | ||
|
50244c1c40 | ||
|
b4395fdad1 | ||
|
1da8994be7 | ||
|
55ff1e2bb1 | ||
|
5fd8528c49 | ||
|
26d8ed783a | ||
|
c0c5bf1591 | ||
|
c17a481b74 | ||
|
a9a0e69ec0 | ||
|
ea1fed2fd3 | ||
|
16453ed602 | ||
|
fde68dc598 | ||
|
847e81e95c | ||
|
1e6e5e66da | ||
|
9b0e88ca87 | ||
|
3acc0babb7 |
2
pom.xml
2
pom.xml
@@ -3,7 +3,7 @@
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>org.qortal</groupId>
|
||||
<artifactId>qortal</artifactId>
|
||||
<version>1.4.6</version>
|
||||
<version>1.5.0</version>
|
||||
<packaging>jar</packaging>
|
||||
<properties>
|
||||
<skipTests>true</skipTests>
|
||||
|
@@ -176,19 +176,26 @@ public class Block {
|
||||
*
|
||||
* @return account-level share "bin" from blockchain config, or null if founder / none found
|
||||
*/
|
||||
public AccountLevelShareBin getShareBin() {
|
||||
public AccountLevelShareBin getShareBin(int blockHeight) {
|
||||
if (this.isMinterFounder)
|
||||
return null;
|
||||
|
||||
final int accountLevel = this.mintingAccountData.getLevel();
|
||||
if (accountLevel <= 0)
|
||||
return null;
|
||||
return null; // level 0 isn't included in any share bins
|
||||
|
||||
final AccountLevelShareBin[] shareBinsByLevel = BlockChain.getInstance().getShareBinsByAccountLevel();
|
||||
final BlockChain blockChain = BlockChain.getInstance();
|
||||
final AccountLevelShareBin[] shareBinsByLevel = blockChain.getShareBinsByAccountLevel();
|
||||
if (accountLevel > shareBinsByLevel.length)
|
||||
return null;
|
||||
|
||||
return shareBinsByLevel[accountLevel];
|
||||
if (blockHeight < blockChain.getShareBinFixHeight())
|
||||
// Off-by-one bug still in effect
|
||||
return shareBinsByLevel[accountLevel];
|
||||
|
||||
// level 1 stored at index 0, level 2 stored at index 1, etc.
|
||||
return shareBinsByLevel[accountLevel-1];
|
||||
|
||||
}
|
||||
|
||||
public long distribute(long accountAmount, Map<String, Long> balanceChanges) {
|
||||
@@ -789,7 +796,9 @@ public class Block {
|
||||
NumberFormat formatter = new DecimalFormat("0.###E0");
|
||||
boolean isLogging = LOGGER.getLevel().isLessSpecificThan(Level.TRACE);
|
||||
|
||||
int blockCount = 0;
|
||||
for (BlockSummaryData blockSummaryData : blockSummaries) {
|
||||
blockCount++;
|
||||
StringBuilder stringBuilder = isLogging ? new StringBuilder(512) : null;
|
||||
|
||||
if (isLogging)
|
||||
@@ -818,11 +827,11 @@ public class Block {
|
||||
parentHeight = blockSummaryData.getHeight();
|
||||
parentBlockSignature = blockSummaryData.getSignature();
|
||||
|
||||
/* Potential future consensus change: only comparing the same number of blocks.
|
||||
if (parentHeight >= maxHeight)
|
||||
// After this timestamp, we only compare the same number of blocks
|
||||
if (NTP.getTime() >= BlockChain.getInstance().getCalcChainWeightTimestamp() && parentHeight >= maxHeight)
|
||||
break;
|
||||
*/
|
||||
}
|
||||
LOGGER.debug(String.format("Chain weight calculation was based on %d blocks", blockCount));
|
||||
|
||||
return cumulativeWeight;
|
||||
}
|
||||
@@ -1328,6 +1337,9 @@ public class Block {
|
||||
|
||||
// Give Controller our cached, valid online accounts data (if any) to help reduce CPU load for next block
|
||||
Controller.getInstance().pushLatestBlocksOnlineAccounts(this.cachedValidOnlineAccounts);
|
||||
|
||||
// Log some debugging info relating to the block weight calculation
|
||||
this.logDebugInfo();
|
||||
}
|
||||
|
||||
protected void increaseAccountLevels() throws DataException {
|
||||
@@ -1509,6 +1521,9 @@ public class Block {
|
||||
public void orphan() throws DataException {
|
||||
LOGGER.trace(() -> String.format("Orphaning block %d", this.blockData.getHeight()));
|
||||
|
||||
// Log some debugging info relating to the block weight calculation
|
||||
this.logDebugInfo();
|
||||
|
||||
// Return AT fees and delete AT states from repository
|
||||
orphanAtFeesAndStates();
|
||||
|
||||
@@ -1783,7 +1798,7 @@ public class Block {
|
||||
// Find all accounts in share bin. getShareBin() returns null for minter accounts that are also founders, so they are effectively filtered out.
|
||||
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() == accountLevelShareBin).collect(Collectors.toList());
|
||||
List<ExpandedAccount> binnedAccounts = expandedAccounts.stream().filter(accountInfo -> accountInfo.getShareBin(this.blockData.getHeight()) == accountLevelShareBin).collect(Collectors.toList());
|
||||
|
||||
// No online accounts in this bin? Skip to next one
|
||||
if (binnedAccounts.isEmpty())
|
||||
@@ -1981,4 +1996,33 @@ public class Block {
|
||||
this.repository.getAccountRepository().tidy();
|
||||
}
|
||||
|
||||
private void logDebugInfo() {
|
||||
try {
|
||||
if (this.repository == null || this.getMinter() == null || this.getBlockData() == null)
|
||||
return;
|
||||
|
||||
int minterLevel = Account.getRewardShareEffectiveMintingLevel(this.repository, this.getMinter().getPublicKey());
|
||||
|
||||
LOGGER.debug(String.format("======= BLOCK %d (%.8s) =======", this.getBlockData().getHeight(), Base58.encode(this.getSignature())));
|
||||
LOGGER.debug(String.format("Timestamp: %d", this.getBlockData().getTimestamp()));
|
||||
LOGGER.debug(String.format("Minter level: %d", minterLevel));
|
||||
LOGGER.debug(String.format("Online accounts: %d", this.getBlockData().getOnlineAccountsCount()));
|
||||
|
||||
BlockSummaryData blockSummaryData = new BlockSummaryData(this.getBlockData());
|
||||
if (this.getParent() == null || this.getParent().getSignature() == null || blockSummaryData == null)
|
||||
return;
|
||||
|
||||
blockSummaryData.setMinterLevel(minterLevel);
|
||||
BigInteger blockWeight = calcBlockWeight(this.getParent().getHeight(), this.getParent().getSignature(), blockSummaryData);
|
||||
BigInteger keyDistance = calcKeyDistance(this.getParent().getHeight(), this.getParent().getSignature(), blockSummaryData.getMinterPublicKey(), blockSummaryData.getMinterLevel());
|
||||
NumberFormat formatter = new DecimalFormat("0.###E0");
|
||||
|
||||
LOGGER.debug(String.format("Key distance: %s", formatter.format(keyDistance)));
|
||||
LOGGER.debug(String.format("Weight: %s", formatter.format(blockWeight)));
|
||||
|
||||
} catch (DataException e) {
|
||||
LOGGER.info(() -> String.format("Unable to log block debugging info: %s", e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -71,7 +71,9 @@ public class BlockChain {
|
||||
|
||||
public enum FeatureTrigger {
|
||||
atFindNextTransactionFix,
|
||||
newBlockSigHeight;
|
||||
newBlockSigHeight,
|
||||
shareBinFix,
|
||||
calcChainWeightTimestamp;
|
||||
}
|
||||
|
||||
/** Map of which blockchain features are enabled when (height/timestamp) */
|
||||
@@ -381,6 +383,14 @@ public class BlockChain {
|
||||
return this.featureTriggers.get(FeatureTrigger.newBlockSigHeight.name()).intValue();
|
||||
}
|
||||
|
||||
public int getShareBinFixHeight() {
|
||||
return this.featureTriggers.get(FeatureTrigger.shareBinFix.name()).intValue();
|
||||
}
|
||||
|
||||
public long getCalcChainWeightTimestamp() {
|
||||
return this.featureTriggers.get(FeatureTrigger.calcChainWeightTimestamp.name()).longValue();
|
||||
}
|
||||
|
||||
// More complex getters for aspects that change by height or timestamp
|
||||
|
||||
public long getRewardAtHeight(int ourHeight) {
|
||||
|
@@ -16,6 +16,7 @@ import org.qortal.account.Account;
|
||||
import org.qortal.account.PublicKeyAccount;
|
||||
import org.qortal.block.Block;
|
||||
import org.qortal.block.Block.ValidationResult;
|
||||
import org.qortal.block.BlockChain;
|
||||
import org.qortal.data.block.BlockData;
|
||||
import org.qortal.data.block.BlockSummaryData;
|
||||
import org.qortal.data.block.CommonBlockData;
|
||||
@@ -36,6 +37,7 @@ import org.qortal.repository.Repository;
|
||||
import org.qortal.repository.RepositoryManager;
|
||||
import org.qortal.transaction.Transaction;
|
||||
import org.qortal.utils.Base58;
|
||||
import org.qortal.utils.NTP;
|
||||
|
||||
public class Synchronizer {
|
||||
|
||||
@@ -120,6 +122,7 @@ public class Synchronizer {
|
||||
// Check if we can use the cached common block data, by comparing the peer's current chain tip against the peer's chain tip when we last found our common block
|
||||
if (peer.canUseCachedCommonBlockData()) {
|
||||
LOGGER.debug(String.format("Skipping peer %s because we already have the latest common block data in our cache. Cached common block sig is %.08s", peer, Base58.encode(peer.getCommonBlockData().getCommonBlockSummary().getSignature())));
|
||||
commonBlocksFound++;
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -226,6 +229,10 @@ public class Synchronizer {
|
||||
return peers;
|
||||
}
|
||||
|
||||
// We will switch to a new chain weight consensus algorithm at a hard fork, so determine if this has happened yet
|
||||
boolean usingSameLengthChainWeight = (NTP.getTime() >= BlockChain.getInstance().getCalcChainWeightTimestamp());
|
||||
LOGGER.debug(String.format("Using %s chain weight consensus algorithm", (usingSameLengthChainWeight ? "same-length" : "variable-length")));
|
||||
|
||||
// Retrieve a list of unique common blocks from this list of peers
|
||||
List<BlockSummaryData> commonBlocks = this.uniqueCommonBlocks(peers);
|
||||
|
||||
@@ -267,7 +274,7 @@ public class Synchronizer {
|
||||
|
||||
// Calculate the length of the shortest peer chain sharing this common block, including our chain
|
||||
final int ourAdditionalBlocksAfterCommonBlock = ourHeight - commonBlockSummary.getHeight();
|
||||
int minChainLength = this.calculateMinChainLength(commonBlockSummary, ourAdditionalBlocksAfterCommonBlock, peersSharingCommonBlock);
|
||||
int minChainLength = this.calculateMinChainLengthOfPeers(peersSharingCommonBlock, commonBlockSummary);
|
||||
|
||||
// Fetch block summaries from each peer
|
||||
for (Peer peer : peersSharingCommonBlock) {
|
||||
@@ -303,43 +310,51 @@ public class Synchronizer {
|
||||
if (blockSummaries != null) {
|
||||
LOGGER.trace(String.format("Peer %s returned %d block summar%s", peer, blockSummaries.size(), (blockSummaries.size() != 1 ? "ies" : "y")));
|
||||
|
||||
// We need to adjust minChainLength if peers fail to return all expected block summaries
|
||||
if (blockSummaries.size() < summariesRequired) {
|
||||
if (blockSummaries.size() < summariesRequired)
|
||||
// This could mean that the peer has re-orged. But we still have the same common block, so it's safe to proceed with this set of signatures instead.
|
||||
LOGGER.debug(String.format("Peer %s returned %d block summar%s instead of expected %d", peer, blockSummaries.size(), (blockSummaries.size() != 1 ? "ies" : "y"), summariesRequired));
|
||||
|
||||
// Update minChainLength if we have at least 1 block for this peer. If we don't have any blocks, this peer will be excluded from chain weight comparisons later in the process, so we shouldn't update minChainLength
|
||||
if (blockSummaries.size() > 0)
|
||||
minChainLength = blockSummaries.size();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// There are no block summaries after this common block
|
||||
peer.getCommonBlockData().setBlockSummariesAfterCommonBlock(null);
|
||||
}
|
||||
}
|
||||
|
||||
// Reduce minChainLength if needed. If we don't have any blocks, this peer will be excluded from chain weight comparisons later in the process, so we shouldn't update minChainLength
|
||||
List <BlockSummaryData> peerBlockSummaries = peer.getCommonBlockData().getBlockSummariesAfterCommonBlock();
|
||||
if (peerBlockSummaries != null && peerBlockSummaries.size() > 0)
|
||||
if (peerBlockSummaries.size() < minChainLength)
|
||||
minChainLength = peerBlockSummaries.size();
|
||||
}
|
||||
|
||||
// Fetch our corresponding block summaries. Limit to MAXIMUM_REQUEST_SIZE, in order to make the comparison fairer, as peers have been limited too
|
||||
final int ourSummariesRequired = Math.min(ourAdditionalBlocksAfterCommonBlock, MAXIMUM_REQUEST_SIZE);
|
||||
LOGGER.trace(String.format("About to fetch our block summaries from %d to %d. Our height: %d", commonBlockSummary.getHeight() + 1, commonBlockSummary.getHeight() + ourSummariesRequired, ourHeight));
|
||||
List<BlockSummaryData> ourBlockSummaries = repository.getBlockRepository().getBlockSummaries(commonBlockSummary.getHeight() + 1, commonBlockSummary.getHeight() + ourSummariesRequired);
|
||||
if (ourBlockSummaries.isEmpty())
|
||||
if (ourBlockSummaries.isEmpty()) {
|
||||
LOGGER.debug(String.format("We don't have any block summaries so can't compare our chain against peers with this common block. We can still compare them against each other."));
|
||||
else
|
||||
}
|
||||
else {
|
||||
populateBlockSummariesMinterLevels(repository, ourBlockSummaries);
|
||||
// Reduce minChainLength if we have less summaries
|
||||
if (ourBlockSummaries.size() < minChainLength)
|
||||
minChainLength = ourBlockSummaries.size();
|
||||
}
|
||||
|
||||
// Create array to hold peers for comparison
|
||||
List<Peer> superiorPeersForComparison = new ArrayList<>();
|
||||
|
||||
// Calculate max height for chain weight comparisons
|
||||
int maxHeightForChainWeightComparisons = commonBlockSummary.getHeight() + minChainLength;
|
||||
|
||||
// Calculate our chain weight
|
||||
BigInteger ourChainWeight = BigInteger.valueOf(0);
|
||||
if (ourBlockSummaries.size() > 0)
|
||||
ourChainWeight = Block.calcChainWeight(commonBlockSummary.getHeight(), commonBlockSummary.getSignature(), ourBlockSummaries, minChainLength);
|
||||
ourChainWeight = Block.calcChainWeight(commonBlockSummary.getHeight(), commonBlockSummary.getSignature(), ourBlockSummaries, maxHeightForChainWeightComparisons);
|
||||
|
||||
NumberFormat formatter = new DecimalFormat("0.###E0");
|
||||
NumberFormat accurateFormatter = new DecimalFormat("0.################E0");
|
||||
LOGGER.debug(String.format("Our chain weight based on %d blocks is %s", ourBlockSummaries.size(), formatter.format(ourChainWeight)));
|
||||
LOGGER.debug(String.format("Our chain weight based on %d blocks is %s", (usingSameLengthChainWeight ? minChainLength : ourBlockSummaries.size()), formatter.format(ourChainWeight)));
|
||||
|
||||
LOGGER.debug(String.format("Listing peers with common block %.8s...", Base58.encode(commonBlockSummary.getSignature())));
|
||||
for (Peer peer : peersSharingCommonBlock) {
|
||||
@@ -358,10 +373,10 @@ public class Synchronizer {
|
||||
populateBlockSummariesMinterLevels(repository, peerBlockSummariesAfterCommonBlock);
|
||||
|
||||
// Calculate cumulative chain weight of this blockchain subset, from common block to highest mutual block held by all peers in this group.
|
||||
LOGGER.debug(String.format("About to calculate chain weight based on %d blocks for peer %s with common block %.8s (peer has %d blocks after common block)", peerBlockSummariesAfterCommonBlock.size(), peer, Base58.encode(commonBlockSummary.getSignature()), peerAdditionalBlocksAfterCommonBlock));
|
||||
BigInteger peerChainWeight = Block.calcChainWeight(commonBlockSummary.getHeight(), commonBlockSummary.getSignature(), peerBlockSummariesAfterCommonBlock, minChainLength);
|
||||
LOGGER.debug(String.format("About to calculate chain weight based on %d blocks for peer %s with common block %.8s (peer has %d blocks after common block)", (usingSameLengthChainWeight ? minChainLength : peerBlockSummariesAfterCommonBlock.size()), peer, Base58.encode(commonBlockSummary.getSignature()), peerAdditionalBlocksAfterCommonBlock));
|
||||
BigInteger peerChainWeight = Block.calcChainWeight(commonBlockSummary.getHeight(), commonBlockSummary.getSignature(), peerBlockSummariesAfterCommonBlock, maxHeightForChainWeightComparisons);
|
||||
peer.getCommonBlockData().setChainWeight(peerChainWeight);
|
||||
LOGGER.debug(String.format("Chain weight of peer %s based on %d blocks (%d - %d) is %s", peer, peerBlockSummariesAfterCommonBlock.size(), peerBlockSummariesAfterCommonBlock.get(0).getHeight(), peerBlockSummariesAfterCommonBlock.get(peerBlockSummariesAfterCommonBlock.size()-1).getHeight(), formatter.format(peerChainWeight)));
|
||||
LOGGER.debug(String.format("Chain weight of peer %s based on %d blocks (%d - %d) is %s", peer, (usingSameLengthChainWeight ? minChainLength : peerBlockSummariesAfterCommonBlock.size()), peerBlockSummariesAfterCommonBlock.get(0).getHeight(), peerBlockSummariesAfterCommonBlock.get(peerBlockSummariesAfterCommonBlock.size()-1).getHeight(), formatter.format(peerChainWeight)));
|
||||
|
||||
// Compare against our chain - if our blockchain has greater weight then don't synchronize with peer (or any others in this group)
|
||||
if (ourChainWeight.compareTo(peerChainWeight) > 0) {
|
||||
@@ -425,14 +440,14 @@ public class Synchronizer {
|
||||
return commonBlocks;
|
||||
}
|
||||
|
||||
private int calculateMinChainLength(BlockSummaryData commonBlockSummary, int ourAdditionalBlocksAfterCommonBlock, List<Peer> peersSharingCommonBlock) {
|
||||
// Calculate the length of the shortest peer chain sharing this common block, including our chain
|
||||
int minChainLength = ourAdditionalBlocksAfterCommonBlock;
|
||||
private int calculateMinChainLengthOfPeers(List<Peer> peersSharingCommonBlock, BlockSummaryData commonBlockSummary) {
|
||||
// Calculate the length of the shortest peer chain sharing this common block
|
||||
int minChainLength = 0;
|
||||
for (Peer peer : peersSharingCommonBlock) {
|
||||
final int peerHeight = peer.getChainTipData().getLastHeight();
|
||||
final int peerAdditionalBlocksAfterCommonBlock = peerHeight - commonBlockSummary.getHeight();
|
||||
|
||||
if (peerAdditionalBlocksAfterCommonBlock < minChainLength)
|
||||
if (peerAdditionalBlocksAfterCommonBlock < minChainLength || minChainLength == 0)
|
||||
minChainLength = peerAdditionalBlocksAfterCommonBlock;
|
||||
}
|
||||
return minChainLength;
|
||||
@@ -703,7 +718,7 @@ public class Synchronizer {
|
||||
populateBlockSummariesMinterLevels(repository, ourBlockSummaries);
|
||||
populateBlockSummariesMinterLevels(repository, peerBlockSummaries);
|
||||
|
||||
final int mutualHeight = commonBlockHeight - 1 + Math.min(ourBlockSummaries.size(), peerBlockSummaries.size());
|
||||
final int mutualHeight = commonBlockHeight + Math.min(ourBlockSummaries.size(), peerBlockSummaries.size());
|
||||
|
||||
// Calculate cumulative chain weights of both blockchain subsets, from common block to highest mutual block.
|
||||
BigInteger ourChainWeight = Block.calcChainWeight(commonBlockHeight, commonBlockSig, ourBlockSummaries, mutualHeight);
|
||||
@@ -771,14 +786,32 @@ public class Synchronizer {
|
||||
if (cachedCommonBlockData != null)
|
||||
cachedCommonBlockData.setBlockSummariesAfterCommonBlock(null);
|
||||
|
||||
// If we have already received RECENT blocks from this peer, go ahead and apply them
|
||||
// If we have already received recent or newer blocks from this peer, go ahead and apply them
|
||||
if (peerBlocks.size() > 0) {
|
||||
final BlockData ourLatestBlockData = repository.getBlockRepository().getLastBlock();
|
||||
final Block peerLatestBlock = peerBlocks.get(peerBlocks.size() - 1);
|
||||
final Long minLatestBlockTimestamp = Controller.getMinimumLatestBlockTimestamp();
|
||||
if (peerLatestBlock != null && minLatestBlockTimestamp != null
|
||||
&& peerLatestBlock.getBlockData().getTimestamp() > minLatestBlockTimestamp) {
|
||||
LOGGER.debug("Newly received blocks are recent, so we will apply them");
|
||||
break;
|
||||
if (ourLatestBlockData != null && peerLatestBlock != null && minLatestBlockTimestamp != null) {
|
||||
|
||||
// If we have received at least one recent block, we can apply them
|
||||
if (peerLatestBlock.getBlockData().getTimestamp() > minLatestBlockTimestamp) {
|
||||
LOGGER.debug("Newly received blocks are recent, so we will apply them");
|
||||
break;
|
||||
}
|
||||
|
||||
// If our latest block is very old....
|
||||
if (ourLatestBlockData.getTimestamp() < minLatestBlockTimestamp) {
|
||||
// ... and we have received a block that is more recent than our latest block ...
|
||||
if (peerLatestBlock.getBlockData().getTimestamp() > ourLatestBlockData.getTimestamp()) {
|
||||
// ... then apply the blocks, as it takes us a step forward.
|
||||
// This is particularly useful when starting up a node that was on a small fork when it was last shut down.
|
||||
// In these cases, we now allow the node to sync forward, and get onto the main chain again.
|
||||
// Without this, we would require that the node syncs ENTIRELY with this peer,
|
||||
// and any problems downloading a block would cause all progress to be lost.
|
||||
LOGGER.debug(String.format("Newly received blocks are %d ms newer than our latest block - so we will apply them", peerLatestBlock.getBlockData().getTimestamp() - ourLatestBlockData.getTimestamp()));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Otherwise, give up and move on to the next peer, to avoid putting our chain into an outdated state
|
||||
@@ -806,14 +839,32 @@ public class Synchronizer {
|
||||
|
||||
if (retryCount >= MAXIMUM_RETRIES) {
|
||||
|
||||
// If we have already received RECENT blocks from this peer, go ahead and apply them
|
||||
// If we have already received recent or newer blocks from this peer, go ahead and apply them
|
||||
if (peerBlocks.size() > 0) {
|
||||
final BlockData ourLatestBlockData = repository.getBlockRepository().getLastBlock();
|
||||
final Block peerLatestBlock = peerBlocks.get(peerBlocks.size() - 1);
|
||||
final Long minLatestBlockTimestamp = Controller.getMinimumLatestBlockTimestamp();
|
||||
if (peerLatestBlock != null && minLatestBlockTimestamp != null
|
||||
&& peerLatestBlock.getBlockData().getTimestamp() > minLatestBlockTimestamp) {
|
||||
LOGGER.debug("Newly received blocks are recent, so we will apply them");
|
||||
break;
|
||||
if (ourLatestBlockData != null && peerLatestBlock != null && minLatestBlockTimestamp != null) {
|
||||
|
||||
// If we have received at least one recent block, we can apply them
|
||||
if (peerLatestBlock.getBlockData().getTimestamp() > minLatestBlockTimestamp) {
|
||||
LOGGER.debug("Newly received blocks are recent, so we will apply them");
|
||||
break;
|
||||
}
|
||||
|
||||
// If our latest block is very old....
|
||||
if (ourLatestBlockData.getTimestamp() < minLatestBlockTimestamp) {
|
||||
// ... and we have received a block that is more recent than our latest block ...
|
||||
if (peerLatestBlock.getBlockData().getTimestamp() > ourLatestBlockData.getTimestamp()) {
|
||||
// ... then apply the blocks, as it takes us a step forward.
|
||||
// This is particularly useful when starting up a node that was on a small fork when it was last shut down.
|
||||
// In these cases, we now allow the node to sync forward, and get onto the main chain again.
|
||||
// Without this, we would require that the node syncs ENTIRELY with this peer,
|
||||
// and any problems downloading a block would cause all progress to be lost.
|
||||
LOGGER.debug(String.format("Newly received blocks are %d ms newer than our latest block - so we will apply them", peerLatestBlock.getBlockData().getTimestamp() - ourLatestBlockData.getTimestamp()));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Otherwise, give up and move on to the next peer, to avoid putting our chain into an outdated state
|
||||
|
@@ -42,35 +42,32 @@ public class Bitcoin extends Bitcoiny {
|
||||
public Collection<ElectrumX.Server> getServers() {
|
||||
return Arrays.asList(
|
||||
// Servers chosen on NO BASIS WHATSOEVER from various sources!
|
||||
new Server("enode.duckdns.org", Server.ConnectionType.SSL, 50002),
|
||||
new Server("electrumx.ml", Server.ConnectionType.SSL, 50002),
|
||||
new Server("electrum.bitkoins.nl", Server.ConnectionType.SSL, 50512),
|
||||
new Server("btc.electroncash.dk", Server.ConnectionType.SSL, 60002),
|
||||
new Server("electrumx.electricnewyear.net", Server.ConnectionType.SSL, 50002),
|
||||
new Server("dxm.no-ip.biz", Server.ConnectionType.TCP, 50001),
|
||||
new Server("kirsche.emzy.de", Server.ConnectionType.TCP, 50001),
|
||||
new Server("2AZZARITA.hopto.org", Server.ConnectionType.TCP, 50001),
|
||||
new Server("xtrum.com", Server.ConnectionType.TCP, 50001),
|
||||
new Server("electrum.srvmin.network", Server.ConnectionType.TCP, 50001),
|
||||
new Server("electrumx.alexridevski.net", Server.ConnectionType.TCP, 50001),
|
||||
new Server("bitcoin.lukechilds.co", Server.ConnectionType.TCP, 50001),
|
||||
new Server("electrum.poiuty.com", Server.ConnectionType.TCP, 50001),
|
||||
new Server("horsey.cryptocowboys.net", Server.ConnectionType.TCP, 50001),
|
||||
new Server("128.0.190.26", Server.ConnectionType.SSL, 50002),
|
||||
new Server("hodlers.beer", Server.ConnectionType.SSL, 50002),
|
||||
new Server("electrumx.erbium.eu", Server.ConnectionType.TCP, 50001),
|
||||
new Server("electrumx.erbium.eu", Server.ConnectionType.SSL, 50002),
|
||||
new Server("btc.lastingcoin.net", Server.ConnectionType.SSL, 50002),
|
||||
new Server("electrum.bitaroo.net", Server.ConnectionType.SSL, 50002),
|
||||
new Server("bitcoin.grey.pw", Server.ConnectionType.SSL, 50002),
|
||||
new Server("2electrumx.hopto.me", Server.ConnectionType.SSL, 56022),
|
||||
new Server("185.64.116.15", Server.ConnectionType.SSL, 50002),
|
||||
new Server("kirsche.emzy.de", Server.ConnectionType.SSL, 50002),
|
||||
new Server("alviss.coinjoined.com", Server.ConnectionType.SSL, 50002),
|
||||
new Server("electrum.emzy.de", Server.ConnectionType.SSL, 50002),
|
||||
new Server("electrum.emzy.de", Server.ConnectionType.TCP, 50001),
|
||||
new Server("electrum-server.ninja", Server.ConnectionType.TCP, 50081),
|
||||
new Server("bitcoin.electrumx.multicoin.co", Server.ConnectionType.TCP, 50001),
|
||||
new Server("esx.geekhosters.com", Server.ConnectionType.TCP, 50001),
|
||||
new Server("bitcoin.grey.pw", Server.ConnectionType.TCP, 50003),
|
||||
new Server("exs.ignorelist.com", Server.ConnectionType.TCP, 50001),
|
||||
new Server("electrum.coinext.com.br", Server.ConnectionType.TCP, 50001),
|
||||
new Server("bitcoin.aranguren.org", Server.ConnectionType.TCP, 50001),
|
||||
new Server("skbxmit.coinjoined.com", Server.ConnectionType.TCP, 50001),
|
||||
new Server("alviss.coinjoined.com", Server.ConnectionType.TCP, 50001),
|
||||
new Server("electrum2.privateservers.network", Server.ConnectionType.TCP, 50001),
|
||||
new Server("electrumx.schulzemic.net", Server.ConnectionType.TCP, 50001),
|
||||
new Server("bitcoins.sk", Server.ConnectionType.TCP, 56001),
|
||||
new Server("node.mendonca.xyz", Server.ConnectionType.TCP, 50001),
|
||||
new Server("bitcoin.aranguren.org", Server.ConnectionType.TCP, 50001));
|
||||
new Server("vmd71287.contaboserver.net", Server.ConnectionType.SSL, 50002),
|
||||
new Server("btc.litepay.ch", Server.ConnectionType.SSL, 50002),
|
||||
new Server("electrum.stippy.com", Server.ConnectionType.SSL, 50002),
|
||||
new Server("xtrum.com", Server.ConnectionType.SSL, 50002),
|
||||
new Server("electrum.acinq.co", Server.ConnectionType.SSL, 50002),
|
||||
new Server("electrum2.taborsky.cz", Server.ConnectionType.SSL, 50002),
|
||||
new Server("vmd63185.contaboserver.net", Server.ConnectionType.SSL, 50002),
|
||||
new Server("electrum2.privateservers.network", Server.ConnectionType.SSL, 50002),
|
||||
new Server("electrumx.alexridevski.net", Server.ConnectionType.SSL, 50002),
|
||||
new Server("192.166.219.200", Server.ConnectionType.SSL, 50002),
|
||||
new Server("2ex.digitaleveryware.com", Server.ConnectionType.SSL, 50002),
|
||||
new Server("dxm.no-ip.biz", Server.ConnectionType.SSL, 50002),
|
||||
new Server("caleb.vegas", Server.ConnectionType.SSL, 50002));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -96,10 +93,8 @@ public class Bitcoin extends Bitcoiny {
|
||||
@Override
|
||||
public Collection<ElectrumX.Server> getServers() {
|
||||
return Arrays.asList(
|
||||
new Server("electrum.blockstream.info", Server.ConnectionType.TCP, 60001),
|
||||
new Server("electrum.blockstream.info", Server.ConnectionType.SSL, 60002),
|
||||
new Server("tn.not.fyi", Server.ConnectionType.SSL, 55002),
|
||||
new Server("electrumx-test.1209k.com", Server.ConnectionType.SSL, 50002),
|
||||
new Server("testnet.qtornado.com", Server.ConnectionType.TCP, 51001),
|
||||
new Server("testnet.qtornado.com", Server.ConnectionType.SSL, 51002),
|
||||
new Server("testnet.aranguren.org", Server.ConnectionType.TCP, 51001),
|
||||
new Server("testnet.aranguren.org", Server.ConnectionType.SSL, 51002),
|
||||
|
@@ -49,7 +49,9 @@
|
||||
},
|
||||
"featureTriggers": {
|
||||
"atFindNextTransactionFix": 275000,
|
||||
"newBlockSigHeight": 320000
|
||||
"newBlockSigHeight": 320000,
|
||||
"shareBinFix": 399000,
|
||||
"calcChainWeightTimestamp": 1616000000000
|
||||
},
|
||||
"genesisInfo": {
|
||||
"version": 4,
|
||||
|
@@ -3,12 +3,15 @@ package org.qortal.test;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.text.DecimalFormat;
|
||||
import java.text.NumberFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
import org.qortal.account.Account;
|
||||
import org.qortal.block.Block;
|
||||
import org.qortal.block.BlockChain;
|
||||
import org.qortal.data.block.BlockSummaryData;
|
||||
import org.qortal.repository.DataException;
|
||||
import org.qortal.repository.Repository;
|
||||
@@ -17,12 +20,21 @@ import org.qortal.test.common.Common;
|
||||
import org.qortal.test.common.TestAccount;
|
||||
import org.qortal.transform.Transformer;
|
||||
import org.qortal.transform.block.BlockTransformer;
|
||||
import org.qortal.utils.NTP;
|
||||
import org.junit.Before;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
public class ChainWeightTests extends Common {
|
||||
|
||||
private static final Random RANDOM = new Random();
|
||||
private static final NumberFormat FORMATTER = new DecimalFormat("0.###E0");
|
||||
|
||||
@BeforeClass
|
||||
public static void beforeClass() {
|
||||
// We need this so that NTP.getTime() in Block.calcChainWeight() doesn't return null, causing NPE
|
||||
NTP.setFixedOffset(0L);
|
||||
}
|
||||
|
||||
@Before
|
||||
public void beforeTest() throws DataException {
|
||||
@@ -89,7 +101,97 @@ public class ChainWeightTests extends Common {
|
||||
}
|
||||
}
|
||||
|
||||
// Check that a longer chain beats a shorter chain
|
||||
// Demonstrates that typical key distance ranges from roughly 1E75 to 1E77
|
||||
@Test
|
||||
public void testKeyDistances() {
|
||||
byte[] parentMinterKey = new byte[Transformer.PUBLIC_KEY_LENGTH];
|
||||
byte[] testKey = new byte[Transformer.PUBLIC_KEY_LENGTH];
|
||||
|
||||
for (int i = 0; i < 50; ++i) {
|
||||
int parentHeight = RANDOM.nextInt(50000);
|
||||
RANDOM.nextBytes(parentMinterKey);
|
||||
RANDOM.nextBytes(testKey);
|
||||
int minterLevel = RANDOM.nextInt(10) + 1;
|
||||
|
||||
BigInteger keyDistance = Block.calcKeyDistance(parentHeight, parentMinterKey, testKey, minterLevel);
|
||||
|
||||
System.out.println(String.format("Parent height: %d, minter level: %d, distance: %s",
|
||||
parentHeight,
|
||||
minterLevel,
|
||||
FORMATTER.format(keyDistance)));
|
||||
}
|
||||
}
|
||||
|
||||
// If typical key distance ranges from 1E75 to 1E77
|
||||
// then we want lots of online accounts to push a 1E75 distance
|
||||
// towards 1E77 so that it competes with a 1E77 key that has hardly any online accounts
|
||||
// 1E75 is approx. 2**249 so maybe that's a good value for Block.ACCOUNTS_COUNT_SHIFT
|
||||
@Test
|
||||
public void testMoreAccountsVersusKeyDistance() throws DataException {
|
||||
BigInteger minimumBetterKeyDistance = BigInteger.TEN.pow(77);
|
||||
BigInteger maximumWorseKeyDistance = BigInteger.TEN.pow(75);
|
||||
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
final byte[] parentMinterKey = new byte[Transformer.PUBLIC_KEY_LENGTH];
|
||||
|
||||
TestAccount betterAccount = Common.getTestAccount(repository, "bob-reward-share");
|
||||
byte[] betterKey = betterAccount.getPublicKey();
|
||||
int betterMinterLevel = Account.getRewardShareEffectiveMintingLevel(repository, betterKey);
|
||||
|
||||
TestAccount worseAccount = Common.getTestAccount(repository, "dilbert-reward-share");
|
||||
byte[] worseKey = worseAccount.getPublicKey();
|
||||
int worseMinterLevel = Account.getRewardShareEffectiveMintingLevel(repository, worseKey);
|
||||
|
||||
// This is to check that the hard-coded keys ARE actually better/worse as expected, before moving on testing more online accounts
|
||||
BigInteger betterKeyDistance;
|
||||
BigInteger worseKeyDistance;
|
||||
|
||||
int parentHeight = 0;
|
||||
do {
|
||||
++parentHeight;
|
||||
betterKeyDistance = Block.calcKeyDistance(parentHeight, parentMinterKey, betterKey, betterMinterLevel);
|
||||
worseKeyDistance = Block.calcKeyDistance(parentHeight, parentMinterKey, worseKey, worseMinterLevel);
|
||||
} while (betterKeyDistance.compareTo(minimumBetterKeyDistance) < 0 || worseKeyDistance.compareTo(maximumWorseKeyDistance) > 0);
|
||||
|
||||
System.out.println(String.format("Parent height: %d, better key distance: %s, worse key distance: %s",
|
||||
parentHeight,
|
||||
FORMATTER.format(betterKeyDistance),
|
||||
FORMATTER.format(worseKeyDistance)));
|
||||
|
||||
for (int accountsCountShift = 244; accountsCountShift <= 256; accountsCountShift += 2) {
|
||||
for (int worseAccountsCount = 1; worseAccountsCount <= 101; worseAccountsCount += 25) {
|
||||
for (int betterAccountsCount = 1; betterAccountsCount <= 1001; betterAccountsCount += 250) {
|
||||
BlockSummaryData worseKeyBlockSummary = new BlockSummaryData(parentHeight + 1, null, worseKey, betterAccountsCount);
|
||||
BlockSummaryData betterKeyBlockSummary = new BlockSummaryData(parentHeight + 1, null, betterKey, worseAccountsCount);
|
||||
|
||||
populateBlockSummaryMinterLevel(repository, worseKeyBlockSummary);
|
||||
populateBlockSummaryMinterLevel(repository, betterKeyBlockSummary);
|
||||
|
||||
BigInteger worseKeyBlockWeight = calcBlockWeight(parentHeight, parentMinterKey, worseKeyBlockSummary, accountsCountShift);
|
||||
BigInteger betterKeyBlockWeight = calcBlockWeight(parentHeight, parentMinterKey, betterKeyBlockSummary, accountsCountShift);
|
||||
|
||||
System.out.println(String.format("Shift: %d, worse key: %d accounts, %s diff; better key: %d accounts: %s diff; winner: %s",
|
||||
accountsCountShift,
|
||||
betterAccountsCount, // used with worseKey
|
||||
FORMATTER.format(worseKeyBlockWeight),
|
||||
worseAccountsCount, // used with betterKey
|
||||
FORMATTER.format(betterKeyBlockWeight),
|
||||
worseKeyBlockWeight.compareTo(betterKeyBlockWeight) > 0 ? "worse key/better accounts" : "better key/worse accounts"
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
System.out.println();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static BigInteger calcBlockWeight(int parentHeight, byte[] parentBlockSignature, BlockSummaryData blockSummaryData, int accountsCountShift) {
|
||||
BigInteger keyDistance = Block.calcKeyDistance(parentHeight, parentBlockSignature, blockSummaryData.getMinterPublicKey(), blockSummaryData.getMinterLevel());
|
||||
return BigInteger.valueOf(blockSummaryData.getOnlineAccountsCount()).shiftLeft(accountsCountShift).add(keyDistance);
|
||||
}
|
||||
|
||||
// Check that a longer chain has same weight as shorter/truncated chain
|
||||
@Test
|
||||
public void testLongerChain() throws DataException {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
@@ -97,18 +199,20 @@ public class ChainWeightTests extends Common {
|
||||
BlockSummaryData commonBlockSummary = genBlockSummary(repository, commonBlockHeight);
|
||||
byte[] commonBlockGeneratorKey = commonBlockSummary.getMinterPublicKey();
|
||||
|
||||
List<BlockSummaryData> shorterChain = genBlockSummaries(repository, 3, commonBlockSummary);
|
||||
List<BlockSummaryData> longerChain = genBlockSummaries(repository, shorterChain.size() + 1, commonBlockSummary);
|
||||
|
||||
populateBlockSummariesMinterLevels(repository, shorterChain);
|
||||
List<BlockSummaryData> longerChain = genBlockSummaries(repository, 6, commonBlockSummary);
|
||||
populateBlockSummariesMinterLevels(repository, longerChain);
|
||||
|
||||
List<BlockSummaryData> shorterChain = longerChain.subList(0, longerChain.size() / 2);
|
||||
|
||||
final int mutualHeight = commonBlockHeight - 1 + Math.min(shorterChain.size(), longerChain.size());
|
||||
|
||||
BigInteger shorterChainWeight = Block.calcChainWeight(commonBlockHeight, commonBlockGeneratorKey, shorterChain, mutualHeight);
|
||||
BigInteger longerChainWeight = Block.calcChainWeight(commonBlockHeight, commonBlockGeneratorKey, longerChain, mutualHeight);
|
||||
|
||||
assertEquals("longer chain should have greater weight", 1, longerChainWeight.compareTo(shorterChainWeight));
|
||||
if (NTP.getTime() >= BlockChain.getInstance().getCalcChainWeightTimestamp())
|
||||
assertEquals("longer chain should have same weight", 0, longerChainWeight.compareTo(shorterChainWeight));
|
||||
else
|
||||
assertEquals("longer chain should have greater weight", 1, longerChainWeight.compareTo(shorterChainWeight));
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -336,4 +336,457 @@ public class RewardTests extends Common {
|
||||
}
|
||||
}
|
||||
|
||||
/** Test rewards for level 1 and 2 accounts both pre and post the shareBinFix, including orphaning back through the feature trigger block */
|
||||
@Test
|
||||
public void testLevel1And2Rewards() throws DataException {
|
||||
Common.useSettings("test-settings-v2-reward-levels.json");
|
||||
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
|
||||
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 a couple of blocks so that we are able to orphan them later
|
||||
for (int i=0; i<2; i++)
|
||||
BlockMinter.mintTestingBlock(repository, mintingAndOnlineAccounts.toArray(new PrivateKeyAccount[0]));
|
||||
|
||||
// Ensure that the levels are as we expect
|
||||
assertEquals(1, (int) Common.getTestAccount(repository, "alice").getLevel());
|
||||
assertEquals(1, (int) Common.getTestAccount(repository, "bob").getLevel());
|
||||
assertEquals(1, (int) Common.getTestAccount(repository, "chloe").getLevel());
|
||||
assertEquals(2, (int) Common.getTestAccount(repository, "dilbert").getLevel());
|
||||
|
||||
// Ensure that only Alice is a founder
|
||||
assertEquals(1, getFlags(repository, "alice"));
|
||||
assertEquals(0, getFlags(repository, "bob"));
|
||||
assertEquals(0, getFlags(repository, "chloe"));
|
||||
assertEquals(0, getFlags(repository, "dilbert"));
|
||||
|
||||
// Now that everyone is at level 1 or 2, 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 at the correct height and block reward value
|
||||
assertEquals(6, (int) repository.getBlockRepository().getLastBlock().getHeight());
|
||||
assertEquals(10000000000L, blockReward);
|
||||
|
||||
/*
|
||||
* Alice, Chloe, and Dilbert are 'online'. Bob is offline.
|
||||
* Chloe is level 1, Dilbert is level 2.
|
||||
* One founder online (Alice, who is also level 1).
|
||||
* No legacy QORA holders.
|
||||
*
|
||||
* Chloe and Dilbert should receive equal shares of the 5% block reward for Level 1 and 2
|
||||
* Alice should receive the remainder (95%)
|
||||
*/
|
||||
|
||||
// We are after the shareBinFix feature trigger, so we expect level 1 and 2 to share the same reward (5%)
|
||||
final int level1And2SharePercent = 5_00; // 5%
|
||||
final long level1And2ShareAmount = (blockReward * level1And2SharePercent) / 100L / 100L;
|
||||
final long expectedReward = level1And2ShareAmount / 2; // The reward is split between Chloe and Dilbert
|
||||
final long expectedFounderReward = blockReward - level1And2ShareAmount; // Alice should receive the remainder
|
||||
|
||||
// Validate the balances to ensure that the correct post-shareBinFix distribution is being applied
|
||||
assertEquals(500000000, level1And2ShareAmount);
|
||||
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+expectedReward);
|
||||
AccountUtils.assertBalance(repository, "dilbert", Asset.QORT, dilbertInitialBalance+expectedReward);
|
||||
|
||||
// Now orphan the latest block. This brings us to the threshold of the shareBinFix feature trigger.
|
||||
BlockUtils.orphanBlocks(repository, 1);
|
||||
assertEquals(5, (int) repository.getBlockRepository().getLastBlock().getHeight());
|
||||
|
||||
// Ensure the latest post-fix block rewards have been subtracted and they have returned to their initial values
|
||||
AccountUtils.assertBalance(repository, "alice", Asset.QORT, aliceInitialBalance);
|
||||
AccountUtils.assertBalance(repository, "bob", Asset.QORT, bobInitialBalance); // Bob not online so his balance remains the same
|
||||
AccountUtils.assertBalance(repository, "chloe", Asset.QORT, chloeInitialBalance);
|
||||
AccountUtils.assertBalance(repository, "dilbert", Asset.QORT, dilbertInitialBalance);
|
||||
|
||||
// Orphan another block. This time, the block that was orphaned was prior to the shareBinFix feature trigger.
|
||||
BlockUtils.orphanBlocks(repository, 1);
|
||||
assertEquals(4, (int) repository.getBlockRepository().getLastBlock().getHeight());
|
||||
|
||||
// Prior to the fix, the levels were incorrectly grouped
|
||||
// Chloe should receive 100% of the level 1 reward, and Dilbert should receive 100% of the level 2+3 reward
|
||||
final int level1SharePercent = 5_00; // 5%
|
||||
final int level2And3SharePercent = 10_00; // 10%
|
||||
final long level1ShareAmountBeforeFix = (blockReward * level1SharePercent) / 100L / 100L;
|
||||
final long level2And3ShareAmountBeforeFix = (blockReward * level2And3SharePercent) / 100L / 100L;
|
||||
final long expectedFounderRewardBeforeFix = blockReward - level1ShareAmountBeforeFix - level2And3ShareAmountBeforeFix; // Alice should receive the remainder
|
||||
|
||||
// Validate the share amounts and balances
|
||||
assertEquals(500000000, level1ShareAmountBeforeFix);
|
||||
assertEquals(1000000000, level2And3ShareAmountBeforeFix);
|
||||
AccountUtils.assertBalance(repository, "alice", Asset.QORT, aliceInitialBalance-expectedFounderRewardBeforeFix);
|
||||
AccountUtils.assertBalance(repository, "bob", Asset.QORT, bobInitialBalance); // Bob not online so his balance remains the same
|
||||
AccountUtils.assertBalance(repository, "chloe", Asset.QORT, chloeInitialBalance-level1ShareAmountBeforeFix);
|
||||
AccountUtils.assertBalance(repository, "dilbert", Asset.QORT, dilbertInitialBalance-level2And3ShareAmountBeforeFix);
|
||||
|
||||
// Orphan the latest block one last time
|
||||
BlockUtils.orphanBlocks(repository, 1);
|
||||
assertEquals(3, (int) repository.getBlockRepository().getLastBlock().getHeight());
|
||||
|
||||
// Validate balances
|
||||
AccountUtils.assertBalance(repository, "alice", Asset.QORT, aliceInitialBalance-(expectedFounderRewardBeforeFix*2));
|
||||
AccountUtils.assertBalance(repository, "bob", Asset.QORT, bobInitialBalance); // Bob not online so his balance remains the same
|
||||
AccountUtils.assertBalance(repository, "chloe", Asset.QORT, chloeInitialBalance-(level1ShareAmountBeforeFix*2));
|
||||
AccountUtils.assertBalance(repository, "dilbert", Asset.QORT, dilbertInitialBalance-(level2And3ShareAmountBeforeFix*2));
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/** Test rewards for level 3 and 4 accounts */
|
||||
@Test
|
||||
public void testLevel3And4Rewards() throws DataException {
|
||||
Common.useSettings("test-settings-v2-reward-levels.json");
|
||||
|
||||
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 online
|
||||
byte[] bobRewardSharePrivateKey = AccountUtils.rewardShare(repository, "bob", "bob", 0);
|
||||
PrivateKeyAccount bobRewardShareAccount = new PrivateKeyAccount(repository, bobRewardSharePrivateKey);
|
||||
mintingAndOnlineAccounts.add(bobRewardShareAccount);
|
||||
|
||||
// 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 3 and 4
|
||||
final int minterBlocksNeeded = cumulativeBlocksByLevel.get(4) - 20; // 20 blocks before level 4, 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(3, (int) Common.getTestAccount(repository, "alice").getLevel());
|
||||
assertEquals(3, (int) Common.getTestAccount(repository, "bob").getLevel());
|
||||
assertEquals(3, (int) Common.getTestAccount(repository, "chloe").getLevel());
|
||||
assertEquals(4, (int) Common.getTestAccount(repository, "dilbert").getLevel());
|
||||
|
||||
// Now that everyone is at level 3 or 4, 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 and Chloe are level 3; Dilbert is level 4.
|
||||
* One founder online (Alice, who is also level 3).
|
||||
* No legacy QORA holders.
|
||||
*
|
||||
* Chloe, Bob and Dilbert should receive equal shares of the 10% block reward for level 3 and 4
|
||||
* Alice should receive the remainder (90%)
|
||||
*/
|
||||
|
||||
// We are after the shareBinFix feature trigger, so we expect level 3 and 4 to share the same reward (10%)
|
||||
final int level3And4SharePercent = 10_00; // 10%
|
||||
final long level3And4ShareAmount = (blockReward * level3And4SharePercent) / 100L / 100L;
|
||||
final long expectedReward = level3And4ShareAmount / 3; // The reward is split between Bob, Chloe, and Dilbert
|
||||
final long expectedFounderReward = blockReward - level3And4ShareAmount; // Alice should receive the remainder
|
||||
|
||||
// Validate the balances to ensure that the correct post-shareBinFix distribution is being applied
|
||||
AccountUtils.assertBalance(repository, "alice", Asset.QORT, aliceInitialBalance+expectedFounderReward);
|
||||
AccountUtils.assertBalance(repository, "bob", Asset.QORT, bobInitialBalance+expectedReward);
|
||||
AccountUtils.assertBalance(repository, "chloe", Asset.QORT, chloeInitialBalance+expectedReward);
|
||||
AccountUtils.assertBalance(repository, "dilbert", Asset.QORT, dilbertInitialBalance+expectedReward);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/** Test rewards for level 5 and 6 accounts */
|
||||
@Test
|
||||
public void testLevel5And6Rewards() throws DataException {
|
||||
Common.useSettings("test-settings-v2-reward-levels.json");
|
||||
|
||||
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 5 and 6
|
||||
final int minterBlocksNeeded = cumulativeBlocksByLevel.get(6) - 20; // 20 blocks before level 6, 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(5, (int) Common.getTestAccount(repository, "alice").getLevel());
|
||||
assertEquals(1, (int) Common.getTestAccount(repository, "bob").getLevel());
|
||||
assertEquals(5, (int) Common.getTestAccount(repository, "chloe").getLevel());
|
||||
assertEquals(6, (int) Common.getTestAccount(repository, "dilbert").getLevel());
|
||||
|
||||
// Now that everyone is at level 5 or 6 (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 5; Dilbert is level 6.
|
||||
* One founder online (Alice, who is also level 5).
|
||||
* No legacy QORA holders.
|
||||
*
|
||||
* Chloe and Dilbert should receive equal shares of the 15% block reward for level 5 and 6
|
||||
* Bob should receive all of the level 1 and 2 reward (5%)
|
||||
* Alice should receive the remainder (80%)
|
||||
*/
|
||||
|
||||
// We are after the shareBinFix feature trigger, so we expect level 5 and 6 to share the same reward (15%)
|
||||
final int level1And2SharePercent = 5_00; // 5%
|
||||
final int level5And6SharePercent = 15_00; // 10%
|
||||
final long level1And2ShareAmount = (blockReward * level1And2SharePercent) / 100L / 100L;
|
||||
final long level5And6ShareAmount = (blockReward * level5And6SharePercent) / 100L / 100L;
|
||||
final long expectedLevel1And2Reward = level1And2ShareAmount; // The reward is given entirely to Bob
|
||||
final long expectedLevel5And6Reward = level5And6ShareAmount / 2; // The reward is split between Chloe and Dilbert
|
||||
final long expectedFounderReward = blockReward - level1And2ShareAmount - level5And6ShareAmount; // Alice should receive the remainder
|
||||
|
||||
// Validate the balances to ensure that the correct post-shareBinFix distribution is being applied
|
||||
AccountUtils.assertBalance(repository, "alice", Asset.QORT, aliceInitialBalance+expectedFounderReward);
|
||||
AccountUtils.assertBalance(repository, "bob", Asset.QORT, bobInitialBalance+expectedLevel1And2Reward);
|
||||
AccountUtils.assertBalance(repository, "chloe", Asset.QORT, chloeInitialBalance+expectedLevel5And6Reward);
|
||||
AccountUtils.assertBalance(repository, "dilbert", Asset.QORT, dilbertInitialBalance+expectedLevel5And6Reward);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/** Test rewards for level 7 and 8 accounts */
|
||||
@Test
|
||||
public void testLevel7And8Rewards() throws DataException {
|
||||
Common.useSettings("test-settings-v2-reward-levels.json");
|
||||
|
||||
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.
|
||||
*
|
||||
* Chloe and Dilbert should receive equal shares of the 20% block reward for level 7 and 8
|
||||
* Alice should receive the remainder (80%)
|
||||
*/
|
||||
|
||||
// We are after the shareBinFix feature trigger, so we expect level 7 and 8 to share the same reward (20%)
|
||||
final int level7And8SharePercent = 20_00; // 20%
|
||||
final long level7And8ShareAmount = (blockReward * level7And8SharePercent) / 100L / 100L;
|
||||
final long expectedLevel7And8Reward = level7And8ShareAmount / 2; // The reward is split between Chloe and Dilbert
|
||||
final long expectedFounderReward = blockReward - level7And8ShareAmount; // Alice should receive the remainder
|
||||
|
||||
// Validate the balances to ensure that the correct post-shareBinFix distribution is being applied
|
||||
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+expectedLevel7And8Reward);
|
||||
AccountUtils.assertBalance(repository, "dilbert", Asset.QORT, dilbertInitialBalance+expectedLevel7And8Reward);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/** Test rewards for level 9 and 10 accounts */
|
||||
@Test
|
||||
public void testLevel9And10Rewards() throws DataException {
|
||||
Common.useSettings("test-settings-v2-reward-levels.json");
|
||||
|
||||
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.
|
||||
*
|
||||
* Chloe and Dilbert should receive equal shares of the 25% block reward for level 9 and 10
|
||||
* Bob should receive all of the level 1 and 2 reward (5%)
|
||||
* Alice should receive the remainder (70%)
|
||||
*/
|
||||
|
||||
// We are after the shareBinFix feature trigger, so we expect level 9 and 10 to share the same reward (25%)
|
||||
final int level1And2SharePercent = 5_00; // 5%
|
||||
final int level9And10SharePercent = 25_00; // 25%
|
||||
final long level1And2ShareAmount = (blockReward * level1And2SharePercent) / 100L / 100L;
|
||||
final long level9And10ShareAmount = (blockReward * level9And10SharePercent) / 100L / 100L;
|
||||
final long expectedLevel1And2Reward = level1And2ShareAmount; // The reward is given entirely to Bob
|
||||
final long expectedLevel9And10Reward = level9And10ShareAmount / 2; // The reward is split between Chloe and Dilbert
|
||||
final long expectedFounderReward = blockReward - level1And2ShareAmount - level9And10ShareAmount; // Alice should receive the remainder
|
||||
|
||||
// Validate the balances to ensure that the correct post-shareBinFix distribution is being applied
|
||||
AccountUtils.assertBalance(repository, "alice", Asset.QORT, aliceInitialBalance+expectedFounderReward);
|
||||
AccountUtils.assertBalance(repository, "bob", Asset.QORT, bobInitialBalance+expectedLevel1And2Reward);
|
||||
AccountUtils.assertBalance(repository, "chloe", Asset.QORT, chloeInitialBalance+expectedLevel9And10Reward);
|
||||
AccountUtils.assertBalance(repository, "dilbert", Asset.QORT, dilbertInitialBalance+expectedLevel9And10Reward);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private int getFlags(Repository repository, String name) throws DataException {
|
||||
TestAccount testAccount = Common.getTestAccount(repository, name);
|
||||
return repository.getAccountRepository().getAccount(testAccount.getAddress()).getFlags();
|
||||
}
|
||||
|
||||
}
|
@@ -46,7 +46,9 @@
|
||||
"newAssetPricingTimestamp": 0,
|
||||
"groupApprovalTimestamp": 0,
|
||||
"atFindNextTransactionFix": 0,
|
||||
"newBlockSigHeight": 999999
|
||||
"newBlockSigHeight": 999999,
|
||||
"shareBinFix": 999999,
|
||||
"calcChainWeightTimestamp": 0
|
||||
},
|
||||
"genesisInfo": {
|
||||
"version": 4,
|
||||
|
@@ -46,7 +46,9 @@
|
||||
"newAssetPricingTimestamp": 0,
|
||||
"groupApprovalTimestamp": 0,
|
||||
"atFindNextTransactionFix": 0,
|
||||
"newBlockSigHeight": 999999
|
||||
"newBlockSigHeight": 999999,
|
||||
"shareBinFix": 999999,
|
||||
"calcChainWeightTimestamp": 0
|
||||
},
|
||||
"genesisInfo": {
|
||||
"version": 4,
|
||||
|
@@ -46,7 +46,9 @@
|
||||
"newAssetPricingTimestamp": 0,
|
||||
"groupApprovalTimestamp": 0,
|
||||
"atFindNextTransactionFix": 0,
|
||||
"newBlockSigHeight": 999999
|
||||
"newBlockSigHeight": 999999,
|
||||
"shareBinFix": 999999,
|
||||
"calcChainWeightTimestamp": 0
|
||||
},
|
||||
"genesisInfo": {
|
||||
"version": 4,
|
||||
|
@@ -46,7 +46,9 @@
|
||||
"newAssetPricingTimestamp": 0,
|
||||
"groupApprovalTimestamp": 0,
|
||||
"atFindNextTransactionFix": 0,
|
||||
"newBlockSigHeight": 999999
|
||||
"newBlockSigHeight": 999999,
|
||||
"shareBinFix": 999999,
|
||||
"calcChainWeightTimestamp": 0
|
||||
},
|
||||
"genesisInfo": {
|
||||
"version": 4,
|
||||
|
@@ -46,7 +46,9 @@
|
||||
"newAssetPricingTimestamp": 0,
|
||||
"groupApprovalTimestamp": 0,
|
||||
"atFindNextTransactionFix": 0,
|
||||
"newBlockSigHeight": 999999
|
||||
"newBlockSigHeight": 999999,
|
||||
"shareBinFix": 999999,
|
||||
"calcChainWeightTimestamp": 0
|
||||
},
|
||||
"genesisInfo": {
|
||||
"version": 4,
|
||||
|
75
src/test/resources/test-chain-v2-reward-levels.json
Normal file
75
src/test/resources/test-chain-v2-reward-levels.json
Normal file
@@ -0,0 +1,75 @@
|
||||
{
|
||||
"isTestChain": true,
|
||||
"blockTimestampMargin": 500,
|
||||
"transactionExpiryPeriod": 86400000,
|
||||
"maxBlockSize": 2097152,
|
||||
"maxBytesPerUnitFee": 1024,
|
||||
"unitFee": "0.1",
|
||||
"requireGroupForApproval": false,
|
||||
"minAccountLevelToRewardShare": 5,
|
||||
"maxRewardSharesPerMintingAccount": 20,
|
||||
"founderEffectiveMintingLevel": 10,
|
||||
"onlineAccountSignaturesMinLifetime": 3600000,
|
||||
"onlineAccountSignaturesMaxLifetime": 86400000,
|
||||
"rewardsByHeight": [
|
||||
{ "height": 1, "reward": 100 },
|
||||
{ "height": 11, "reward": 10 },
|
||||
{ "height": 21, "reward": 1 }
|
||||
],
|
||||
"sharesByLevel": [
|
||||
{ "levels": [ 1, 2 ], "share": 0.05 },
|
||||
{ "levels": [ 3, 4 ], "share": 0.10 },
|
||||
{ "levels": [ 5, 6 ], "share": 0.15 },
|
||||
{ "levels": [ 7, 8 ], "share": 0.20 },
|
||||
{ "levels": [ 9, 10 ], "share": 0.25 }
|
||||
],
|
||||
"qoraHoldersShare": 0.20,
|
||||
"qoraPerQortReward": 250,
|
||||
"blocksNeededByLevel": [ 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 ],
|
||||
"blockTimingsByHeight": [
|
||||
{ "height": 1, "target": 60000, "deviation": 30000, "power": 0.2 }
|
||||
],
|
||||
"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": 6,
|
||||
"calcChainWeightTimestamp": 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": "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": "QgV4s3xnzLhVBEJxcYui4u4q11yhUHsd9v", "level": 1 },
|
||||
{ "type": "ACCOUNT_LEVEL", "target": "QixPbJUwsaHsVEofJdozU9zgVqkK6aYhrK", "level": 1 },
|
||||
{ "type": "ACCOUNT_LEVEL", "target": "QaUpHNhT3Ygx6avRiKobuLdusppR5biXjL", "level": 1 },
|
||||
{ "type": "ACCOUNT_LEVEL", "target": "Qci5m9k4rcwe4ruKrZZQKka4FzUUMut3er", "level": 2 }
|
||||
]
|
||||
}
|
||||
}
|
@@ -46,7 +46,9 @@
|
||||
"newAssetPricingTimestamp": 0,
|
||||
"groupApprovalTimestamp": 0,
|
||||
"atFindNextTransactionFix": 0,
|
||||
"newBlockSigHeight": 999999
|
||||
"newBlockSigHeight": 999999,
|
||||
"shareBinFix": 999999,
|
||||
"calcChainWeightTimestamp": 0
|
||||
},
|
||||
"genesisInfo": {
|
||||
"version": 4,
|
||||
|
@@ -46,7 +46,9 @@
|
||||
"newAssetPricingTimestamp": 0,
|
||||
"groupApprovalTimestamp": 0,
|
||||
"atFindNextTransactionFix": 0,
|
||||
"newBlockSigHeight": 999999
|
||||
"newBlockSigHeight": 999999,
|
||||
"shareBinFix": 999999,
|
||||
"calcChainWeightTimestamp": 0
|
||||
},
|
||||
"genesisInfo": {
|
||||
"version": 4,
|
||||
|
7
src/test/resources/test-settings-v2-reward-levels.json
Normal file
7
src/test/resources/test-settings-v2-reward-levels.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"restrictedApi": false,
|
||||
"blockchainConfig": "src/test/resources/test-chain-v2-reward-levels.json",
|
||||
"wipeUnconfirmedOnStart": false,
|
||||
"testNtpOffset": 0,
|
||||
"minPeers": 0
|
||||
}
|
Reference in New Issue
Block a user