diff --git a/src/main/java/org/qortal/account/Account.java b/src/main/java/org/qortal/account/Account.java index 1aaceffb0..537f07881 100644 --- a/src/main/java/org/qortal/account/Account.java +++ b/src/main/java/org/qortal/account/Account.java @@ -205,10 +205,11 @@ public boolean isFounder() throws DataException { *
  • 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 boolean canMint() throws DataException { 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 boolean canMint() throws DataException { // 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 9a61a21b2..279485bc1 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 String addMintingAccount(@HeaderParam(Security.API_KEY_HEADER) String api // 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 eba55069d..ddde81e39 100644 --- a/src/main/java/org/qortal/block/Block.java +++ b/src/main/java/org/qortal/block/Block.java @@ -1518,7 +1518,7 @@ protected boolean isMinterValid(Block parentBlock) throws DataException { 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 7ea2242ce..a1fb9769a 100644 --- a/src/main/java/org/qortal/controller/BlockMinter.java +++ b/src/main/java/org/qortal/controller/BlockMinter.java @@ -148,7 +148,7 @@ public void run() { } 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 5572dee1d..332bf867c 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.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 @@ private void processOnlineAccountsImportQueue() { 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 @@ private void processOnlineAccountsImportQueue() { continue; } - boolean isValid = this.isValidCurrentAccount(repository, onlineAccountData); + boolean isValid = this.isValidCurrentAccount(repository, mintingGroupMemberAddresses, onlineAccountData); if (isValid) onlineAccountsToAdd.add(onlineAccountData); @@ -315,7 +322,7 @@ public static byte[] xorByteArrayInPlace(byte[] inplaceArray, byte[] otherArray) 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 @@ private static boolean isValidCurrentAccount(Repository repository, OnlineAccoun 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 @@ private boolean computeOurAccountsForTimestamp(Long onlineAccountsTimestamp) { } 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 50eead310..317342041 100644 --- a/src/main/java/org/qortal/transaction/RewardShareTransaction.java +++ b/src/main/java/org/qortal/transaction/RewardShareTransaction.java @@ -123,7 +123,7 @@ public ValidationResult isValid() throws DataException { 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 86a0e7433..ad1d2a2a9 100644 --- a/src/test/java/org/qortal/test/TransferPrivsTests.java +++ b/src/test/java/org/qortal/test/TransferPrivsTests.java @@ -74,7 +74,7 @@ public void testNewAccountsTransferPrivs() throws DataException { 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 void testAliceIntoNewAccountTransferPrivs() throws DataException { combineAccounts(repository, alice, randomAccount, aliceMintingAccount); - assertFalse(alice.canMint()); - assertTrue(randomAccount.canMint()); + assertFalse(alice.canMint(false)); + assertTrue(randomAccount.canMint(false)); } } @@ -97,8 +97,8 @@ public void testAliceIntoDilbertTransferPrivs() throws DataException { 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 void testAliceIntoDilbertTransferPrivs() throws DataException { // 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 void testAliceIntoDilbertTransferPrivs() throws DataException { // 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 void testDilbertIntoAliceTransferPrivs() throws DataException { 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 void testDilbertIntoAliceTransferPrivs() throws DataException { // 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 void testDilbertIntoAliceTransferPrivs() throws DataException { // 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 void testMultipleIntoChloeTransferPrivs() throws DataException { 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 void testMultipleIntoChloeTransferPrivs() throws DataException { // 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 void testMultipleIntoChloeTransferPrivs() throws DataException { // 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 void testMultipleIntoChloeTransferPrivs() throws DataException { // 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 void testMultipleIntoChloeTransferPrivs() throws DataException { // 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 void testMultipleIntoChloeTransferPrivs() throws DataException { // 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)); } }