From 130bb6cf505fc26b7079691e94966d44dcb0dd2c Mon Sep 17 00:00:00 2001 From: kennycud Date: Sun, 17 Nov 2024 17:17:00 -0800 Subject: [PATCH 1/9] Added logging statements to demonstrate order of operations. This will be removed ASAP and should not be included in a PR. --- src/main/java/org/qortal/account/Account.java | 2 ++ .../qortal/repository/hsqldb/HSQLDBNameRepository.java | 9 +++++++++ 2 files changed, 11 insertions(+) diff --git a/src/main/java/org/qortal/account/Account.java b/src/main/java/org/qortal/account/Account.java index 2cc031fe..d0a29c7d 100644 --- a/src/main/java/org/qortal/account/Account.java +++ b/src/main/java/org/qortal/account/Account.java @@ -232,6 +232,8 @@ public class Account { if (blockchainHeight < nameCheckHeight && level >= levelToMint) return true; + LOGGER.info("Calling myName.isEmpty(): myAddress = " + myAddress); + // Can only mint if have registered a name if (blockchainHeight >= nameCheckHeight && blockchainHeight < groupCheckHeight && level >= levelToMint && !myName.isEmpty()) return true; diff --git a/src/main/java/org/qortal/repository/hsqldb/HSQLDBNameRepository.java b/src/main/java/org/qortal/repository/hsqldb/HSQLDBNameRepository.java index 06e41663..25a6a385 100644 --- a/src/main/java/org/qortal/repository/hsqldb/HSQLDBNameRepository.java +++ b/src/main/java/org/qortal/repository/hsqldb/HSQLDBNameRepository.java @@ -1,5 +1,8 @@ package org.qortal.repository.hsqldb; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.qortal.account.Account; import org.qortal.data.naming.NameData; import org.qortal.repository.DataException; import org.qortal.repository.NameRepository; @@ -11,6 +14,8 @@ import java.util.List; public class HSQLDBNameRepository implements NameRepository { + private static final Logger LOGGER = LogManager.getLogger(HSQLDBNameRepository.class); + protected HSQLDBRepository repository; public HSQLDBNameRepository(HSQLDBRepository repository) { @@ -264,6 +269,8 @@ public class HSQLDBNameRepository implements NameRepository { @Override public List getNamesByOwner(String owner, Integer limit, Integer offset, Boolean reverse) throws DataException { + LOGGER.info("Executing getNamesByOwner: owner = " + owner); + StringBuilder sql = new StringBuilder(512); sql.append("SELECT name, reduced_name, data, registered_when, updated_when, " @@ -306,6 +313,8 @@ public class HSQLDBNameRepository implements NameRepository { return names; } catch (SQLException e) { throw new DataException("Unable to fetch account's names from repository", e); + } finally { + LOGGER.info("Executed getNamesByOwner: owner = " + owner); } } From f55efe38c59aa7673decf12d947a26fdc8b6a0fe Mon Sep 17 00:00:00 2001 From: kennycud Date: Mon, 18 Nov 2024 15:09:43 -0800 Subject: [PATCH 2/9] Removed logging statements to demonstrate order of operations to others. Added optimizations for the canMint() method. This is a quick fix and a more comprehensive fix will be done in the future. --- src/main/java/org/qortal/account/Account.java | 37 ++++++++----------- .../hsqldb/HSQLDBNameRepository.java | 9 ----- 2 files changed, 15 insertions(+), 31 deletions(-) diff --git a/src/main/java/org/qortal/account/Account.java b/src/main/java/org/qortal/account/Account.java index d0a29c7d..735bb30c 100644 --- a/src/main/java/org/qortal/account/Account.java +++ b/src/main/java/org/qortal/account/Account.java @@ -222,8 +222,6 @@ public class Account { int removeNameCheckHeight = BlockChain.getInstance().getRemoveOnlyMintWithNameHeight(); String myAddress = accountData.getAddress(); - List myName = nameRepository.getNamesByOwner(myAddress); - boolean isMember = groupRepository.memberExists(groupIdToMint, myAddress); if (accountData == null) return false; @@ -232,45 +230,40 @@ public class Account { if (blockchainHeight < nameCheckHeight && level >= levelToMint) return true; - LOGGER.info("Calling myName.isEmpty(): myAddress = " + myAddress); - // Can only mint if have registered a name - if (blockchainHeight >= nameCheckHeight && blockchainHeight < groupCheckHeight && level >= levelToMint && !myName.isEmpty()) + if (blockchainHeight >= nameCheckHeight && blockchainHeight < groupCheckHeight && level >= levelToMint && !nameRepository.getNamesByOwner(myAddress).isEmpty()) return true; // Can only mint if have registered a name and is member of minter group id - if (blockchainHeight >= groupCheckHeight && blockchainHeight < removeNameCheckHeight && level >= levelToMint && !myName.isEmpty() && isMember) + if (blockchainHeight >= groupCheckHeight && blockchainHeight < removeNameCheckHeight && level >= levelToMint && groupRepository.memberExists(groupIdToMint, myAddress) && !nameRepository.getNamesByOwner(myAddress).isEmpty() && groupRepository.memberExists(groupIdToMint, myAddress)) return true; // Can only mint if is member of minter group id - if (blockchainHeight >= removeNameCheckHeight && level >= levelToMint && isMember) + if (blockchainHeight >= removeNameCheckHeight && level >= levelToMint && groupRepository.memberExists(groupIdToMint, myAddress)) + return true; + + // if you aren't a legit founder by this point, then you can't mint + if( !(Account.isFounder(accountData.getFlags()) && + accountData.getBlocksMintedPenalty() == 0)) + return false; + + if (blockchainHeight < nameCheckHeight ) return true; // Founders needs to pass same tests like minters - if (blockchainHeight < nameCheckHeight && - Account.isFounder(accountData.getFlags()) && - accountData.getBlocksMintedPenalty() == 0) - return true; - if (blockchainHeight >= nameCheckHeight && blockchainHeight < groupCheckHeight && - Account.isFounder(accountData.getFlags()) && - accountData.getBlocksMintedPenalty() == 0 && - !myName.isEmpty()) + !nameRepository.getNamesByOwner(myAddress).isEmpty()) return true; if (blockchainHeight >= groupCheckHeight && blockchainHeight < removeNameCheckHeight && - Account.isFounder(accountData.getFlags()) && - accountData.getBlocksMintedPenalty() == 0 && - !myName.isEmpty() && - isMember) + !nameRepository.getNamesByOwner(myAddress).isEmpty() && + groupRepository.memberExists(groupIdToMint, myAddress)) return true; if (blockchainHeight >= removeNameCheckHeight && - Account.isFounder(accountData.getFlags()) && - accountData.getBlocksMintedPenalty() == 0 && - isMember) + groupRepository.memberExists(groupIdToMint, myAddress)) return true; return false; diff --git a/src/main/java/org/qortal/repository/hsqldb/HSQLDBNameRepository.java b/src/main/java/org/qortal/repository/hsqldb/HSQLDBNameRepository.java index 25a6a385..06e41663 100644 --- a/src/main/java/org/qortal/repository/hsqldb/HSQLDBNameRepository.java +++ b/src/main/java/org/qortal/repository/hsqldb/HSQLDBNameRepository.java @@ -1,8 +1,5 @@ package org.qortal.repository.hsqldb; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.qortal.account.Account; import org.qortal.data.naming.NameData; import org.qortal.repository.DataException; import org.qortal.repository.NameRepository; @@ -14,8 +11,6 @@ import java.util.List; public class HSQLDBNameRepository implements NameRepository { - private static final Logger LOGGER = LogManager.getLogger(HSQLDBNameRepository.class); - protected HSQLDBRepository repository; public HSQLDBNameRepository(HSQLDBRepository repository) { @@ -269,8 +264,6 @@ public class HSQLDBNameRepository implements NameRepository { @Override public List getNamesByOwner(String owner, Integer limit, Integer offset, Boolean reverse) throws DataException { - LOGGER.info("Executing getNamesByOwner: owner = " + owner); - StringBuilder sql = new StringBuilder(512); sql.append("SELECT name, reduced_name, data, registered_when, updated_when, " @@ -313,8 +306,6 @@ public class HSQLDBNameRepository implements NameRepository { return names; } catch (SQLException e) { throw new DataException("Unable to fetch account's names from repository", e); - } finally { - LOGGER.info("Executed getNamesByOwner: owner = " + owner); } } From b0d43a18900210747879c983ee9ab74166f22777 Mon Sep 17 00:00:00 2001 From: kennycud Date: Wed, 20 Nov 2024 19:12:21 -0800 Subject: [PATCH 3/9] minter group check optimizations --- src/main/java/org/qortal/account/Account.java | 11 ++-- .../restricted/resource/AdminResource.java | 2 +- src/main/java/org/qortal/block/Block.java | 2 +- .../org/qortal/controller/BlockMinter.java | 2 +- .../controller/OnlineAccountsManager.java | 20 +++++-- .../transaction/RewardShareTransaction.java | 2 +- .../org/qortal/test/TransferPrivsTests.java | 54 +++++++++---------- 7 files changed, 53 insertions(+), 40 deletions(-) diff --git a/src/main/java/org/qortal/account/Account.java b/src/main/java/org/qortal/account/Account.java index 1aaceffb..537f0788 100644 --- a/src/main/java/org/qortal/account/Account.java +++ b/src/main/java/org/qortal/account/Account.java @@ -205,10 +205,11 @@ public class Account { *
  • account's address is a member of the minter group
  • * * + * @param isGroupValidated true if this account has already been validated for MINTER Group membership * @return true if account can be considered "minting account" * @throws DataException */ - public boolean canMint() throws DataException { + public boolean canMint(boolean isGroupValidated) throws DataException { AccountData accountData = this.repository.getAccountRepository().getAccount(this.address); NameRepository nameRepository = this.repository.getNameRepository(); GroupRepository groupRepository = this.repository.getGroupRepository(); @@ -251,9 +252,9 @@ public class Account { if (blockchainHeight >= groupCheckHeight && blockchainHeight < removeNameCheckHeight) { List myName = nameRepository.getNamesByOwner(myAddress); if (Account.isFounder(accountData.getFlags())) { - return accountData.getBlocksMintedPenalty() == 0 && !myName.isEmpty() && groupRepository.memberExists(groupIdToMint, myAddress); + return accountData.getBlocksMintedPenalty() == 0 && !myName.isEmpty() && (isGroupValidated || groupRepository.memberExists(groupIdToMint, myAddress)); } else { - return level >= levelToMint && !myName.isEmpty() && groupRepository.memberExists(groupIdToMint, myAddress); + return level >= levelToMint && !myName.isEmpty() && (isGroupValidated || groupRepository.memberExists(groupIdToMint, myAddress)); } } @@ -262,9 +263,9 @@ public class Account { // Account's address is a member of the minter group if (blockchainHeight >= removeNameCheckHeight) { if (Account.isFounder(accountData.getFlags())) { - return accountData.getBlocksMintedPenalty() == 0 && groupRepository.memberExists(groupIdToMint, myAddress); + return accountData.getBlocksMintedPenalty() == 0 && (isGroupValidated || groupRepository.memberExists(groupIdToMint, myAddress)); } else { - return level >= levelToMint && groupRepository.memberExists(groupIdToMint, myAddress); + return level >= levelToMint && (isGroupValidated || groupRepository.memberExists(groupIdToMint, myAddress)); } } diff --git a/src/main/java/org/qortal/api/restricted/resource/AdminResource.java b/src/main/java/org/qortal/api/restricted/resource/AdminResource.java index 9a61a21b..279485bc 100644 --- a/src/main/java/org/qortal/api/restricted/resource/AdminResource.java +++ b/src/main/java/org/qortal/api/restricted/resource/AdminResource.java @@ -459,7 +459,7 @@ public class AdminResource { // Qortal: check reward-share's minting account is still allowed to mint Account rewardShareMintingAccount = new Account(repository, rewardShareData.getMinter()); - if (!rewardShareMintingAccount.canMint()) + if (!rewardShareMintingAccount.canMint(false)) throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.CANNOT_MINT); MintingAccountData mintingAccountData = new MintingAccountData(mintingAccount.getPrivateKey(), mintingAccount.getPublicKey()); diff --git a/src/main/java/org/qortal/block/Block.java b/src/main/java/org/qortal/block/Block.java index eba55069..ddde81e3 100644 --- a/src/main/java/org/qortal/block/Block.java +++ b/src/main/java/org/qortal/block/Block.java @@ -1518,7 +1518,7 @@ public class Block { return false; Account mintingAccount = new PublicKeyAccount(this.repository, rewardShareData.getMinterPublicKey()); - return mintingAccount.canMint(); + return mintingAccount.canMint(false); } /** diff --git a/src/main/java/org/qortal/controller/BlockMinter.java b/src/main/java/org/qortal/controller/BlockMinter.java index 7ea2242c..a1fb9769 100644 --- a/src/main/java/org/qortal/controller/BlockMinter.java +++ b/src/main/java/org/qortal/controller/BlockMinter.java @@ -148,7 +148,7 @@ public class BlockMinter extends Thread { } Account mintingAccount = new Account(repository, rewardShareData.getMinter()); - if (!mintingAccount.canMint()) { + if (!mintingAccount.canMint(true)) { // Minting-account component of reward-share can no longer mint - disregard madi.remove(); continue; diff --git a/src/main/java/org/qortal/controller/OnlineAccountsManager.java b/src/main/java/org/qortal/controller/OnlineAccountsManager.java index 5572dee1..332bf867 100644 --- a/src/main/java/org/qortal/controller/OnlineAccountsManager.java +++ b/src/main/java/org/qortal/controller/OnlineAccountsManager.java @@ -13,6 +13,7 @@ import org.qortal.crypto.MemoryPoW; import org.qortal.crypto.Qortal25519Extras; import org.qortal.data.account.MintingAccountData; import org.qortal.data.account.RewardShareData; +import org.qortal.data.group.GroupMemberData; import org.qortal.data.network.OnlineAccountData; import org.qortal.network.Network; import org.qortal.network.Peer; @@ -224,6 +225,12 @@ public class OnlineAccountsManager { Set onlineAccountsToAdd = new HashSet<>(); Set onlineAccountsToRemove = new HashSet<>(); try (final Repository repository = RepositoryManager.getRepository()) { + List mintingGroupMemberAddresses + = repository.getGroupRepository() + .getGroupMembers(BlockChain.getInstance().getMintingGroupId()).stream() + .map(GroupMemberData::getMember) + .collect(Collectors.toList()); + for (OnlineAccountData onlineAccountData : this.onlineAccountsImportQueue) { if (isStopping) return; @@ -236,7 +243,7 @@ public class OnlineAccountsManager { continue; } - boolean isValid = this.isValidCurrentAccount(repository, onlineAccountData); + boolean isValid = this.isValidCurrentAccount(repository, mintingGroupMemberAddresses, onlineAccountData); if (isValid) onlineAccountsToAdd.add(onlineAccountData); @@ -315,7 +322,7 @@ public class OnlineAccountsManager { return inplaceArray; } - private static boolean isValidCurrentAccount(Repository repository, OnlineAccountData onlineAccountData) throws DataException { + private static boolean isValidCurrentAccount(Repository repository, List mintingGroupMemberAddresses, OnlineAccountData onlineAccountData) throws DataException { final Long now = NTP.getTime(); if (now == null) return false; @@ -350,9 +357,14 @@ public class OnlineAccountsManager { LOGGER.trace(() -> String.format("Rejecting unknown online reward-share public key %s", Base58.encode(rewardSharePublicKey))); return false; } + // reject account address that are not in the MINTER Group + else if( !mintingGroupMemberAddresses.contains(rewardShareData.getMinter())) { + LOGGER.trace(() -> String.format("Rejecting online reward-share that is not in MINTER Group, account %s", rewardShareData.getMinter())); + return false; + } Account mintingAccount = new Account(repository, rewardShareData.getMinter()); - if (!mintingAccount.canMint()) { + if (!mintingAccount.canMint(true)) { // group validation is a few lines above // Minting-account component of reward-share can no longer mint - disregard LOGGER.trace(() -> String.format("Rejecting online reward-share with non-minting account %s", mintingAccount.getAddress())); return false; @@ -539,7 +551,7 @@ public class OnlineAccountsManager { } Account mintingAccount = new Account(repository, rewardShareData.getMinter()); - if (!mintingAccount.canMint()) { + if (!mintingAccount.canMint(true)) { // Minting-account component of reward-share can no longer mint - disregard iterator.remove(); continue; diff --git a/src/main/java/org/qortal/transaction/RewardShareTransaction.java b/src/main/java/org/qortal/transaction/RewardShareTransaction.java index 50eead31..31734204 100644 --- a/src/main/java/org/qortal/transaction/RewardShareTransaction.java +++ b/src/main/java/org/qortal/transaction/RewardShareTransaction.java @@ -123,7 +123,7 @@ public class RewardShareTransaction extends Transaction { final boolean isCancellingSharePercent = this.rewardShareTransactionData.getSharePercent() < 0; // Creator themselves needs to be allowed to mint (unless cancelling) - if (!isCancellingSharePercent && !creator.canMint()) + if (!isCancellingSharePercent && !creator.canMint(false)) return ValidationResult.NOT_MINTING_ACCOUNT; // Qortal: special rules in play depending whether recipient is also minter diff --git a/src/test/java/org/qortal/test/TransferPrivsTests.java b/src/test/java/org/qortal/test/TransferPrivsTests.java index 86a0e743..ad1d2a2a 100644 --- a/src/test/java/org/qortal/test/TransferPrivsTests.java +++ b/src/test/java/org/qortal/test/TransferPrivsTests.java @@ -74,7 +74,7 @@ public class TransferPrivsTests extends Common { public void testAliceIntoNewAccountTransferPrivs() throws DataException { try (final Repository repository = RepositoryManager.getRepository()) { TestAccount alice = Common.getTestAccount(repository, "alice"); - assertTrue(alice.canMint()); + assertTrue(alice.canMint(false)); PrivateKeyAccount aliceMintingAccount = Common.getTestAccount(repository, "alice-reward-share"); @@ -86,8 +86,8 @@ public class TransferPrivsTests extends Common { combineAccounts(repository, alice, randomAccount, aliceMintingAccount); - assertFalse(alice.canMint()); - assertTrue(randomAccount.canMint()); + assertFalse(alice.canMint(false)); + assertTrue(randomAccount.canMint(false)); } } @@ -97,8 +97,8 @@ public class TransferPrivsTests extends Common { TestAccount alice = Common.getTestAccount(repository, "alice"); TestAccount dilbert = Common.getTestAccount(repository, "dilbert"); - assertTrue(alice.canMint()); - assertTrue(dilbert.canMint()); + assertTrue(alice.canMint(false)); + assertTrue(dilbert.canMint(false)); // Dilbert has level, Alice does not so we need Alice to mint enough blocks to bump Dilbert's level post-combine final int expectedPostCombineLevel = dilbert.getLevel() + 1; @@ -118,11 +118,11 @@ public class TransferPrivsTests extends Common { // Post-combine sender checks checkSenderPostTransfer(postCombineAliceData); - assertFalse(alice.canMint()); + assertFalse(alice.canMint(false)); // Post-combine recipient checks checkRecipientPostTransfer(preCombineAliceData, preCombineDilbertData, postCombineDilbertData, expectedPostCombineLevel); - assertTrue(dilbert.canMint()); + assertTrue(dilbert.canMint(false)); // Orphan previous block BlockUtils.orphanLastBlock(repository); @@ -130,12 +130,12 @@ public class TransferPrivsTests extends Common { // Sender checks AccountData orphanedAliceData = repository.getAccountRepository().getAccount(alice.getAddress()); checkAccountDataRestored("sender", preCombineAliceData, orphanedAliceData); - assertTrue(alice.canMint()); + assertTrue(alice.canMint(false)); // Recipient checks AccountData orphanedDilbertData = repository.getAccountRepository().getAccount(dilbert.getAddress()); checkAccountDataRestored("recipient", preCombineDilbertData, orphanedDilbertData); - assertTrue(dilbert.canMint()); + assertTrue(dilbert.canMint(false)); } } @@ -145,8 +145,8 @@ public class TransferPrivsTests extends Common { TestAccount alice = Common.getTestAccount(repository, "alice"); TestAccount dilbert = Common.getTestAccount(repository, "dilbert"); - assertTrue(dilbert.canMint()); - assertTrue(alice.canMint()); + assertTrue(dilbert.canMint(false)); + assertTrue(alice.canMint(false)); // Dilbert has level, Alice does not so we need Alice to mint enough blocks to surpass Dilbert's level post-combine final int expectedPostCombineLevel = dilbert.getLevel() + 1; @@ -166,11 +166,11 @@ public class TransferPrivsTests extends Common { // Post-combine sender checks checkSenderPostTransfer(postCombineDilbertData); - assertFalse(dilbert.canMint()); + assertFalse(dilbert.canMint(false)); // Post-combine recipient checks checkRecipientPostTransfer(preCombineDilbertData, preCombineAliceData, postCombineAliceData, expectedPostCombineLevel); - assertTrue(alice.canMint()); + assertTrue(alice.canMint(false)); // Orphan previous block BlockUtils.orphanLastBlock(repository); @@ -178,12 +178,12 @@ public class TransferPrivsTests extends Common { // Sender checks AccountData orphanedDilbertData = repository.getAccountRepository().getAccount(dilbert.getAddress()); checkAccountDataRestored("sender", preCombineDilbertData, orphanedDilbertData); - assertTrue(dilbert.canMint()); + assertTrue(dilbert.canMint(false)); // Recipient checks AccountData orphanedAliceData = repository.getAccountRepository().getAccount(alice.getAddress()); checkAccountDataRestored("recipient", preCombineAliceData, orphanedAliceData); - assertTrue(alice.canMint()); + assertTrue(alice.canMint(false)); } } @@ -202,8 +202,8 @@ public class TransferPrivsTests extends Common { TestAccount chloe = Common.getTestAccount(repository, "chloe"); TestAccount dilbert = Common.getTestAccount(repository, "dilbert"); - assertTrue(dilbert.canMint()); - assertFalse(chloe.canMint()); + assertTrue(dilbert.canMint(false)); + assertFalse(chloe.canMint(false)); // COMBINE DILBERT INTO CHLOE @@ -225,16 +225,16 @@ public class TransferPrivsTests extends Common { // Post-combine sender checks checkSenderPostTransfer(post1stCombineDilbertData); - assertFalse(dilbert.canMint()); + assertFalse(dilbert.canMint(false)); // Post-combine recipient checks checkRecipientPostTransfer(pre1stCombineDilbertData, pre1stCombineChloeData, post1stCombineChloeData, expectedPost1stCombineLevel); - assertTrue(chloe.canMint()); + assertTrue(chloe.canMint(false)); // COMBINE ALICE INTO CHLOE - assertTrue(alice.canMint()); - assertTrue(chloe.canMint()); + assertTrue(alice.canMint(false)); + assertTrue(chloe.canMint(false)); // Alice needs to mint enough blocks to surpass Chloe's level post-combine final int expectedPost2ndCombineLevel = chloe.getLevel() + 1; @@ -254,11 +254,11 @@ public class TransferPrivsTests extends Common { // Post-combine sender checks checkSenderPostTransfer(post2ndCombineAliceData); - assertFalse(alice.canMint()); + assertFalse(alice.canMint(false)); // Post-combine recipient checks checkRecipientPostTransfer(pre2ndCombineAliceData, pre2ndCombineChloeData, post2ndCombineChloeData, expectedPost2ndCombineLevel); - assertTrue(chloe.canMint()); + assertTrue(chloe.canMint(false)); // Orphan 2nd combine BlockUtils.orphanLastBlock(repository); @@ -266,12 +266,12 @@ public class TransferPrivsTests extends Common { // Sender checks AccountData orphanedAliceData = repository.getAccountRepository().getAccount(alice.getAddress()); checkAccountDataRestored("sender", pre2ndCombineAliceData, orphanedAliceData); - assertTrue(alice.canMint()); + assertTrue(alice.canMint(false)); // Recipient checks AccountData orphanedChloeData = repository.getAccountRepository().getAccount(chloe.getAddress()); checkAccountDataRestored("recipient", pre2ndCombineChloeData, orphanedChloeData); - assertTrue(chloe.canMint()); + assertTrue(chloe.canMint(false)); // Orphan 1nd combine BlockUtils.orphanToBlock(repository, pre1stCombineBlockHeight); @@ -279,7 +279,7 @@ public class TransferPrivsTests extends Common { // Sender checks AccountData orphanedDilbertData = repository.getAccountRepository().getAccount(dilbert.getAddress()); checkAccountDataRestored("sender", pre1stCombineDilbertData, orphanedDilbertData); - assertTrue(dilbert.canMint()); + assertTrue(dilbert.canMint(false)); // Recipient checks orphanedChloeData = repository.getAccountRepository().getAccount(chloe.getAddress()); @@ -287,7 +287,7 @@ public class TransferPrivsTests extends Common { // Chloe canMint() would return true here due to Alice-Chloe reward-share minting at top of method, so undo that minting by orphaning back to block 1 BlockUtils.orphanToBlock(repository, 1); - assertFalse(chloe.canMint()); + assertFalse(chloe.canMint(false)); } } From c010ab47db9a80a7289654bb9b16d814b2b9ece4 Mon Sep 17 00:00:00 2001 From: AlphaX-Qortal Date: Tue, 26 Nov 2024 00:03:04 +0100 Subject: [PATCH 4/9] 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 5/9] 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 6/9] 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 7/9] 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 8/9] 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 9/9] 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