From c010ab47db9a80a7289654bb9b16d814b2b9ece4 Mon Sep 17 00:00:00 2001 From: AlphaX-Qortal Date: Tue, 26 Nov 2024 00:03:04 +0100 Subject: [PATCH 1/6] Fix batch reward --- src/main/java/org/qortal/block/Block.java | 99 +++++++++++-------- .../java/org/qortal/block/BlockChain.java | 7 +- src/main/resources/blockchain.json | 3 +- 3 files changed, 65 insertions(+), 44 deletions(-) diff --git a/src/main/java/org/qortal/block/Block.java b/src/main/java/org/qortal/block/Block.java index eba55069..81838e2a 100644 --- a/src/main/java/org/qortal/block/Block.java +++ b/src/main/java/org/qortal/block/Block.java @@ -25,10 +25,7 @@ import org.qortal.data.block.BlockSummaryData; 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; @@ -144,6 +141,7 @@ public class Block { private final Account mintingAccount; private final AccountData mintingAccountData; private final boolean isMinterFounder; + private final boolean isMinterMember; private final Account recipientAccount; private final AccountData recipientAccountData; @@ -157,6 +155,7 @@ public class Block { 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 @@ -181,7 +180,7 @@ public class Block { *

* 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) { @@ -192,6 +191,11 @@ public class Block { 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()) ? @@ -262,7 +266,7 @@ public class Block { * Constructs new Block without loading transactions and AT states. *

* Transactions and AT states are loaded on first call to getTransactions() or getATStates() respectively. - * + * * @param repository * @param blockData */ @@ -333,7 +337,7 @@ public class Block { /** * Constructs new Block with empty transaction list, using passed minter account. - * + * * @param repository * @param blockData * @param minter @@ -351,7 +355,7 @@ public class Block { * This constructor typically used when minting a new block. *

* 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 @@ -377,7 +381,7 @@ public class Block { 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 @@ -512,7 +516,7 @@ public class Block { * Mints new block using this block as template, but with different minting account. *

* NOTE: uses the same transactions list, AT states, etc. - * + * * @param minter * @return * @throws DataException @@ -598,7 +602,7 @@ public class Block { /** * Return composite block signature (minterSignature + transactionsSignature). - * + * * @return byte[], or null if either component signature is null. */ public byte[] getSignature() { @@ -613,7 +617,7 @@ public class Block { *

* 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() { @@ -627,7 +631,7 @@ public class Block { * Return block's transactions. *

* 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 */ @@ -661,7 +665,7 @@ public class Block { * 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. *

* Note: AT states fetched from repository only contain summary info, not actual data like serialized state data or AT creation timestamps! - * + * * @return * @throws DataException */ @@ -697,7 +701,7 @@ public class Block { *

* Typically called as part of Block.process() or Block.orphan() * so ideally after any calls to Block.isValid(). - * + * * @throws DataException */ public List getExpandedAccounts() throws DataException { @@ -715,8 +719,18 @@ public class Block { List 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; @@ -727,7 +741,7 @@ public class Block { /** * Load parent block's data from repository via this block's reference. - * + * * @return parent's BlockData, or null if no parent found * @throws DataException */ @@ -741,7 +755,7 @@ public class Block { /** * Load child block's data from repository via this block's signature. - * + * * @return child's BlockData, or null if no parent found * @throws DataException */ @@ -761,7 +775,7 @@ public class Block { * Used when constructing a new block during minting. *

* 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 @@ -814,7 +828,7 @@ public class Block { * Used when constructing a new block during minting. *

* 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}. @@ -859,7 +873,7 @@ public class Block { * previous block's minter signature + minter's public key + (encoded) online-accounts data *

* (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 @@ -876,7 +890,7 @@ public class Block { * Recalculate block's transactions signature. *

* Requires block's {@code minter} being a {@code PrivateKeyAccount}. - * + * * @throws IllegalStateException * if block's {@code minter} is not a {@code PrivateKeyAccount}. * @throws RuntimeException @@ -998,7 +1012,7 @@ public class Block { * Recalculate block's minter and transactions signatures, thus giving block full signature. *

* Note: Block instance must have been constructed with a PrivateKeyAccount minter or this call will throw an IllegalStateException. - * + * * @throws IllegalStateException * if block's {@code minter} is not a {@code PrivateKeyAccount}. */ @@ -1011,7 +1025,7 @@ public class Block { /** * Returns whether this block's signatures are valid. - * + * * @return true if both minter and transaction signatures are valid, false otherwise */ public boolean isSignatureValid() { @@ -1035,7 +1049,7 @@ public class Block { *

* 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 */ @@ -1215,7 +1229,7 @@ public class Block { *

* Checks block's transactions by testing their validity then processing them.
* Hence uses a repository savepoint during execution. - * + * * @return ValidationResult.OK if block is valid, or some other ValidationResult otherwise. * @throws DataException */ @@ -1386,7 +1400,7 @@ public class Block { *

* NOTE: will execute ATs locally if not already done.
* This is so we have locally-generated AT states for comparison. - * + * * @return OK, or some AT-related validation result * @throws DataException */ @@ -1462,11 +1476,11 @@ public class Block { * Note: this method does not store new AT state data into repository - that is handled by process(). *

* 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. @@ -1538,7 +1552,7 @@ public class Block { /** * Process block, and its transactions, adding them to the blockchain. - * + * * @throws DataException */ public void process() throws DataException { @@ -1839,7 +1853,7 @@ public class Block { /** * Removes block from blockchain undoing transactions and adding them to unconfirmed pile. - * + * * @throws DataException */ public void orphan() throws DataException { @@ -1879,7 +1893,7 @@ public class Block { 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 @@ -2213,6 +2227,7 @@ public class Block { List 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); } @@ -2225,30 +2240,30 @@ public class Block { /* * 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% diff --git a/src/main/java/org/qortal/block/BlockChain.java b/src/main/java/org/qortal/block/BlockChain.java index 34097f0f..3e98d4a0 100644 --- a/src/main/java/org/qortal/block/BlockChain.java +++ b/src/main/java/org/qortal/block/BlockChain.java @@ -87,7 +87,8 @@ public class BlockChain { enableRewardshareHeight, onlyMintWithNameHeight, removeOnlyMintWithNameHeight, - groupMemberCheckHeight + groupMemberCheckHeight, + fixBatchRewardHeight } // Custom transaction fees @@ -657,6 +658,10 @@ public class BlockChain { 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) { diff --git a/src/main/resources/blockchain.json b/src/main/resources/blockchain.json index 1dcd9e46..11437bf0 100644 --- a/src/main/resources/blockchain.json +++ b/src/main/resources/blockchain.json @@ -112,7 +112,8 @@ "enableRewardshareHeight": 1905100, "onlyMintWithNameHeight": 1900300, "removeOnlyMintWithNameHeight": 1935500, - "groupMemberCheckHeight": 1902700 + "groupMemberCheckHeight": 1902700, + "fixBatchRewardHeight": 9999999 }, "checkpoints": [ { "height": 1136300, "signature": "3BbwawEF2uN8Ni5ofpJXkukoU8ctAPxYoFB7whq9pKfBnjfZcpfEJT4R95NvBDoTP8WDyWvsUvbfHbcr9qSZuYpSKZjUQTvdFf6eqznHGEwhZApWfvXu6zjGCxYCp65F4jsVYYJjkzbjmkCg5WAwN5voudngA23kMK6PpTNygapCzXt" } From 0a44928e93edcc66a9eba87de1771bdcb0932fc9 Mon Sep 17 00:00:00 2001 From: AlphaX-Qortal Date: Tue, 26 Nov 2024 11:05:46 +0100 Subject: [PATCH 2/6] Set peer connect to a dedicated thread pool for non-blocking I/O (Thanks to RAZ) --- pom.xml | 4 +-- src/main/java/org/qortal/block/Block.java | 5 ++-- .../qortal/network/task/PeerConnectTask.java | 25 ++++++++++++++++++- 3 files changed, 29 insertions(+), 5 deletions(-) diff --git a/pom.xml b/pom.xml index 6dae45b3..8ab4c6ce 100644 --- a/pom.xml +++ b/pom.xml @@ -16,7 +16,7 @@ 1.4.2 3.8.0 1.12.0 - 2.17.0 + 2.18.0 1.27.1 3.17.0 1.2.2 @@ -28,7 +28,7 @@ 1.2.1 2.7.4 76.1 - 4.12 + 4.15 4.0.1 2.3.9 2.42 diff --git a/src/main/java/org/qortal/block/Block.java b/src/main/java/org/qortal/block/Block.java index 086b3fc8..918a20ae 100644 --- a/src/main/java/org/qortal/block/Block.java +++ b/src/main/java/org/qortal/block/Block.java @@ -145,6 +145,8 @@ public class Block { private final Account recipientAccount; private final AccountData recipientAccountData; + + final BlockChain blockChain = BlockChain.getInstance(); ExpandedAccount(Repository repository, RewardShareData rewardShareData) throws DataException { this.rewardShareData = rewardShareData; @@ -191,13 +193,12 @@ public class Block { if (accountLevel <= 0) return null; // level 0 isn't included in any share bins - if (blockHeight >= BlockChain.getInstance().getFixBatchRewardHeight()) { + if (blockHeight >= blockChain.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()) ? blockChain.getShareBinsByAccountLevelV2() : blockChain.getShareBinsByAccountLevelV1(); diff --git a/src/main/java/org/qortal/network/task/PeerConnectTask.java b/src/main/java/org/qortal/network/task/PeerConnectTask.java index 7eec4e6b..3ae1b640 100644 --- a/src/main/java/org/qortal/network/task/PeerConnectTask.java +++ b/src/main/java/org/qortal/network/task/PeerConnectTask.java @@ -4,10 +4,15 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.qortal.network.Network; import org.qortal.network.Peer; +import org.qortal.utils.DaemonThreadFactory; import org.qortal.utils.ExecuteProduceConsume.Task; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + public class PeerConnectTask implements Task { private static final Logger LOGGER = LogManager.getLogger(PeerConnectTask.class); + private static final ExecutorService connectionExecutor = Executors.newCachedThreadPool(new DaemonThreadFactory(8)); private final Peer peer; private final String name; @@ -24,6 +29,24 @@ public class PeerConnectTask implements Task { @Override public void perform() throws InterruptedException { - Network.getInstance().connectPeer(peer); + // Submit connection task to a dedicated thread pool for non-blocking I/O + connectionExecutor.submit(() -> { + try { + connectPeerAsync(peer); + } catch (InterruptedException e) { + LOGGER.error("Connection attempt interrupted for peer {}", peer, e); + Thread.currentThread().interrupt(); // Reset interrupt flag + } + }); + } + + private void connectPeerAsync(Peer peer) throws InterruptedException { + // Perform peer connection in a separate thread to avoid blocking main task execution + try { + Network.getInstance().connectPeer(peer); + LOGGER.trace("Successfully connected to peer {}", peer); + } catch (Exception e) { + LOGGER.error("Error connecting to peer {}", peer, e); + } } } From 4d28ba692d092a7d30b90a85b89a33912f05d6a8 Mon Sep 17 00:00:00 2001 From: AlphaX Date: Tue, 26 Nov 2024 19:34:45 +0100 Subject: [PATCH 3/6] Update minimum peer version --- src/main/java/org/qortal/settings/Settings.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/qortal/settings/Settings.java b/src/main/java/org/qortal/settings/Settings.java index 937d352a..305021d5 100644 --- a/src/main/java/org/qortal/settings/Settings.java +++ b/src/main/java/org/qortal/settings/Settings.java @@ -213,7 +213,7 @@ public class Settings { public long recoveryModeTimeout = 9999999999999L; /** Minimum peer version number required in order to sync with them */ - private String minPeerVersion = "4.6.2"; + private String minPeerVersion = "4.6.3"; /** Whether to allow connections with peers below minPeerVersion * If true, we won't sync with them but they can still sync with us, and will show in the peers list * If false, sync will be blocked both ways, and they will not appear in the peers list */ From 89999e6b3305243645d638f68c30ff26efc157e8 Mon Sep 17 00:00:00 2001 From: AlphaX Date: Tue, 26 Nov 2024 19:41:15 +0100 Subject: [PATCH 4/6] Set feature trigger --- src/main/resources/blockchain.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/blockchain.json b/src/main/resources/blockchain.json index 11437bf0..762a5708 100644 --- a/src/main/resources/blockchain.json +++ b/src/main/resources/blockchain.json @@ -113,7 +113,7 @@ "onlyMintWithNameHeight": 1900300, "removeOnlyMintWithNameHeight": 1935500, "groupMemberCheckHeight": 1902700, - "fixBatchRewardHeight": 9999999 + "fixBatchRewardHeight": 1945900 }, "checkpoints": [ { "height": 1136300, "signature": "3BbwawEF2uN8Ni5ofpJXkukoU8ctAPxYoFB7whq9pKfBnjfZcpfEJT4R95NvBDoTP8WDyWvsUvbfHbcr9qSZuYpSKZjUQTvdFf6eqznHGEwhZApWfvXu6zjGCxYCp65F4jsVYYJjkzbjmkCg5WAwN5voudngA23kMK6PpTNygapCzXt" } From 2ce02faa07ad5f64c0abf139a6bc1da060e12cbe Mon Sep 17 00:00:00 2001 From: AlphaX Date: Tue, 26 Nov 2024 19:42:13 +0100 Subject: [PATCH 5/6] Bump version to 4.6.4 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 8ab4c6ce..833487a8 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 org.qortal qortal - 4.6.3 + 4.6.4 jar UTF-8 From 8ffb0625a1edcf0b3d1ec2498b15a31ec38ade3c Mon Sep 17 00:00:00 2001 From: AlphaX Date: Tue, 26 Nov 2024 23:27:35 +0100 Subject: [PATCH 6/6] Bump version to 4.6.5 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 833487a8..bde846a0 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 org.qortal qortal - 4.6.4 + 4.6.5 jar UTF-8