mirror of
https://github.com/Qortal/qortal.git
synced 2025-02-12 02:05:50 +00:00
Interim minting commit with block rewards (untested)
+ minting long-term simulator + Maven pom.xml changes for Eclipse IDE
This commit is contained in:
parent
3c06d358b7
commit
9b859f3efd
@ -18,7 +18,9 @@ import org.qora.account.PrivateKeyAccount;
|
||||
import org.qora.account.PublicKeyAccount;
|
||||
import org.qora.asset.Asset;
|
||||
import org.qora.at.AT;
|
||||
import org.qora.block.BlockChain.RewardsByHeight;
|
||||
import org.qora.crypto.Crypto;
|
||||
import org.qora.data.account.ProxyForgerData;
|
||||
import org.qora.data.at.ATData;
|
||||
import org.qora.data.at.ATStateData;
|
||||
import org.qora.data.block.BlockData;
|
||||
@ -962,13 +964,20 @@ public class Block {
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Process block, and its transactions, adding them to the blockchain.
|
||||
*
|
||||
* @throws DataException
|
||||
*/
|
||||
public void process() throws DataException {
|
||||
// Set our block's height
|
||||
int blockchainHeight = this.repository.getBlockRepository().getBlockchainHeight();
|
||||
this.blockData.setHeight(blockchainHeight + 1);
|
||||
|
||||
// Block rewards go before transactions processed
|
||||
processBlockRewards();
|
||||
|
||||
// Process transactions (we'll link them to this block after saving the block itself)
|
||||
// AT-generated transactions are already added to our transactions so no special handling is needed here.
|
||||
List<Transaction> transactions = this.getTransactions();
|
||||
@ -980,9 +989,6 @@ public class Block {
|
||||
if (blockFee.compareTo(BigDecimal.ZERO) > 0)
|
||||
this.generator.setConfirmedBalance(Asset.QORA, this.generator.getConfirmedBalance(Asset.QORA).add(blockFee));
|
||||
|
||||
// Block rewards go here
|
||||
processBlockRewards();
|
||||
|
||||
// Process AT fees and save AT states into repository
|
||||
ATRepository atRepository = this.repository.getATRepository();
|
||||
for (ATStateData atState : this.getATStates()) {
|
||||
@ -995,12 +1001,10 @@ public class Block {
|
||||
}
|
||||
|
||||
// Link block into blockchain by fetching signature of highest block and setting that as our reference
|
||||
int blockchainHeight = this.repository.getBlockRepository().getBlockchainHeight();
|
||||
BlockData latestBlockData = this.repository.getBlockRepository().fromHeight(blockchainHeight);
|
||||
if (latestBlockData != null)
|
||||
this.blockData.setReference(latestBlockData.getSignature());
|
||||
|
||||
this.blockData.setHeight(blockchainHeight + 1);
|
||||
this.repository.getBlockRepository().save(this.blockData);
|
||||
|
||||
// Link transactions to this block, thus removing them from unconfirmed transactions list.
|
||||
@ -1023,7 +1027,28 @@ public class Block {
|
||||
}
|
||||
|
||||
protected void processBlockRewards() throws DataException {
|
||||
// NOP for vanilla qora-core
|
||||
BigDecimal reward = getRewardAtHeight(this.blockData.getHeight());
|
||||
|
||||
// No reward for our height?
|
||||
if (reward == null)
|
||||
return;
|
||||
|
||||
// Is generator public key actually a proxy forge key?
|
||||
ProxyForgerData proxyForgerData = this.repository.getAccountRepository().getProxyForgeData(this.blockData.getGeneratorPublicKey());
|
||||
if (proxyForgerData != null) {
|
||||
// Split reward to forger and recipient;
|
||||
Account recipient = new Account(this.repository, proxyForgerData.getRecipient());
|
||||
BigDecimal recipientShare = reward.multiply(proxyForgerData.getShare());
|
||||
recipient.setConfirmedBalance(Asset.QORA, recipient.getConfirmedBalance(Asset.QORA).add(recipientShare));
|
||||
|
||||
Account forger = new PublicKeyAccount(this.repository, proxyForgerData.getForgerPublicKey());
|
||||
BigDecimal forgerShare = reward.subtract(recipientShare);
|
||||
forger.setConfirmedBalance(Asset.QORA, forger.getConfirmedBalance(Asset.QORA).add(forgerShare));
|
||||
return;
|
||||
}
|
||||
|
||||
// Give block reward to generator
|
||||
this.generator.setConfirmedBalance(Asset.QORA, this.generator.getConfirmedBalance(Asset.QORA).add(reward));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1051,7 +1076,7 @@ public class Block {
|
||||
this.repository.getTransactionRepository().deleteParticipants(transaction.getTransactionData());
|
||||
}
|
||||
|
||||
// Block rewards removed here
|
||||
// Block rewards removed after transactions undone
|
||||
orphanBlockRewards();
|
||||
|
||||
// If fees are non-zero then remove fees from generator's balance
|
||||
@ -1075,7 +1100,43 @@ public class Block {
|
||||
}
|
||||
|
||||
protected void orphanBlockRewards() throws DataException {
|
||||
// NOP for vanilla qora-core
|
||||
BigDecimal reward = getRewardAtHeight(this.blockData.getHeight());
|
||||
|
||||
// No reward for our height?
|
||||
if (reward == null)
|
||||
return;
|
||||
|
||||
// Is generator public key actually a proxy forge key?
|
||||
ProxyForgerData proxyForgerData = this.repository.getAccountRepository().getProxyForgeData(this.blockData.getGeneratorPublicKey());
|
||||
if (proxyForgerData != null) {
|
||||
// Split reward from forger and recipient;
|
||||
Account recipient = new Account(this.repository, proxyForgerData.getRecipient());
|
||||
BigDecimal recipientShare = reward.multiply(proxyForgerData.getShare());
|
||||
recipient.setConfirmedBalance(Asset.QORA, recipient.getConfirmedBalance(Asset.QORA).subtract(recipientShare));
|
||||
|
||||
Account forger = new PublicKeyAccount(this.repository, proxyForgerData.getForgerPublicKey());
|
||||
BigDecimal forgerShare = reward.subtract(recipientShare);
|
||||
forger.setConfirmedBalance(Asset.QORA, forger.getConfirmedBalance(Asset.QORA).subtract(forgerShare));
|
||||
return;
|
||||
}
|
||||
|
||||
// Take block reward from generator
|
||||
this.generator.setConfirmedBalance(Asset.QORA, this.generator.getConfirmedBalance(Asset.QORA).subtract(reward));
|
||||
}
|
||||
|
||||
protected BigDecimal getRewardAtHeight(int ourHeight) {
|
||||
List<RewardsByHeight> rewardsByHeight = BlockChain.getInstance().getBlockRewardsByHeight();
|
||||
|
||||
// No rewards configured?
|
||||
if (rewardsByHeight == null)
|
||||
return null;
|
||||
|
||||
// Scan through for reward at our height
|
||||
for (RewardsByHeight rewardInfo : rewardsByHeight)
|
||||
if (rewardInfo.height <= ourHeight)
|
||||
return rewardInfo.reward;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -7,6 +7,7 @@ import java.io.Reader;
|
||||
import java.math.BigDecimal;
|
||||
import java.math.MathContext;
|
||||
import java.sql.SQLException;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.xml.bind.JAXBContext;
|
||||
@ -88,6 +89,13 @@ public class BlockChain {
|
||||
/** Whether only one registered name is allowed per account. */
|
||||
private boolean oneNamePerAccount = false;
|
||||
|
||||
/** Block rewards by block height */
|
||||
public static class RewardsByHeight {
|
||||
public int height;
|
||||
public BigDecimal reward;
|
||||
}
|
||||
List<RewardsByHeight> rewardsByHeight;
|
||||
|
||||
// Constructors, etc.
|
||||
|
||||
private BlockChain() {
|
||||
@ -222,6 +230,10 @@ public class BlockChain {
|
||||
return this.oneNamePerAccount;
|
||||
}
|
||||
|
||||
public List<RewardsByHeight> getBlockRewardsByHeight() {
|
||||
return this.rewardsByHeight;
|
||||
}
|
||||
|
||||
// Convenience methods for specific blockchain feature triggers
|
||||
|
||||
public long getMessageReleaseHeight() {
|
||||
|
182
src/main/java/org/qora/mintsim.java
Normal file
182
src/main/java/org/qora/mintsim.java
Normal file
@ -0,0 +1,182 @@
|
||||
package org.qora;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
public class mintsim {
|
||||
|
||||
private static final int NUMBER_BLOCKS = 5_000_000;
|
||||
private static final double GRANT_PROB = 0.001;
|
||||
private static final int BLOCK_HISTORY = 0;
|
||||
private static final int WEIGHTING = 2;
|
||||
|
||||
private static final int TOP_MINTERS_SIZE = 200;
|
||||
|
||||
private static final Random random = new Random();
|
||||
private static List<TierInfo> tiers = new ArrayList<>();
|
||||
private static List<Account> accounts = new ArrayList<>();
|
||||
private static List<Integer> blockMinters = new ArrayList<>();
|
||||
|
||||
private static List<Integer> accountsWithGrants = new ArrayList<>();
|
||||
|
||||
public static class TierInfo {
|
||||
public final int maxAccounts;
|
||||
public final int minBlocks;
|
||||
public int numberAccounts;
|
||||
|
||||
public TierInfo(int maxAccounts, int minBlocks) {
|
||||
this.maxAccounts = maxAccounts;
|
||||
this.minBlocks = minBlocks;
|
||||
this.numberAccounts = 0;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Account {
|
||||
public final int tierIndex;
|
||||
public int blocksForged;
|
||||
public int rightsGranted;
|
||||
|
||||
public Account(int tierIndex) {
|
||||
this.tierIndex = tierIndex;
|
||||
this.blocksForged = 0;
|
||||
this.rightsGranted = 0;
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String args[]) {
|
||||
if (args.length < 2 || (args.length % 2) != 0) {
|
||||
System.err.println("usage: mintsim <tier1-min-blocks> <number-tier1-accounts> [<tier2-min-blocks> <max-tier2-per-tier1> [...]]");
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
try {
|
||||
int argIndex = 0;
|
||||
do {
|
||||
int minBlocks = Integer.valueOf(args[argIndex++]);
|
||||
int maxAccounts = Integer.valueOf(args[argIndex++]);
|
||||
|
||||
tiers.add(new TierInfo(maxAccounts, minBlocks));
|
||||
} while (argIndex < args.length);
|
||||
} catch (NumberFormatException e) {
|
||||
System.err.println("Can't parse number?");
|
||||
System.exit(2);
|
||||
}
|
||||
|
||||
// Print summary
|
||||
System.out.println(String.format("Number of tiers: %d", tiers.size()));
|
||||
|
||||
for (int i = 0; i < tiers.size(); ++i) {
|
||||
TierInfo tier = tiers.get(i);
|
||||
System.out.println(String.format("Tier %d:", i));
|
||||
System.out.println(String.format("\tMinimum forged blocks to grant right: %d", tier.minBlocks));
|
||||
System.out.println(String.format("\tMaximum tier%d grants: %d", i + 1, tier.maxAccounts));
|
||||
}
|
||||
|
||||
TierInfo initialTier = tiers.get(0);
|
||||
|
||||
int totalAccounts = initialTier.maxAccounts;
|
||||
for (int i = 1; i < tiers.size(); ++i)
|
||||
totalAccounts *= 1 + tiers.get(i).maxAccounts;
|
||||
|
||||
System.out.println(String.format("Total accounts: %d", totalAccounts));
|
||||
|
||||
// Create initial accounts
|
||||
initialTier.numberAccounts = initialTier.maxAccounts;
|
||||
for (int i = 0; i < initialTier.maxAccounts; ++i)
|
||||
accounts.add(new Account(0));
|
||||
|
||||
for (int height = 1; height < NUMBER_BLOCKS; ++height) {
|
||||
int minterId = pickMinterId();
|
||||
Account minter = accounts.get(minterId);
|
||||
|
||||
++minter.blocksForged;
|
||||
blockMinters.add(minterId);
|
||||
|
||||
if (minter.tierIndex < tiers.size() - 1) {
|
||||
TierInfo nextTier = tiers.get(minter.tierIndex + 1);
|
||||
|
||||
// Minter just reached threshold to grant rights
|
||||
if (minter.blocksForged == nextTier.minBlocks)
|
||||
accountsWithGrants.add(minterId);
|
||||
}
|
||||
|
||||
List<Integer> accountsToRemove = new ArrayList<>();
|
||||
// Do any account with spare grants want to grant?
|
||||
for (int granterId : accountsWithGrants) {
|
||||
if (random.nextDouble() >= GRANT_PROB)
|
||||
continue;
|
||||
|
||||
Account granter = accounts.get(granterId);
|
||||
TierInfo nextTier = tiers.get(granter.tierIndex + 1);
|
||||
|
||||
accounts.add(new Account(granter.tierIndex + 1));
|
||||
|
||||
++nextTier.numberAccounts;
|
||||
++granter.rightsGranted;
|
||||
|
||||
if (granter.rightsGranted == nextTier.maxAccounts)
|
||||
accountsToRemove.add(granterId);
|
||||
}
|
||||
|
||||
// Remove granters that have used their allowance
|
||||
accountsWithGrants.removeAll(accountsToRemove);
|
||||
|
||||
if (height % 100000 == 0) {
|
||||
System.out.println(String.format("Summary after block %d:", height));
|
||||
for (int i = 0; i < tiers.size(); ++i)
|
||||
System.out.println(String.format("\tTier %d: number of accounts: %d", i, tiers.get(i).numberAccounts));
|
||||
}
|
||||
}
|
||||
|
||||
// Top minters
|
||||
List<Integer> topMinters = new ArrayList<>();
|
||||
for (int i = 0; i < accounts.size(); ++i) {
|
||||
topMinters.add(i);
|
||||
topMinters.sort((a, b) -> Integer.compare(accounts.get(b).blocksForged, accounts.get(a).blocksForged));
|
||||
|
||||
if (topMinters.size() > TOP_MINTERS_SIZE)
|
||||
topMinters.remove(TOP_MINTERS_SIZE);
|
||||
}
|
||||
|
||||
System.out.println(String.format("Top %d minters:", TOP_MINTERS_SIZE));
|
||||
for (int i = 0; i < topMinters.size(); ++i) {
|
||||
int topMinterId = topMinters.get(i);
|
||||
Account topMinter = accounts.get(topMinterId);
|
||||
System.out.println(String.format("\tAccount %d (tier %d) has minted %d blocks", topMinterId, topMinter.tierIndex, topMinter.blocksForged));
|
||||
}
|
||||
|
||||
for (int i = 0; i < tiers.size(); ++i)
|
||||
System.out.println(String.format("Tier %d: number of accounts: %d", i, tiers.get(i).numberAccounts));
|
||||
}
|
||||
|
||||
private static int pickMinterId() {
|
||||
// There might not be enough block history yet...
|
||||
final int blockHistory = Math.min(BLOCK_HISTORY, blockMinters.size());
|
||||
|
||||
// Weighting (W)
|
||||
|
||||
// An account that HASN'T forged in the last X blocks has 1 standard chance to forge
|
||||
// but an account that HAS forged Y in the last X blocks has 1 + (Y / X) * W chances to forge
|
||||
// e.g. forged 25 in last 100 blocks, with weighting 8, gives (25 / 100) * 8 = 2 extra chances
|
||||
|
||||
// So in X blocks there will be X * W extra chances.
|
||||
// We pick winning number from (number-of-accounts + X * W) chances
|
||||
int totalChances = accounts.size() + blockHistory * WEIGHTING;
|
||||
int winningNumber = random.nextInt(totalChances);
|
||||
|
||||
// Simple case if winning number is less than number of accounts,
|
||||
// otherwise we need to handle extra chances for accounts that have forged in last X blocks.
|
||||
if (winningNumber < accounts.size())
|
||||
return winningNumber;
|
||||
|
||||
// Handling extra chances
|
||||
|
||||
// We can work out which block in last X blocks as each block is worth W chances
|
||||
int blockOffset = (winningNumber - accounts.size()) / WEIGHTING;
|
||||
int blockIndex = blockMinters.size() - 1 - blockOffset;
|
||||
|
||||
return blockMinters.get(blockIndex);
|
||||
}
|
||||
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user