diff --git a/src/main/java/org/qortal/account/Account.java b/src/main/java/org/qortal/account/Account.java index 856b79ef..722e70da 100644 --- a/src/main/java/org/qortal/account/Account.java +++ b/src/main/java/org/qortal/account/Account.java @@ -14,6 +14,7 @@ import org.qortal.repository.NameRepository; import org.qortal.repository.Repository; import org.qortal.settings.Settings; import org.qortal.utils.Base58; +import org.qortal.utils.Groups; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; @@ -227,7 +228,7 @@ public class Account { } int level = accountData.getLevel(); - int groupIdToMint = BlockChain.getInstance().getMintingGroupId(); + List groupIdsToMint = Groups.getGroupIdsToMint( BlockChain.getInstance(), blockchainHeight ); int nameCheckHeight = BlockChain.getInstance().getOnlyMintWithNameHeight(); int groupCheckHeight = BlockChain.getInstance().getGroupMemberCheckHeight(); int removeNameCheckHeight = BlockChain.getInstance().getRemoveOnlyMintWithNameHeight(); @@ -261,9 +262,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() && (isGroupValidated || groupRepository.memberExists(groupIdToMint, myAddress)); + return accountData.getBlocksMintedPenalty() == 0 && !myName.isEmpty() && (isGroupValidated || Groups.memberExistsInAnyGroup(groupRepository, groupIdsToMint, myAddress)); } else { - return level >= levelToMint && !myName.isEmpty() && (isGroupValidated || groupRepository.memberExists(groupIdToMint, myAddress)); + return level >= levelToMint && !myName.isEmpty() && (isGroupValidated || Groups.memberExistsInAnyGroup(groupRepository, groupIdsToMint, myAddress)); } } @@ -272,9 +273,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 && (isGroupValidated || groupRepository.memberExists(groupIdToMint, myAddress)); + return accountData.getBlocksMintedPenalty() == 0 && (isGroupValidated || Groups.memberExistsInAnyGroup(groupRepository, groupIdsToMint, myAddress)); } else { - return level >= levelToMint && (isGroupValidated || groupRepository.memberExists(groupIdToMint, myAddress)); + return level >= levelToMint && (isGroupValidated || Groups.memberExistsInAnyGroup(groupRepository, groupIdsToMint, myAddress)); } } diff --git a/src/main/java/org/qortal/block/Block.java b/src/main/java/org/qortal/block/Block.java index 21cbddc4..67e6dd43 100644 --- a/src/main/java/org/qortal/block/Block.java +++ b/src/main/java/org/qortal/block/Block.java @@ -39,6 +39,7 @@ import org.qortal.transform.block.BlockTransformer; import org.qortal.transform.transaction.TransactionTransformer; import org.qortal.utils.Amounts; import org.qortal.utils.Base58; +import org.qortal.utils.Groups; import org.qortal.utils.NTP; import java.io.ByteArrayOutputStream; @@ -150,7 +151,7 @@ public class Block { final BlockChain blockChain = BlockChain.getInstance(); - ExpandedAccount(Repository repository, RewardShareData rewardShareData) throws DataException { + ExpandedAccount(Repository repository, RewardShareData rewardShareData, int blockHeight) throws DataException { this.rewardShareData = rewardShareData; this.sharePercent = this.rewardShareData.getSharePercent(); @@ -159,7 +160,12 @@ 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()); + this.isMinterMember + = Groups.memberExistsInAnyGroup( + repository.getGroupRepository(), + Groups.getGroupIdsToMint(BlockChain.getInstance(), blockHeight), + this.mintingAccount.getAddress() + ); if (this.isRecipientAlsoMinter) { // Self-share: minter is also recipient @@ -435,9 +441,9 @@ public class Block { if (height >= BlockChain.getInstance().getGroupMemberCheckHeight()) { onlineAccounts.removeIf(a -> { try { - int groupId = BlockChain.getInstance().getMintingGroupId(); + List groupIdsToMint = Groups.getGroupIdsToMint(BlockChain.getInstance(), height); String address = Account.getRewardShareMintingAddress(repository, a.getPublicKey()); - boolean isMinterGroupMember = repository.getGroupRepository().memberExists(groupId, address); + boolean isMinterGroupMember = Groups.memberExistsInAnyGroup(repository.getGroupRepository(), groupIdsToMint, address); return !isMinterGroupMember; } catch (DataException e) { // Something went wrong, so remove the account @@ -753,7 +759,7 @@ public class Block { List expandedAccounts = new ArrayList<>(); for (RewardShareData rewardShare : this.cachedOnlineRewardShares) { - expandedAccounts.add(new ExpandedAccount(repository, rewardShare)); + expandedAccounts.add(new ExpandedAccount(repository, rewardShare, this.blockData.getHeight())); } this.cachedExpandedAccounts = expandedAccounts; @@ -2485,11 +2491,10 @@ public class Block { try (final Repository repository = RepositoryManager.getRepository()) { GroupRepository groupRepository = repository.getGroupRepository(); + List mintingGroupIds = Groups.getGroupIdsToMint(BlockChain.getInstance(), this.blockData.getHeight()); + // all minter admins - List minterAdmins - = groupRepository.getGroupAdmins(BlockChain.getInstance().getMintingGroupId()).stream() - .map(GroupAdminData::getAdmin) - .collect(Collectors.toList()); + List minterAdmins = Groups.getAllAdmins(groupRepository, mintingGroupIds); // all minter admins that are online List onlineMinterAdminAccounts diff --git a/src/main/java/org/qortal/block/BlockChain.java b/src/main/java/org/qortal/block/BlockChain.java index 1468fbc3..bce09aed 100644 --- a/src/main/java/org/qortal/block/BlockChain.java +++ b/src/main/java/org/qortal/block/BlockChain.java @@ -212,7 +212,13 @@ public class BlockChain { private int minAccountLevelToRewardShare; private int maxRewardSharesPerFounderMintingAccount; private int founderEffectiveMintingLevel; - private int mintingGroupId; + + public static class IdsForHeight { + public int height; + public List ids; + } + + private List mintingGroupIds; /** Minimum time to retain online account signatures (ms) for block validity checks. */ private long onlineAccountSignaturesMinLifetime; @@ -544,8 +550,8 @@ public class BlockChain { return this.onlineAccountSignaturesMaxLifetime; } - public int getMintingGroupId() { - return this.mintingGroupId; + public List getMintingGroupIds() { + return mintingGroupIds; } public CiyamAtSettings getCiyamAtSettings() { diff --git a/src/main/java/org/qortal/controller/OnlineAccountsManager.java b/src/main/java/org/qortal/controller/OnlineAccountsManager.java index 332bf867..bbca4c7b 100644 --- a/src/main/java/org/qortal/controller/OnlineAccountsManager.java +++ b/src/main/java/org/qortal/controller/OnlineAccountsManager.java @@ -25,6 +25,7 @@ import org.qortal.repository.Repository; import org.qortal.repository.RepositoryManager; import org.qortal.settings.Settings; import org.qortal.utils.Base58; +import org.qortal.utils.Groups; import org.qortal.utils.NTP; import org.qortal.utils.NamedThreadFactory; @@ -225,11 +226,14 @@ public class OnlineAccountsManager { Set onlineAccountsToAdd = new HashSet<>(); Set onlineAccountsToRemove = new HashSet<>(); try (final Repository repository = RepositoryManager.getRepository()) { + + int blockHeight = repository.getBlockRepository().getBlockchainHeight(); + List mintingGroupMemberAddresses - = repository.getGroupRepository() - .getGroupMembers(BlockChain.getInstance().getMintingGroupId()).stream() - .map(GroupMemberData::getMember) - .collect(Collectors.toList()); + = Groups.getAllMembers( + repository.getGroupRepository(), + Groups.getGroupIdsToMint(BlockChain.getInstance(), blockHeight) + ); for (OnlineAccountData onlineAccountData : this.onlineAccountsImportQueue) { if (isStopping) diff --git a/src/main/java/org/qortal/utils/Blocks.java b/src/main/java/org/qortal/utils/Blocks.java index 54ad86da..0681af75 100644 --- a/src/main/java/org/qortal/utils/Blocks.java +++ b/src/main/java/org/qortal/utils/Blocks.java @@ -53,10 +53,10 @@ public class Blocks { // all minting group member addresses List mintingGroupAddresses - = repository.getGroupRepository() - .getGroupMembers(BlockChain.getInstance().getMintingGroupId()).stream() - .map(GroupMemberData::getMember) - .collect(Collectors.toList()); + = Groups.getAllMembers( + repository.getGroupRepository(), + Groups.getGroupIdsToMint(BlockChain.getInstance(), blockData.getHeight()) + ); // all names, indexed by address Map nameByAddress diff --git a/src/main/java/org/qortal/utils/Groups.java b/src/main/java/org/qortal/utils/Groups.java new file mode 100644 index 00000000..131bc93e --- /dev/null +++ b/src/main/java/org/qortal/utils/Groups.java @@ -0,0 +1,122 @@ +package org.qortal.utils; + +import org.qortal.block.BlockChain; +import org.qortal.data.group.GroupAdminData; +import org.qortal.data.group.GroupMemberData; +import org.qortal.repository.DataException; +import org.qortal.repository.GroupRepository; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.HashSet; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * Class Groups + * + * A utility class for group related functionality. + */ +public class Groups { + + /** + * Does the member exist in any of these groups? + * + * @param groupRepository the group data repository + * @param groupsIds the group Ids to look for the address + * @param address the address + * + * @return true if the address is in any of the groups listed otherwise false + * @throws DataException + */ + public static boolean memberExistsInAnyGroup(GroupRepository groupRepository, List groupsIds, String address) throws DataException { + + // if any of the listed groups have the address as a member, then return true + for( Integer groupIdToMint : groupsIds) { + if( groupRepository.memberExists(groupIdToMint, address) ) { + return true; + } + } + + // if none of the listed groups have the address as a member, then return false + return false; + } + + /** + * Get All Members + * + * Get all the group members from a list of groups. + * + * @param groupRepository the group data repository + * @param groupIds the list of group Ids to look at + * + * @return the list of all members belonging to any of the groups, no duplicates + * @throws DataException + */ + public static List getAllMembers( GroupRepository groupRepository, List groupIds ) throws DataException { + // collect all the members in a set, the set keeps out duplicates + Set allMembers = new HashSet<>(); + + // add all members from each group to the all members set + for( int groupId : groupIds ) { + allMembers.addAll( groupRepository.getGroupMembers(groupId).stream().map(GroupMemberData::getMember).collect(Collectors.toList())); + } + + return new ArrayList<>(allMembers); + } + + /** + * Get All Admins + * + * Get all the admins from a list of groups. + * + * @param groupRepository the group data repository + * @param groupIds the list of group Ids to look at + * + * @return the list of all admins to any of the groups, no duplicates + * @throws DataException + */ + public static List getAllAdmins( GroupRepository groupRepository, List groupIds ) throws DataException { + // collect all the admins in a set, the set keeps out duplicates + Set allAdmins = new HashSet<>(); + + // collect admins for each group + for( int groupId : groupIds ) { + allAdmins.addAll( groupRepository.getGroupAdmins(groupId).stream().map(GroupAdminData::getAdmin).collect(Collectors.toList()) ); + } + + return new ArrayList<>(allAdmins); + } + + /** + * Get Group Ids To Mint + * + * @param blockchain the blockchain + * @param blockchainHeight the block height to mint + * + * @return the group Ids for the minting groups at the height given + */ + public static List getGroupIdsToMint(BlockChain blockchain, int blockchainHeight) { + + // sort heights lowest to highest + Comparator compareByHeight = Comparator.comparingInt(entry -> entry.height); + + // sort heights highest to lowest + Comparator compareByHeightReversed = compareByHeight.reversed(); + + // get highest height that is less than the blockchain height + Optional ids = blockchain.getMintingGroupIds().stream() + .filter(entry -> entry.height < blockchainHeight) + .sorted(compareByHeightReversed) + .findFirst(); + + if( ids.isPresent()) { + return ids.get().ids; + } + else { + return new ArrayList<>(0); + } + } +} \ No newline at end of file diff --git a/src/main/resources/blockchain.json b/src/main/resources/blockchain.json index 40ab983a..3264b670 100644 --- a/src/main/resources/blockchain.json +++ b/src/main/resources/blockchain.json @@ -38,7 +38,9 @@ "blockRewardBatchStartHeight": 1508000, "blockRewardBatchSize": 1000, "blockRewardBatchAccountsBlockCount": 25, - "mintingGroupId": 694, + "mintingGroupIds": [ + { "height": 0, "ids": [ 694 ]} + ], "rewardsByHeight": [ { "height": 1, "reward": 5.00 }, { "height": 259201, "reward": 4.75 }, diff --git a/src/test/java/org/qortal/test/utils/GroupsTestUtils.java b/src/test/java/org/qortal/test/utils/GroupsTestUtils.java new file mode 100644 index 00000000..52f106a7 --- /dev/null +++ b/src/test/java/org/qortal/test/utils/GroupsTestUtils.java @@ -0,0 +1,102 @@ +package org.qortal.test.utils; + +import org.qortal.account.PrivateKeyAccount; +import org.qortal.data.transaction.CreateGroupTransactionData; +import org.qortal.data.transaction.GroupInviteTransactionData; +import org.qortal.data.transaction.JoinGroupTransactionData; +import org.qortal.data.transaction.LeaveGroupTransactionData; +import org.qortal.group.Group; +import org.qortal.repository.DataException; +import org.qortal.repository.Repository; +import org.qortal.test.common.TransactionUtils; +import org.qortal.test.common.transaction.TestTransaction; + +/** + * Class GroupsTestUtils + * + * Utility methods for testing the Groups class. + */ +public class GroupsTestUtils { + + /** + * Create Group + * + * @param repository the data repository + * @param owner the group owner + * @param groupName the group name + * @param isOpen true if the group is public, false for private + * + * @return the group Id + * @throws DataException + */ + public static Integer createGroup(Repository repository, PrivateKeyAccount owner, String groupName, boolean isOpen) throws DataException { + String description = groupName + " (description)"; + + Group.ApprovalThreshold approvalThreshold = Group.ApprovalThreshold.ONE; + int minimumBlockDelay = 10; + int maximumBlockDelay = 1440; + + CreateGroupTransactionData transactionData = new CreateGroupTransactionData(TestTransaction.generateBase(owner), groupName, description, isOpen, approvalThreshold, minimumBlockDelay, maximumBlockDelay); + TransactionUtils.signAndMint(repository, transactionData, owner); + + return repository.getGroupRepository().fromGroupName(groupName).getGroupId(); + } + + /** + * Join Group + * + * @param repository the data repository + * @param joiner the address for the account joining the group + * @param groupId the Id for the group to join + * + * @throws DataException + */ + public static void joinGroup(Repository repository, PrivateKeyAccount joiner, int groupId) throws DataException { + JoinGroupTransactionData transactionData = new JoinGroupTransactionData(TestTransaction.generateBase(joiner), groupId); + TransactionUtils.signAndMint(repository, transactionData, joiner); + } + + /** + * Group Invite + * + * @param repository the data repository + * @param admin the admin account to sign the invite + * @param groupId the Id of the group to invite to + * @param invitee the recipient address for the invite + * @param timeToLive the time length of the invite + * + * @throws DataException + */ + public static void groupInvite(Repository repository, PrivateKeyAccount admin, int groupId, String invitee, int timeToLive) throws DataException { + GroupInviteTransactionData transactionData = new GroupInviteTransactionData(TestTransaction.generateBase(admin), groupId, invitee, timeToLive); + TransactionUtils.signAndMint(repository, transactionData, admin); + } + + /** + * Leave Group + * + * @param repository the data repository + * @param leaver the account leaving + * @param groupId the Id of the group being left + * + * @throws DataException + */ + public static void leaveGroup(Repository repository, PrivateKeyAccount leaver, int groupId) throws DataException { + LeaveGroupTransactionData transactionData = new LeaveGroupTransactionData(TestTransaction.generateBase(leaver), groupId); + TransactionUtils.signAndMint(repository, transactionData, leaver); + } + + /** + * Is Member? + * + * @param repository the data repository + * @param address the account address + * @param groupId the group Id + * + * @return true if the account is a member of the group, otherwise false + * @throws DataException + */ + public static boolean isMember(Repository repository, String address, int groupId) throws DataException { + return repository.getGroupRepository().memberExists(groupId, address); + } +} \ No newline at end of file diff --git a/src/test/java/org/qortal/test/utils/GroupsTests.java b/src/test/java/org/qortal/test/utils/GroupsTests.java new file mode 100644 index 00000000..c9a69f1f --- /dev/null +++ b/src/test/java/org/qortal/test/utils/GroupsTests.java @@ -0,0 +1,199 @@ +package org.qortal.test.utils; + +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.qortal.account.PrivateKeyAccount; +import org.qortal.block.Block; +import org.qortal.block.BlockChain; +import org.qortal.repository.DataException; +import org.qortal.repository.Repository; +import org.qortal.repository.RepositoryManager; +import org.qortal.test.common.BlockUtils; +import org.qortal.test.common.Common; +import org.qortal.utils.Groups; + +import java.util.List; + +import static org.junit.Assert.*; + +public class GroupsTests extends Common { + + public static final String ALICE = "alice"; + public static final String BOB = "bob"; + public static final String CHLOE = "chloe"; + public static final String DILBERT = "dilbert"; + + + private static final int HEIGHT_1 = 5; + private static final int HEIGHT_2 = 8; + private static final int HEIGHT_3 = 12; + + @Before + public void beforeTest() throws DataException { + Common.useDefaultSettings(); + } + + @After + public void afterTest() throws DataException { + Common.orphanCheck(); + } + + @Test + public void testGetGroupIdsToMintSimple() { + List ids = Groups.getGroupIdsToMint(BlockChain.getInstance(), 0); + + Assert.assertNotNull(ids); + Assert.assertEquals(0, ids.size()); + } + + @Test + public void testGetGroupIdsToMintComplex() throws DataException { + try (final Repository repository = RepositoryManager.getRepository()) { + + Block block1 = BlockUtils.mintBlocks(repository, HEIGHT_1); + int height1 = block1.getBlockData().getHeight().intValue(); + assertEquals(HEIGHT_1 + 1, height1); + + List ids1 = Groups.getGroupIdsToMint(BlockChain.getInstance(), height1); + + Assert.assertEquals(1, ids1.size() ); + Assert.assertTrue( ids1.contains( 694 ) ); + + Block block2 = BlockUtils.mintBlocks(repository, HEIGHT_2 - HEIGHT_1); + int height2 = block2.getBlockData().getHeight().intValue(); + assertEquals( HEIGHT_2 + 1, height2); + + List ids2 = Groups.getGroupIdsToMint(BlockChain.getInstance(), height2); + + Assert.assertEquals(2, ids2.size() ); + + Assert.assertTrue( ids2.contains( 694 ) ); + Assert.assertTrue( ids2.contains( 800 ) ); + + Block block3 = BlockUtils.mintBlocks(repository, HEIGHT_3 - HEIGHT_2); + int height3 = block3.getBlockData().getHeight().intValue(); + assertEquals( HEIGHT_3 + 1, height3); + + List ids3 = Groups.getGroupIdsToMint(BlockChain.getInstance(), height3); + + Assert.assertEquals( 1, ids3.size() ); + + Assert.assertTrue( ids3.contains( 800 ) ); + } + } + + @Test + public void testMemberExistsInAnyGroupSimple() throws DataException { + + try (final Repository repository = RepositoryManager.getRepository()) { + + PrivateKeyAccount alice = Common.getTestAccount(repository, "alice"); + PrivateKeyAccount bob = Common.getTestAccount(repository, "bob"); + + // Create group + int groupId = GroupsTestUtils.createGroup(repository, alice, "closed-group", false); + + // Confirm Bob is not a member + Assert.assertFalse( Groups.memberExistsInAnyGroup(repository.getGroupRepository(), List.of(groupId), bob.getAddress()) ); + + // Bob to join + GroupsTestUtils.joinGroup(repository, bob, groupId); + + // Confirm Bob still not a member + assertFalse(GroupsTestUtils.isMember(repository, bob.getAddress(), groupId)); + + // Have Alice 'invite' Bob to confirm membership + GroupsTestUtils.groupInvite(repository, alice, groupId, bob.getAddress(), 0); // non-expiring invite + + // Confirm Bob now a member + Assert.assertTrue( Groups.memberExistsInAnyGroup(repository.getGroupRepository(), List.of(groupId), bob.getAddress()) ); + } + } + + @Test + public void testGroupsListedFunctionality() throws DataException { + + try (final Repository repository = RepositoryManager.getRepository()) { + + PrivateKeyAccount alice = Common.getTestAccount(repository, ALICE); + PrivateKeyAccount bob = Common.getTestAccount(repository, BOB); + PrivateKeyAccount chloe = Common.getTestAccount(repository, CHLOE); + PrivateKeyAccount dilbert = Common.getTestAccount(repository, DILBERT); + + // Create groups + int group1Id = GroupsTestUtils.createGroup(repository, alice, "group-1", false); + int group2Id = GroupsTestUtils.createGroup(repository, bob, "group-2", false); + + // test memberExistsInAnyGroup + Assert.assertTrue(Groups.memberExistsInAnyGroup(repository.getGroupRepository(), List.of(group1Id, group2Id), alice.getAddress())); + Assert.assertFalse(Groups.memberExistsInAnyGroup(repository.getGroupRepository(), List.of(group1Id, group2Id), chloe.getAddress())); + + // alice is a member + Assert.assertTrue(GroupsTestUtils.isMember(repository, alice.getAddress(), group1Id)); + List allMembersBeforeJoin = Groups.getAllMembers(repository.getGroupRepository(), List.of(group1Id)); + + // assert one member + Assert.assertNotNull(allMembersBeforeJoin); + Assert.assertEquals(1, allMembersBeforeJoin.size()); + + List allAdminsBeforeJoin = Groups.getAllAdmins(repository.getGroupRepository(), List.of(group1Id)); + + // assert one admin + Assert.assertNotNull(allAdminsBeforeJoin); + Assert.assertEquals( 1, allAdminsBeforeJoin.size()); + + // Bob to join + GroupsTestUtils.joinGroup(repository, bob, group1Id); + + // Have Alice 'invite' Bob to confirm membership + GroupsTestUtils.groupInvite(repository, alice, group1Id, bob.getAddress(), 0); // non-expiring invite + + List allMembersAfterJoin = Groups.getAllMembers(repository.getGroupRepository(), List.of(group1Id)); + + // alice and bob are members + Assert.assertNotNull(allMembersAfterJoin); + Assert.assertEquals(2, allMembersAfterJoin.size()); + + List allAdminsAfterJoin = Groups.getAllAdmins(repository.getGroupRepository(), List.of(group1Id)); + + // assert still one admin + Assert.assertNotNull(allAdminsAfterJoin); + Assert.assertEquals(1, allAdminsAfterJoin.size()); + + List allAdminsFor2Groups = Groups.getAllAdmins(repository.getGroupRepository(), List.of(group1Id, group2Id)); + + // assert 2 admins when including the second group + Assert.assertNotNull(allAdminsFor2Groups); + Assert.assertEquals(2, allAdminsFor2Groups.size()); + + List allMembersFor2Groups = Groups.getAllMembers(repository.getGroupRepository(), List.of(group1Id, group2Id)); + + // assert 2 members when including the seconds group + Assert.assertNotNull(allMembersFor2Groups); + Assert.assertEquals(2, allMembersFor2Groups.size()); + + GroupsTestUtils.leaveGroup(repository, bob, group1Id); + + List allMembersForAfterBobLeavesGroup1InAllGroups = Groups.getAllMembers(repository.getGroupRepository(), List.of(group1Id, group2Id)); + + // alice and bob are members of one group still + Assert.assertNotNull(allMembersForAfterBobLeavesGroup1InAllGroups); + Assert.assertEquals(2, allMembersForAfterBobLeavesGroup1InAllGroups.size()); + + GroupsTestUtils.groupInvite(repository, alice, group1Id, chloe.getAddress(), 3600); + GroupsTestUtils.groupInvite(repository, bob, group2Id, chloe.getAddress(), 3600); + + GroupsTestUtils.joinGroup(repository, chloe, group1Id); + GroupsTestUtils.joinGroup(repository, chloe, group2Id); + + List allMembersAfterDilbert = Groups.getAllMembers((repository.getGroupRepository()), List.of(group1Id, group2Id)); + + // 3 accounts are now members of one group or another + Assert.assertNotNull(allMembersAfterDilbert); + Assert.assertEquals(3, allMembersAfterDilbert.size()); + } + } + +} \ No newline at end of file diff --git a/src/test/resources/test-chain-v2.json b/src/test/resources/test-chain-v2.json index 4d1d6240..5395116f 100644 --- a/src/test/resources/test-chain-v2.json +++ b/src/test/resources/test-chain-v2.json @@ -31,6 +31,12 @@ "blockRewardBatchStartHeight": 999999000, "blockRewardBatchSize": 10, "blockRewardBatchAccountsBlockCount": 3, + "mintingGroupIds": [ + { "height": 0, "ids": []}, + { "height": 5, "ids": [694]}, + { "height": 8, "ids": [694, 800]}, + { "height": 12, "ids": [800]} + ], "rewardsByHeight": [ { "height": 1, "reward": 100 }, { "height": 11, "reward": 10 },