3
0
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:
catbref 2019-03-11 07:45:14 +00:00
parent 3c06d358b7
commit 9b859f3efd
3 changed files with 264 additions and 9 deletions

View File

@ -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;
}
/**

View File

@ -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() {

View 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);
}
}