diff --git a/pom.xml b/pom.xml index 6dae45b3..bde846a0 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 org.qortal qortal - 4.6.3 + 4.6.5 jar UTF-8 @@ -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 ddde81e3..918a20ae 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,9 +141,12 @@ 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; + + final BlockChain blockChain = BlockChain.getInstance(); ExpandedAccount(Repository repository, RewardShareData rewardShareData) throws DataException { this.rewardShareData = rewardShareData; @@ -157,6 +157,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 +182,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,8 +193,12 @@ public class Block { if (accountLevel <= 0) return null; // level 0 isn't included in any share bins + 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(); @@ -262,7 +267,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 +338,7 @@ public class Block { /** * Constructs new Block with empty transaction list, using passed minter account. - * + * * @param repository * @param blockData * @param minter @@ -351,7 +356,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 +382,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 +517,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 +603,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 +618,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 +632,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 +666,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 +702,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 +720,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 +742,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 +756,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 +776,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 +829,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 +874,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 +891,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 +1013,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 +1026,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 +1050,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 +1230,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 +1401,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 +1477,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 +1553,7 @@ public class Block { /** * Process block, and its transactions, adding them to the blockchain. - * + * * @throws DataException */ public void process() throws DataException { @@ -1839,7 +1854,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 +1894,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 +2228,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 +2241,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/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); + } } } diff --git a/src/main/resources/blockchain.json b/src/main/resources/blockchain.json index 1dcd9e46..762a5708 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": 1945900 }, "checkpoints": [ { "height": 1136300, "signature": "3BbwawEF2uN8Ni5ofpJXkukoU8ctAPxYoFB7whq9pKfBnjfZcpfEJT4R95NvBDoTP8WDyWvsUvbfHbcr9qSZuYpSKZjUQTvdFf6eqznHGEwhZApWfvXu6zjGCxYCp65F4jsVYYJjkzbjmkCg5WAwN5voudngA23kMK6PpTNygapCzXt" }