mirror of
https://github.com/Qortal/qortal.git
synced 2025-04-16 16:15:53 +00:00
supporting multiple minting groups instead of supporting one and only one minting group
This commit is contained in:
parent
9017db725e
commit
91ceafe0e3
@ -14,6 +14,7 @@ import org.qortal.repository.NameRepository;
|
|||||||
import org.qortal.repository.Repository;
|
import org.qortal.repository.Repository;
|
||||||
import org.qortal.settings.Settings;
|
import org.qortal.settings.Settings;
|
||||||
import org.qortal.utils.Base58;
|
import org.qortal.utils.Base58;
|
||||||
|
import org.qortal.utils.Groups;
|
||||||
|
|
||||||
import javax.xml.bind.annotation.XmlAccessType;
|
import javax.xml.bind.annotation.XmlAccessType;
|
||||||
import javax.xml.bind.annotation.XmlAccessorType;
|
import javax.xml.bind.annotation.XmlAccessorType;
|
||||||
@ -227,7 +228,7 @@ public class Account {
|
|||||||
}
|
}
|
||||||
|
|
||||||
int level = accountData.getLevel();
|
int level = accountData.getLevel();
|
||||||
int groupIdToMint = BlockChain.getInstance().getMintingGroupId();
|
List<Integer> groupIdsToMint = Groups.getGroupIdsToMint( BlockChain.getInstance(), blockchainHeight );
|
||||||
int nameCheckHeight = BlockChain.getInstance().getOnlyMintWithNameHeight();
|
int nameCheckHeight = BlockChain.getInstance().getOnlyMintWithNameHeight();
|
||||||
int groupCheckHeight = BlockChain.getInstance().getGroupMemberCheckHeight();
|
int groupCheckHeight = BlockChain.getInstance().getGroupMemberCheckHeight();
|
||||||
int removeNameCheckHeight = BlockChain.getInstance().getRemoveOnlyMintWithNameHeight();
|
int removeNameCheckHeight = BlockChain.getInstance().getRemoveOnlyMintWithNameHeight();
|
||||||
@ -261,9 +262,9 @@ public class Account {
|
|||||||
if (blockchainHeight >= groupCheckHeight && blockchainHeight < removeNameCheckHeight) {
|
if (blockchainHeight >= groupCheckHeight && blockchainHeight < removeNameCheckHeight) {
|
||||||
List<NameData> myName = nameRepository.getNamesByOwner(myAddress);
|
List<NameData> myName = nameRepository.getNamesByOwner(myAddress);
|
||||||
if (Account.isFounder(accountData.getFlags())) {
|
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 {
|
} 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
|
// Account's address is a member of the minter group
|
||||||
if (blockchainHeight >= removeNameCheckHeight) {
|
if (blockchainHeight >= removeNameCheckHeight) {
|
||||||
if (Account.isFounder(accountData.getFlags())) {
|
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 {
|
} else {
|
||||||
return level >= levelToMint && (isGroupValidated || groupRepository.memberExists(groupIdToMint, myAddress));
|
return level >= levelToMint && (isGroupValidated || Groups.memberExistsInAnyGroup(groupRepository, groupIdsToMint, myAddress));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -39,6 +39,7 @@ import org.qortal.transform.block.BlockTransformer;
|
|||||||
import org.qortal.transform.transaction.TransactionTransformer;
|
import org.qortal.transform.transaction.TransactionTransformer;
|
||||||
import org.qortal.utils.Amounts;
|
import org.qortal.utils.Amounts;
|
||||||
import org.qortal.utils.Base58;
|
import org.qortal.utils.Base58;
|
||||||
|
import org.qortal.utils.Groups;
|
||||||
import org.qortal.utils.NTP;
|
import org.qortal.utils.NTP;
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
@ -150,7 +151,7 @@ public class Block {
|
|||||||
|
|
||||||
final BlockChain blockChain = BlockChain.getInstance();
|
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.rewardShareData = rewardShareData;
|
||||||
this.sharePercent = this.rewardShareData.getSharePercent();
|
this.sharePercent = this.rewardShareData.getSharePercent();
|
||||||
|
|
||||||
@ -159,7 +160,12 @@ public class Block {
|
|||||||
this.isMinterFounder = Account.isFounder(mintingAccountData.getFlags());
|
this.isMinterFounder = Account.isFounder(mintingAccountData.getFlags());
|
||||||
|
|
||||||
this.isRecipientAlsoMinter = this.rewardShareData.getRecipient().equals(this.mintingAccount.getAddress());
|
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) {
|
if (this.isRecipientAlsoMinter) {
|
||||||
// Self-share: minter is also recipient
|
// Self-share: minter is also recipient
|
||||||
@ -435,9 +441,9 @@ public class Block {
|
|||||||
if (height >= BlockChain.getInstance().getGroupMemberCheckHeight()) {
|
if (height >= BlockChain.getInstance().getGroupMemberCheckHeight()) {
|
||||||
onlineAccounts.removeIf(a -> {
|
onlineAccounts.removeIf(a -> {
|
||||||
try {
|
try {
|
||||||
int groupId = BlockChain.getInstance().getMintingGroupId();
|
List<Integer> groupIdsToMint = Groups.getGroupIdsToMint(BlockChain.getInstance(), height);
|
||||||
String address = Account.getRewardShareMintingAddress(repository, a.getPublicKey());
|
String address = Account.getRewardShareMintingAddress(repository, a.getPublicKey());
|
||||||
boolean isMinterGroupMember = repository.getGroupRepository().memberExists(groupId, address);
|
boolean isMinterGroupMember = Groups.memberExistsInAnyGroup(repository.getGroupRepository(), groupIdsToMint, address);
|
||||||
return !isMinterGroupMember;
|
return !isMinterGroupMember;
|
||||||
} catch (DataException e) {
|
} catch (DataException e) {
|
||||||
// Something went wrong, so remove the account
|
// Something went wrong, so remove the account
|
||||||
@ -753,7 +759,7 @@ public class Block {
|
|||||||
List<ExpandedAccount> expandedAccounts = new ArrayList<>();
|
List<ExpandedAccount> expandedAccounts = new ArrayList<>();
|
||||||
|
|
||||||
for (RewardShareData rewardShare : this.cachedOnlineRewardShares) {
|
for (RewardShareData rewardShare : this.cachedOnlineRewardShares) {
|
||||||
expandedAccounts.add(new ExpandedAccount(repository, rewardShare));
|
expandedAccounts.add(new ExpandedAccount(repository, rewardShare, this.blockData.getHeight()));
|
||||||
}
|
}
|
||||||
|
|
||||||
this.cachedExpandedAccounts = expandedAccounts;
|
this.cachedExpandedAccounts = expandedAccounts;
|
||||||
@ -2485,11 +2491,10 @@ public class Block {
|
|||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
GroupRepository groupRepository = repository.getGroupRepository();
|
GroupRepository groupRepository = repository.getGroupRepository();
|
||||||
|
|
||||||
|
List<Integer> mintingGroupIds = Groups.getGroupIdsToMint(BlockChain.getInstance(), this.blockData.getHeight());
|
||||||
|
|
||||||
// all minter admins
|
// all minter admins
|
||||||
List<String> minterAdmins
|
List<String> minterAdmins = Groups.getAllAdmins(groupRepository, mintingGroupIds);
|
||||||
= groupRepository.getGroupAdmins(BlockChain.getInstance().getMintingGroupId()).stream()
|
|
||||||
.map(GroupAdminData::getAdmin)
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
|
|
||||||
// all minter admins that are online
|
// all minter admins that are online
|
||||||
List<ExpandedAccount> onlineMinterAdminAccounts
|
List<ExpandedAccount> onlineMinterAdminAccounts
|
||||||
|
@ -212,7 +212,13 @@ public class BlockChain {
|
|||||||
private int minAccountLevelToRewardShare;
|
private int minAccountLevelToRewardShare;
|
||||||
private int maxRewardSharesPerFounderMintingAccount;
|
private int maxRewardSharesPerFounderMintingAccount;
|
||||||
private int founderEffectiveMintingLevel;
|
private int founderEffectiveMintingLevel;
|
||||||
private int mintingGroupId;
|
|
||||||
|
public static class IdsForHeight {
|
||||||
|
public int height;
|
||||||
|
public List<Integer> ids;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<IdsForHeight> mintingGroupIds;
|
||||||
|
|
||||||
/** Minimum time to retain online account signatures (ms) for block validity checks. */
|
/** Minimum time to retain online account signatures (ms) for block validity checks. */
|
||||||
private long onlineAccountSignaturesMinLifetime;
|
private long onlineAccountSignaturesMinLifetime;
|
||||||
@ -544,8 +550,8 @@ public class BlockChain {
|
|||||||
return this.onlineAccountSignaturesMaxLifetime;
|
return this.onlineAccountSignaturesMaxLifetime;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getMintingGroupId() {
|
public List<IdsForHeight> getMintingGroupIds() {
|
||||||
return this.mintingGroupId;
|
return mintingGroupIds;
|
||||||
}
|
}
|
||||||
|
|
||||||
public CiyamAtSettings getCiyamAtSettings() {
|
public CiyamAtSettings getCiyamAtSettings() {
|
||||||
|
@ -25,6 +25,7 @@ import org.qortal.repository.Repository;
|
|||||||
import org.qortal.repository.RepositoryManager;
|
import org.qortal.repository.RepositoryManager;
|
||||||
import org.qortal.settings.Settings;
|
import org.qortal.settings.Settings;
|
||||||
import org.qortal.utils.Base58;
|
import org.qortal.utils.Base58;
|
||||||
|
import org.qortal.utils.Groups;
|
||||||
import org.qortal.utils.NTP;
|
import org.qortal.utils.NTP;
|
||||||
import org.qortal.utils.NamedThreadFactory;
|
import org.qortal.utils.NamedThreadFactory;
|
||||||
|
|
||||||
@ -225,11 +226,14 @@ public class OnlineAccountsManager {
|
|||||||
Set<OnlineAccountData> onlineAccountsToAdd = new HashSet<>();
|
Set<OnlineAccountData> onlineAccountsToAdd = new HashSet<>();
|
||||||
Set<OnlineAccountData> onlineAccountsToRemove = new HashSet<>();
|
Set<OnlineAccountData> onlineAccountsToRemove = new HashSet<>();
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
|
|
||||||
|
int blockHeight = repository.getBlockRepository().getBlockchainHeight();
|
||||||
|
|
||||||
List<String> mintingGroupMemberAddresses
|
List<String> mintingGroupMemberAddresses
|
||||||
= repository.getGroupRepository()
|
= Groups.getAllMembers(
|
||||||
.getGroupMembers(BlockChain.getInstance().getMintingGroupId()).stream()
|
repository.getGroupRepository(),
|
||||||
.map(GroupMemberData::getMember)
|
Groups.getGroupIdsToMint(BlockChain.getInstance(), blockHeight)
|
||||||
.collect(Collectors.toList());
|
);
|
||||||
|
|
||||||
for (OnlineAccountData onlineAccountData : this.onlineAccountsImportQueue) {
|
for (OnlineAccountData onlineAccountData : this.onlineAccountsImportQueue) {
|
||||||
if (isStopping)
|
if (isStopping)
|
||||||
|
@ -53,10 +53,10 @@ public class Blocks {
|
|||||||
|
|
||||||
// all minting group member addresses
|
// all minting group member addresses
|
||||||
List<String> mintingGroupAddresses
|
List<String> mintingGroupAddresses
|
||||||
= repository.getGroupRepository()
|
= Groups.getAllMembers(
|
||||||
.getGroupMembers(BlockChain.getInstance().getMintingGroupId()).stream()
|
repository.getGroupRepository(),
|
||||||
.map(GroupMemberData::getMember)
|
Groups.getGroupIdsToMint(BlockChain.getInstance(), blockData.getHeight())
|
||||||
.collect(Collectors.toList());
|
);
|
||||||
|
|
||||||
// all names, indexed by address
|
// all names, indexed by address
|
||||||
Map<String, String> nameByAddress
|
Map<String, String> nameByAddress
|
||||||
|
122
src/main/java/org/qortal/utils/Groups.java
Normal file
122
src/main/java/org/qortal/utils/Groups.java
Normal file
@ -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<Integer> 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<String> getAllMembers( GroupRepository groupRepository, List<Integer> groupIds ) throws DataException {
|
||||||
|
// collect all the members in a set, the set keeps out duplicates
|
||||||
|
Set<String> 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<String> getAllAdmins( GroupRepository groupRepository, List<Integer> groupIds ) throws DataException {
|
||||||
|
// collect all the admins in a set, the set keeps out duplicates
|
||||||
|
Set<String> 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<Integer> getGroupIdsToMint(BlockChain blockchain, int blockchainHeight) {
|
||||||
|
|
||||||
|
// sort heights lowest to highest
|
||||||
|
Comparator<BlockChain.IdsForHeight> compareByHeight = Comparator.comparingInt(entry -> entry.height);
|
||||||
|
|
||||||
|
// sort heights highest to lowest
|
||||||
|
Comparator<BlockChain.IdsForHeight> compareByHeightReversed = compareByHeight.reversed();
|
||||||
|
|
||||||
|
// get highest height that is less than the blockchain height
|
||||||
|
Optional<BlockChain.IdsForHeight> ids = blockchain.getMintingGroupIds().stream()
|
||||||
|
.filter(entry -> entry.height < blockchainHeight)
|
||||||
|
.sorted(compareByHeightReversed)
|
||||||
|
.findFirst();
|
||||||
|
|
||||||
|
if( ids.isPresent()) {
|
||||||
|
return ids.get().ids;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return new ArrayList<>(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -38,7 +38,9 @@
|
|||||||
"blockRewardBatchStartHeight": 1508000,
|
"blockRewardBatchStartHeight": 1508000,
|
||||||
"blockRewardBatchSize": 1000,
|
"blockRewardBatchSize": 1000,
|
||||||
"blockRewardBatchAccountsBlockCount": 25,
|
"blockRewardBatchAccountsBlockCount": 25,
|
||||||
"mintingGroupId": 694,
|
"mintingGroupIds": [
|
||||||
|
{ "height": 0, "ids": [ 694 ]}
|
||||||
|
],
|
||||||
"rewardsByHeight": [
|
"rewardsByHeight": [
|
||||||
{ "height": 1, "reward": 5.00 },
|
{ "height": 1, "reward": 5.00 },
|
||||||
{ "height": 259201, "reward": 4.75 },
|
{ "height": 259201, "reward": 4.75 },
|
||||||
|
102
src/test/java/org/qortal/test/utils/GroupsTestUtils.java
Normal file
102
src/test/java/org/qortal/test/utils/GroupsTestUtils.java
Normal file
@ -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);
|
||||||
|
}
|
||||||
|
}
|
199
src/test/java/org/qortal/test/utils/GroupsTests.java
Normal file
199
src/test/java/org/qortal/test/utils/GroupsTests.java
Normal file
@ -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<Integer> 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<Integer> 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<Integer> 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<Integer> 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<String> allMembersBeforeJoin = Groups.getAllMembers(repository.getGroupRepository(), List.of(group1Id));
|
||||||
|
|
||||||
|
// assert one member
|
||||||
|
Assert.assertNotNull(allMembersBeforeJoin);
|
||||||
|
Assert.assertEquals(1, allMembersBeforeJoin.size());
|
||||||
|
|
||||||
|
List<String> 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<String> allMembersAfterJoin = Groups.getAllMembers(repository.getGroupRepository(), List.of(group1Id));
|
||||||
|
|
||||||
|
// alice and bob are members
|
||||||
|
Assert.assertNotNull(allMembersAfterJoin);
|
||||||
|
Assert.assertEquals(2, allMembersAfterJoin.size());
|
||||||
|
|
||||||
|
List<String> allAdminsAfterJoin = Groups.getAllAdmins(repository.getGroupRepository(), List.of(group1Id));
|
||||||
|
|
||||||
|
// assert still one admin
|
||||||
|
Assert.assertNotNull(allAdminsAfterJoin);
|
||||||
|
Assert.assertEquals(1, allAdminsAfterJoin.size());
|
||||||
|
|
||||||
|
List<String> 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<String> 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<String> 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<String> 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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -31,6 +31,12 @@
|
|||||||
"blockRewardBatchStartHeight": 999999000,
|
"blockRewardBatchStartHeight": 999999000,
|
||||||
"blockRewardBatchSize": 10,
|
"blockRewardBatchSize": 10,
|
||||||
"blockRewardBatchAccountsBlockCount": 3,
|
"blockRewardBatchAccountsBlockCount": 3,
|
||||||
|
"mintingGroupIds": [
|
||||||
|
{ "height": 0, "ids": []},
|
||||||
|
{ "height": 5, "ids": [694]},
|
||||||
|
{ "height": 8, "ids": [694, 800]},
|
||||||
|
{ "height": 12, "ids": [800]}
|
||||||
|
],
|
||||||
"rewardsByHeight": [
|
"rewardsByHeight": [
|
||||||
{ "height": 1, "reward": 100 },
|
{ "height": 1, "reward": 100 },
|
||||||
{ "height": 11, "reward": 10 },
|
{ "height": 11, "reward": 10 },
|
||||||
|
Loading…
x
Reference in New Issue
Block a user