Skip to content

Commit

Permalink
Fix batch reward
Browse files Browse the repository at this point in the history
  • Loading branch information
AlphaX-Qortal committed Nov 25, 2024
1 parent d89f7ad commit c010ab4
Show file tree
Hide file tree
Showing 3 changed files with 65 additions and 44 deletions.
99 changes: 57 additions & 42 deletions src/main/java/org/qortal/block/Block.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,7 @@
import org.qortal.data.block.BlockTransactionData;
import org.qortal.data.network.OnlineAccountData;
import org.qortal.data.transaction.TransactionData;
import org.qortal.repository.ATRepository;
import org.qortal.repository.DataException;
import org.qortal.repository.Repository;
import org.qortal.repository.TransactionRepository;
import org.qortal.repository.*;
import org.qortal.settings.Settings;
import org.qortal.transaction.AtTransaction;
import org.qortal.transaction.Transaction;
Expand Down Expand Up @@ -144,6 +141,7 @@ public static class ExpandedAccount {
private final Account mintingAccount;
private final AccountData mintingAccountData;
private final boolean isMinterFounder;
private final boolean isMinterMember;

private final Account recipientAccount;
private final AccountData recipientAccountData;
Expand All @@ -157,6 +155,7 @@ public static class ExpandedAccount {
this.isMinterFounder = Account.isFounder(mintingAccountData.getFlags());

this.isRecipientAlsoMinter = this.rewardShareData.getRecipient().equals(this.mintingAccount.getAddress());
this.isMinterMember = repository.getGroupRepository().memberExists(BlockChain.getInstance().getMintingGroupId(), this.mintingAccount.getAddress());

if (this.isRecipientAlsoMinter) {
// Self-share: minter is also recipient
Expand All @@ -181,7 +180,7 @@ public Account getRecipientAccount() {
* <p>
* This is a method, not a final variable, because account's level can change between construction and call,
* e.g. during Block.process() where account levels are bumped right before Block.distributeBlockReward().
*
*
* @return account-level share "bin" from blockchain config, or null if founder / none found
*/
public AccountLevelShareBin getShareBin(int blockHeight) {
Expand All @@ -192,6 +191,11 @@ public AccountLevelShareBin getShareBin(int blockHeight) {
if (accountLevel <= 0)
return null; // level 0 isn't included in any share bins

if (blockHeight >= BlockChain.getInstance().getFixBatchRewardHeight()) {
if (!this.isMinterMember)
return null; // not member of minter group isn't included in any share bins
}

// Select the correct set of share bins based on block height
final BlockChain blockChain = BlockChain.getInstance();
final AccountLevelShareBin[] shareBinsByLevel = (blockHeight >= blockChain.getSharesByLevelV2Height()) ?
Expand Down Expand Up @@ -262,7 +266,7 @@ public long distribute(long accountAmount, Map<String, Long> balanceChanges) {
* Constructs new Block without loading transactions and AT states.
* <p>
* Transactions and AT states are loaded on first call to getTransactions() or getATStates() respectively.
*
*
* @param repository
* @param blockData
*/
Expand Down Expand Up @@ -333,7 +337,7 @@ public Block(Repository repository, BlockData blockData, List<TransactionData> t

/**
* Constructs new Block with empty transaction list, using passed minter account.
*
*
* @param repository
* @param blockData
* @param minter
Expand All @@ -351,7 +355,7 @@ private Block(Repository repository, BlockData blockData, PrivateKeyAccount mint
* This constructor typically used when minting a new block.
* <p>
* Note that CIYAM ATs will be executed and AT-Transactions prepended to this block, along with AT state data and fees.
*
*
* @param repository
* @param parentBlockData
* @param minter
Expand All @@ -377,7 +381,7 @@ public static Block mint(Repository repository, BlockData parentBlockData, Priva
byte[] encodedOnlineAccounts = new byte[0];
int onlineAccountsCount = 0;
byte[] onlineAccountsSignatures = null;

if (isBatchRewardDistributionBlock(height)) {
// Batch reward distribution block - copy online accounts from recent block with highest online accounts count

Expand Down Expand Up @@ -512,7 +516,7 @@ else if (isOnlineAccountsBlock(height)) {
* Mints new block using this block as template, but with different minting account.
* <p>
* NOTE: uses the same transactions list, AT states, etc.
*
*
* @param minter
* @return
* @throws DataException
Expand Down Expand Up @@ -598,7 +602,7 @@ public void clearOnlineAccountsValidationCache() {

/**
* Return composite block signature (minterSignature + transactionsSignature).
*
*
* @return byte[], or null if either component signature is null.
*/
public byte[] getSignature() {
Expand All @@ -613,7 +617,7 @@ public byte[] getSignature() {
* <p>
* We're starting with version 4 as a nod to being newer than successor Qora,
* whose latest block version was 3.
*
*
* @return 1, 2, 3 or 4
*/
public int getNextBlockVersion() {
Expand All @@ -627,7 +631,7 @@ public int getNextBlockVersion() {
* Return block's transactions.
* <p>
* If the block was loaded from repository then it's possible this method will call the repository to fetch the transactions if not done already.
*
*
* @return
* @throws DataException
*/
Expand Down Expand Up @@ -661,7 +665,7 @@ public List<Transaction> getTransactions() throws DataException {
* If the block was loaded from repository then it's possible this method will call the repository to fetch the AT states if not done already.
* <p>
* <b>Note:</b> AT states fetched from repository only contain summary info, not actual data like serialized state data or AT creation timestamps!
*
*
* @return
* @throws DataException
*/
Expand Down Expand Up @@ -697,7 +701,7 @@ public byte[] getAtStatesHash() {
* <p>
* Typically called as part of Block.process() or Block.orphan()
* so ideally after any calls to Block.isValid().
*
*
* @throws DataException
*/
public List<ExpandedAccount> getExpandedAccounts() throws DataException {
Expand All @@ -715,8 +719,18 @@ public List<ExpandedAccount> getExpandedAccounts() throws DataException {

List<ExpandedAccount> expandedAccounts = new ArrayList<>();

for (RewardShareData rewardShare : this.cachedOnlineRewardShares)
expandedAccounts.add(new ExpandedAccount(repository, rewardShare));
for (RewardShareData rewardShare : this.cachedOnlineRewardShares) {
if (this.getBlockData().getHeight() < BlockChain.getInstance().getFixBatchRewardHeight()) {
expandedAccounts.add(new ExpandedAccount(repository, rewardShare));
}
if (this.getBlockData().getHeight() >= BlockChain.getInstance().getFixBatchRewardHeight()) {
boolean isMinterGroupMember = repository.getGroupRepository().memberExists(BlockChain.getInstance().getMintingGroupId(), rewardShare.getMinter());
if (isMinterGroupMember) {
expandedAccounts.add(new ExpandedAccount(repository, rewardShare));
}
}
}


this.cachedExpandedAccounts = expandedAccounts;

Expand All @@ -727,7 +741,7 @@ public List<ExpandedAccount> getExpandedAccounts() throws DataException {

/**
* Load parent block's data from repository via this block's reference.
*
*
* @return parent's BlockData, or null if no parent found
* @throws DataException
*/
Expand All @@ -741,7 +755,7 @@ public BlockData getParent() throws DataException {

/**
* Load child block's data from repository via this block's signature.
*
*
* @return child's BlockData, or null if no parent found
* @throws DataException
*/
Expand All @@ -761,7 +775,7 @@ public BlockData getChild() throws DataException {
* Used when constructing a new block during minting.
* <p>
* Requires block's {@code minter} being a {@code PrivateKeyAccount} so block's transactions signature can be recalculated.
*
*
* @param transactionData
* @return true if transaction successfully added to block, false otherwise
* @throws IllegalStateException
Expand Down Expand Up @@ -814,7 +828,7 @@ public boolean addTransaction(TransactionData transactionData) {
* Used when constructing a new block during minting.
* <p>
* Requires block's {@code minter} being a {@code PrivateKeyAccount} so block's transactions signature can be recalculated.
*
*
* @param transactionData
* @throws IllegalStateException
* if block's {@code minter} is not a {@code PrivateKeyAccount}.
Expand Down Expand Up @@ -859,7 +873,7 @@ public void deleteTransaction(TransactionData transactionData) {
* previous block's minter signature + minter's public key + (encoded) online-accounts data
* <p>
* (Previous block's minter signature is extracted from this block's reference).
*
*
* @throws IllegalStateException
* if block's {@code minter} is not a {@code PrivateKeyAccount}.
* @throws RuntimeException
Expand All @@ -876,7 +890,7 @@ protected void calcMinterSignature() {
* Recalculate block's transactions signature.
* <p>
* Requires block's {@code minter} being a {@code PrivateKeyAccount}.
*
*
* @throws IllegalStateException
* if block's {@code minter} is not a {@code PrivateKeyAccount}.
* @throws RuntimeException
Expand Down Expand Up @@ -998,7 +1012,7 @@ public static long calcMinimumTimestamp(BlockData parentBlockData) {
* Recalculate block's minter and transactions signatures, thus giving block full signature.
* <p>
* Note: Block instance must have been constructed with a <tt>PrivateKeyAccount</tt> minter or this call will throw an <tt>IllegalStateException</tt>.
*
*
* @throws IllegalStateException
* if block's {@code minter} is not a {@code PrivateKeyAccount}.
*/
Expand All @@ -1011,7 +1025,7 @@ public void sign() {

/**
* Returns whether this block's signatures are valid.
*
*
* @return true if both minter and transaction signatures are valid, false otherwise
*/
public boolean isSignatureValid() {
Expand All @@ -1035,7 +1049,7 @@ public boolean isSignatureValid() {
* <p>
* Used by BlockMinter to check whether it's time to mint a new block,
* and also used by Block.isValid for checks (if not a testchain).
*
*
* @return ValidationResult.OK if timestamp valid, or some other ValidationResult otherwise.
* @throws DataException
*/
Expand Down Expand Up @@ -1215,7 +1229,7 @@ public ValidationResult areOnlineAccountsValid() throws DataException {
* <p>
* Checks block's transactions by testing their validity then processing them.<br>
* Hence uses a repository savepoint during execution.
*
*
* @return ValidationResult.OK if block is valid, or some other ValidationResult otherwise.
* @throws DataException
*/
Expand Down Expand Up @@ -1386,7 +1400,7 @@ private ValidationResult areTransactionsValid() throws DataException {
* <p>
* NOTE: will execute ATs locally if not already done.<br>
* This is so we have locally-generated AT states for comparison.
*
*
* @return OK, or some AT-related validation result
* @throws DataException
*/
Expand Down Expand Up @@ -1462,11 +1476,11 @@ private ValidationResult areAtsValid() throws DataException {
* Note: this method does not store new AT state data into repository - that is handled by <tt>process()</tt>.
* <p>
* This method is not needed if fetching an existing block from the repository as AT state data will be loaded from repository as well.
*
*
* @see #isValid()
*
*
* @throws DataException
*
*
*/
private void executeATs() throws DataException {
// We're expecting a lack of AT state data at this point.
Expand Down Expand Up @@ -1538,7 +1552,7 @@ public void preProcess() throws DataException {

/**
* Process block, and its transactions, adding them to the blockchain.
*
*
* @throws DataException
*/
public void process() throws DataException {
Expand Down Expand Up @@ -1839,7 +1853,7 @@ protected void linkTransactionsToBlock() throws DataException {

/**
* Removes block from blockchain undoing transactions and adding them to unconfirmed pile.
*
*
* @throws DataException
*/
public void orphan() throws DataException {
Expand Down Expand Up @@ -1879,7 +1893,7 @@ public void orphan() throws DataException {
SelfSponsorshipAlgoV3Block.orphanAccountPenalties(this);
}
}

// Account levels and block rewards are only processed/orphaned on block reward distribution blocks
if (this.isRewardDistributionBlock()) {
// Block rewards, including transaction fees, removed after transactions undone
Expand Down Expand Up @@ -2213,6 +2227,7 @@ protected void distributeBlockReward(long totalAmount) throws DataException {
List<AccountBalanceData> accountBalanceDeltas = balanceChanges.entrySet().stream()
.map(entry -> new AccountBalanceData(entry.getKey(), Asset.QORT, entry.getValue()))
.collect(Collectors.toList());
LOGGER.trace("Account Balance Deltas: {}", accountBalanceDeltas);
this.repository.getAccountRepository().modifyAssetBalances(accountBalanceDeltas);
}

Expand All @@ -2225,30 +2240,30 @@ protected List<BlockRewardCandidate> determineBlockRewardCandidates(boolean isPr

/*
* Distribution rules:
*
*
* Distribution is based on the minting account of 'online' reward-shares.
*
*
* If ANY founders are online, then they receive the leftover non-distributed reward.
* If NO founders are online, then account-level-based rewards are scaled up so 100% of reward is allocated.
*
*
* If ANY non-maxxed legacy QORA holders exist then they are always allocated their fixed share (e.g. 20%).
*
*
* There has to be either at least one 'online' account for blocks to be minted
* so there is always either one account-level-based or founder reward candidate.
*
*
* Examples:
*
*
* With at least one founder online:
* Level 1/2 accounts: 5%
* Legacy QORA holders: 20%
* Founders: ~75%
*
*
* No online founders:
* Level 1/2 accounts: 5%
* Level 5/6 accounts: 15%
* Legacy QORA holders: 20%
* Total: 40%
*
*
* After scaling account-level-based shares to fill 100%:
* Level 1/2 accounts: 20%
* Level 5/6 accounts: 60%
Expand Down
7 changes: 6 additions & 1 deletion src/main/java/org/qortal/block/BlockChain.java
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,8 @@ public enum FeatureTrigger {
enableRewardshareHeight,
onlyMintWithNameHeight,
removeOnlyMintWithNameHeight,
groupMemberCheckHeight
groupMemberCheckHeight,
fixBatchRewardHeight
}

// Custom transaction fees
Expand Down Expand Up @@ -657,6 +658,10 @@ public int getGroupMemberCheckHeight() {
return this.featureTriggers.get(FeatureTrigger.groupMemberCheckHeight.name()).intValue();
}

public int getFixBatchRewardHeight() {
return this.featureTriggers.get(FeatureTrigger.fixBatchRewardHeight.name()).intValue();
}

// More complex getters for aspects that change by height or timestamp

public long getRewardAtHeight(int ourHeight) {
Expand Down
3 changes: 2 additions & 1 deletion src/main/resources/blockchain.json
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,8 @@
"enableRewardshareHeight": 1905100,
"onlyMintWithNameHeight": 1900300,
"removeOnlyMintWithNameHeight": 1935500,
"groupMemberCheckHeight": 1902700
"groupMemberCheckHeight": 1902700,
"fixBatchRewardHeight": 9999999
},
"checkpoints": [
{ "height": 1136300, "signature": "3BbwawEF2uN8Ni5ofpJXkukoU8ctAPxYoFB7whq9pKfBnjfZcpfEJT4R95NvBDoTP8WDyWvsUvbfHbcr9qSZuYpSKZjUQTvdFf6eqznHGEwhZApWfvXu6zjGCxYCp65F4jsVYYJjkzbjmkCg5WAwN5voudngA23kMK6PpTNygapCzXt" }
Expand Down

0 comments on commit c010ab4

Please sign in to comment.