3
0
mirror of https://github.com/Qortal/qortal.git synced 2025-02-11 17:55:50 +00:00

added capabilities for groups with null ownership including banning and kicking members and member ban cancellations; enforcing group approval thresholds to invites and invite cancellations; the established add and remove admin capabilities were used as guidance for this implementation; this was added as a hardfork to preserve group transactions from previous blocks

This commit is contained in:
kennycud 2024-12-29 18:08:04 -08:00
parent bdbbd0152f
commit a300ac2393
11 changed files with 497 additions and 97 deletions

View File

@ -90,7 +90,8 @@ public class BlockChain {
groupMemberCheckHeight, groupMemberCheckHeight,
fixBatchRewardHeight, fixBatchRewardHeight,
adminsReplaceFoundersHeight, adminsReplaceFoundersHeight,
onlineValidationFailSafeHeight onlineValidationFailSafeHeight,
nullGroupMembershipHeight
} }
// Custom transaction fees // Custom transaction fees
@ -672,6 +673,10 @@ public class BlockChain {
return this.featureTriggers.get(FeatureTrigger.onlineValidationFailSafeHeight.name()).intValue(); return this.featureTriggers.get(FeatureTrigger.onlineValidationFailSafeHeight.name()).intValue();
} }
public int getNullGroupMembershipHeight() {
return this.featureTriggers.get(FeatureTrigger.nullGroupMembershipHeight.name()).intValue();
}
// More complex getters for aspects that change by height or timestamp // More complex getters for aspects that change by height or timestamp
public long getRewardAtHeight(int ourHeight) { public long getRewardAtHeight(int ourHeight) {

View File

@ -24,7 +24,7 @@ public class HSQLDBGroupRepository implements GroupRepository {
public GroupData fromGroupId(int groupId) throws DataException { public GroupData fromGroupId(int groupId) throws DataException {
String sql = "SELECT group_name, owner, description, created_when, updated_when, reference, is_open, " String sql = "SELECT group_name, owner, description, created_when, updated_when, reference, is_open, "
+ "approval_threshold, min_block_delay, max_block_delay, creation_group_id, reduced_group_name " + "approval_threshold, min_block_delay, max_block_delay, creation_group_id, reduced_group_name "
+ "FROM Groups WHERE group_id = ?"; + "FROM `Groups` WHERE group_id = ?";
try (ResultSet resultSet = this.repository.checkedExecute(sql, groupId)) { try (ResultSet resultSet = this.repository.checkedExecute(sql, groupId)) {
if (resultSet == null) if (resultSet == null)
@ -62,7 +62,7 @@ public class HSQLDBGroupRepository implements GroupRepository {
public GroupData fromGroupName(String groupName) throws DataException { public GroupData fromGroupName(String groupName) throws DataException {
String sql = "SELECT group_id, owner, description, created_when, updated_when, reference, is_open, " String sql = "SELECT group_id, owner, description, created_when, updated_when, reference, is_open, "
+ "approval_threshold, min_block_delay, max_block_delay, creation_group_id, reduced_group_name " + "approval_threshold, min_block_delay, max_block_delay, creation_group_id, reduced_group_name "
+ "FROM Groups WHERE group_name = ?"; + "FROM `Groups` WHERE group_name = ?";
try (ResultSet resultSet = this.repository.checkedExecute(sql, groupName)) { try (ResultSet resultSet = this.repository.checkedExecute(sql, groupName)) {
if (resultSet == null) if (resultSet == null)
@ -99,7 +99,7 @@ public class HSQLDBGroupRepository implements GroupRepository {
@Override @Override
public boolean groupExists(int groupId) throws DataException { public boolean groupExists(int groupId) throws DataException {
try { try {
return this.repository.exists("Groups", "group_id = ?", groupId); return this.repository.exists("`Groups`", "group_id = ?", groupId);
} catch (SQLException e) { } catch (SQLException e) {
throw new DataException("Unable to check for group in repository", e); throw new DataException("Unable to check for group in repository", e);
} }
@ -108,7 +108,7 @@ public class HSQLDBGroupRepository implements GroupRepository {
@Override @Override
public boolean groupExists(String groupName) throws DataException { public boolean groupExists(String groupName) throws DataException {
try { try {
return this.repository.exists("Groups", "group_name = ?", groupName); return this.repository.exists("`Groups`", "group_name = ?", groupName);
} catch (SQLException e) { } catch (SQLException e) {
throw new DataException("Unable to check for group in repository", e); throw new DataException("Unable to check for group in repository", e);
} }
@ -117,7 +117,7 @@ public class HSQLDBGroupRepository implements GroupRepository {
@Override @Override
public boolean reducedGroupNameExists(String reducedGroupName) throws DataException { public boolean reducedGroupNameExists(String reducedGroupName) throws DataException {
try { try {
return this.repository.exists("Groups", "reduced_group_name = ?", reducedGroupName); return this.repository.exists("`Groups`", "reduced_group_name = ?", reducedGroupName);
} catch (SQLException e) { } catch (SQLException e) {
throw new DataException("Unable to check for reduced group name in repository", e); throw new DataException("Unable to check for reduced group name in repository", e);
} }
@ -129,7 +129,7 @@ public class HSQLDBGroupRepository implements GroupRepository {
sql.append("SELECT group_id, owner, group_name, description, created_when, updated_when, reference, is_open, " sql.append("SELECT group_id, owner, group_name, description, created_when, updated_when, reference, is_open, "
+ "approval_threshold, min_block_delay, max_block_delay, creation_group_id, reduced_group_name " + "approval_threshold, min_block_delay, max_block_delay, creation_group_id, reduced_group_name "
+ "FROM Groups ORDER BY group_name"); + "FROM `Groups` ORDER BY group_name");
if (reverse != null && reverse) if (reverse != null && reverse)
sql.append(" DESC"); sql.append(" DESC");
@ -181,7 +181,7 @@ public class HSQLDBGroupRepository implements GroupRepository {
sql.append("SELECT group_id, group_name, description, created_when, updated_when, reference, is_open, " sql.append("SELECT group_id, group_name, description, created_when, updated_when, reference, is_open, "
+ "approval_threshold, min_block_delay, max_block_delay, creation_group_id, reduced_group_name " + "approval_threshold, min_block_delay, max_block_delay, creation_group_id, reduced_group_name "
+ "FROM Groups WHERE owner = ? ORDER BY group_name"); + "FROM `Groups` WHERE owner = ? ORDER BY group_name");
if (reverse != null && reverse) if (reverse != null && reverse)
sql.append(" DESC"); sql.append(" DESC");
@ -231,7 +231,7 @@ public class HSQLDBGroupRepository implements GroupRepository {
StringBuilder sql = new StringBuilder(512); StringBuilder sql = new StringBuilder(512);
sql.append("SELECT group_id, owner, group_name, description, created_when, updated_when, reference, is_open, " sql.append("SELECT group_id, owner, group_name, description, created_when, updated_when, reference, is_open, "
+ "approval_threshold, min_block_delay, max_block_delay, creation_group_id, reduced_group_name, admin FROM Groups " + "approval_threshold, min_block_delay, max_block_delay, creation_group_id, reduced_group_name, admin FROM `Groups` "
+ "JOIN GroupMembers USING (group_id) " + "JOIN GroupMembers USING (group_id) "
+ "LEFT OUTER JOIN GroupAdmins ON GroupAdmins.group_id = GroupMembers.group_id AND GroupAdmins.admin = GroupMembers.address " + "LEFT OUTER JOIN GroupAdmins ON GroupAdmins.group_id = GroupMembers.group_id AND GroupAdmins.admin = GroupMembers.address "
+ "WHERE address = ? ORDER BY group_name"); + "WHERE address = ? ORDER BY group_name");
@ -289,7 +289,7 @@ public class HSQLDBGroupRepository implements GroupRepository {
@Override @Override
public void save(GroupData groupData) throws DataException { public void save(GroupData groupData) throws DataException {
HSQLDBSaver saveHelper = new HSQLDBSaver("Groups"); HSQLDBSaver saveHelper = new HSQLDBSaver("`Groups`");
saveHelper.bind("group_id", groupData.getGroupId()).bind("owner", groupData.getOwner()).bind("group_name", groupData.getGroupName()) saveHelper.bind("group_id", groupData.getGroupId()).bind("owner", groupData.getOwner()).bind("group_name", groupData.getGroupName())
.bind("description", groupData.getDescription()).bind("created_when", groupData.getCreated()).bind("updated_when", groupData.getUpdated()) .bind("description", groupData.getDescription()).bind("created_when", groupData.getCreated()).bind("updated_when", groupData.getUpdated())
@ -302,7 +302,7 @@ public class HSQLDBGroupRepository implements GroupRepository {
if (groupData.getGroupId() == null) { if (groupData.getGroupId() == null) {
// Fetch new groupId // Fetch new groupId
try (ResultSet resultSet = this.repository.checkedExecute("SELECT group_id FROM Groups WHERE reference = ?", groupData.getReference())) { try (ResultSet resultSet = this.repository.checkedExecute("SELECT group_id FROM `Groups` WHERE reference = ?", groupData.getReference())) {
if (resultSet == null) if (resultSet == null)
throw new DataException("Unable to fetch new group ID from repository"); throw new DataException("Unable to fetch new group ID from repository");
@ -318,7 +318,7 @@ public class HSQLDBGroupRepository implements GroupRepository {
public void delete(int groupId) throws DataException { public void delete(int groupId) throws DataException {
try { try {
// Remove group // Remove group
this.repository.delete("Groups", "group_id = ?", groupId); this.repository.delete("`Groups`", "group_id = ?", groupId);
} catch (SQLException e) { } catch (SQLException e) {
throw new DataException("Unable to delete group info from repository", e); throw new DataException("Unable to delete group info from repository", e);
} }
@ -328,7 +328,7 @@ public class HSQLDBGroupRepository implements GroupRepository {
public void delete(String groupName) throws DataException { public void delete(String groupName) throws DataException {
try { try {
// Remove group // Remove group
this.repository.delete("Groups", "group_name = ?", groupName); this.repository.delete("`Groups`", "group_name = ?", groupName);
} catch (SQLException e) { } catch (SQLException e) {
throw new DataException("Unable to delete group info from repository", e); throw new DataException("Unable to delete group info from repository", e);
} }
@ -338,7 +338,7 @@ public class HSQLDBGroupRepository implements GroupRepository {
@Override @Override
public String getOwner(int groupId) throws DataException { public String getOwner(int groupId) throws DataException {
try (ResultSet resultSet = this.repository.checkedExecute("SELECT owner FROM Groups WHERE group_id = ?", groupId)) { try (ResultSet resultSet = this.repository.checkedExecute("SELECT owner FROM `Groups` WHERE group_id = ?", groupId)) {
if (resultSet == null) if (resultSet == null)
return null; return null;

View File

@ -2,6 +2,7 @@ package org.qortal.transaction;
import org.qortal.account.Account; import org.qortal.account.Account;
import org.qortal.asset.Asset; import org.qortal.asset.Asset;
import org.qortal.block.BlockChain;
import org.qortal.crypto.Crypto; import org.qortal.crypto.Crypto;
import org.qortal.data.group.GroupData; import org.qortal.data.group.GroupData;
import org.qortal.data.transaction.CancelGroupBanTransactionData; import org.qortal.data.transaction.CancelGroupBanTransactionData;
@ -12,6 +13,7 @@ import org.qortal.repository.Repository;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Objects;
public class CancelGroupBanTransaction extends Transaction { public class CancelGroupBanTransaction extends Transaction {
@ -70,9 +72,26 @@ public class CancelGroupBanTransaction extends Transaction {
if (!this.repository.getGroupRepository().adminExists(groupId, admin.getAddress())) if (!this.repository.getGroupRepository().adminExists(groupId, admin.getAddress()))
return ValidationResult.NOT_GROUP_ADMIN; return ValidationResult.NOT_GROUP_ADMIN;
// Can't unban if not group's current owner if( this.repository.getBlockRepository().getBlockchainHeight() < BlockChain.getInstance().getNullGroupMembershipHeight() ) {
if (!admin.getAddress().equals(groupData.getOwner())) // Can't cancel ban if not group's current owner
return ValidationResult.INVALID_GROUP_OWNER; if (!admin.getAddress().equals(groupData.getOwner()))
return ValidationResult.INVALID_GROUP_OWNER;
}
// if( this.repository.getBlockRepository().getBlockchainHeight() >= BlockChain.getInstance().getNullGroupMembershipHeight() )
else {
String groupOwner = this.repository.getGroupRepository().getOwner(groupId);
boolean groupOwnedByNullAccount = Objects.equals(groupOwner, Group.NULL_OWNER_ADDRESS);
// if null ownership group, then check for admin approval
if(groupOwnedByNullAccount ) {
// Require approval if transaction relates to a group owned by the null account
if (!this.needsGroupApproval())
return ValidationResult.GROUP_APPROVAL_REQUIRED;
}
// Can't cancel ban if not group's current owner
else if (!admin.getAddress().equals(groupData.getOwner()))
return ValidationResult.INVALID_GROUP_OWNER;
}
Account member = getMember(); Account member = getMember();

View File

@ -2,6 +2,7 @@ package org.qortal.transaction;
import org.qortal.account.Account; import org.qortal.account.Account;
import org.qortal.asset.Asset; import org.qortal.asset.Asset;
import org.qortal.block.BlockChain;
import org.qortal.crypto.Crypto; import org.qortal.crypto.Crypto;
import org.qortal.data.group.GroupData; import org.qortal.data.group.GroupData;
import org.qortal.data.transaction.CancelGroupInviteTransactionData; import org.qortal.data.transaction.CancelGroupInviteTransactionData;
@ -12,6 +13,7 @@ import org.qortal.repository.Repository;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Objects;
public class CancelGroupInviteTransaction extends Transaction { public class CancelGroupInviteTransaction extends Transaction {
@ -80,6 +82,16 @@ public class CancelGroupInviteTransaction extends Transaction {
if (admin.getConfirmedBalance(Asset.QORT) < this.cancelGroupInviteTransactionData.getFee()) if (admin.getConfirmedBalance(Asset.QORT) < this.cancelGroupInviteTransactionData.getFee())
return ValidationResult.NO_BALANCE; return ValidationResult.NO_BALANCE;
// if null ownership group, then check for admin approval
if( this.repository.getBlockRepository().getBlockchainHeight() >= BlockChain.getInstance().getNullGroupMembershipHeight() ) {
String groupOwner = this.repository.getGroupRepository().getOwner(groupId);
boolean groupOwnedByNullAccount = Objects.equals(groupOwner, Group.NULL_OWNER_ADDRESS);
// Require approval if transaction relates to a group owned by the null account
if (groupOwnedByNullAccount && !this.needsGroupApproval())
return ValidationResult.GROUP_APPROVAL_REQUIRED;
}
return ValidationResult.OK; return ValidationResult.OK;
} }

View File

@ -2,6 +2,7 @@ package org.qortal.transaction;
import org.qortal.account.Account; import org.qortal.account.Account;
import org.qortal.asset.Asset; import org.qortal.asset.Asset;
import org.qortal.block.BlockChain;
import org.qortal.crypto.Crypto; import org.qortal.crypto.Crypto;
import org.qortal.data.group.GroupData; import org.qortal.data.group.GroupData;
import org.qortal.data.transaction.GroupBanTransactionData; import org.qortal.data.transaction.GroupBanTransactionData;
@ -12,6 +13,7 @@ import org.qortal.repository.Repository;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Objects;
public class GroupBanTransaction extends Transaction { public class GroupBanTransaction extends Transaction {
@ -70,9 +72,25 @@ public class GroupBanTransaction extends Transaction {
if (!this.repository.getGroupRepository().adminExists(groupId, admin.getAddress())) if (!this.repository.getGroupRepository().adminExists(groupId, admin.getAddress()))
return ValidationResult.NOT_GROUP_ADMIN; return ValidationResult.NOT_GROUP_ADMIN;
// Can't ban if not group's current owner if( this.repository.getBlockRepository().getBlockchainHeight() < BlockChain.getInstance().getNullGroupMembershipHeight() ) {
if (!admin.getAddress().equals(groupData.getOwner())) // Can't ban if not group's current owner
return ValidationResult.INVALID_GROUP_OWNER; if (!admin.getAddress().equals(groupData.getOwner()))
return ValidationResult.INVALID_GROUP_OWNER;
}
// if( this.repository.getBlockRepository().getBlockchainHeight() >= BlockChain.getInstance().getNullGroupMembershipHeight() )
else {
String groupOwner = this.repository.getGroupRepository().getOwner(groupId);
boolean groupOwnedByNullAccount = Objects.equals(groupOwner, Group.NULL_OWNER_ADDRESS);
// if null ownership group, then check for admin approval
if(groupOwnedByNullAccount ) {
// Require approval if transaction relates to a group owned by the null account
if (!this.needsGroupApproval())
return ValidationResult.GROUP_APPROVAL_REQUIRED;
}
else if (!admin.getAddress().equals(groupData.getOwner()))
return ValidationResult.INVALID_GROUP_OWNER;
}
Account offender = getOffender(); Account offender = getOffender();

View File

@ -2,6 +2,7 @@ package org.qortal.transaction;
import org.qortal.account.Account; import org.qortal.account.Account;
import org.qortal.asset.Asset; import org.qortal.asset.Asset;
import org.qortal.block.BlockChain;
import org.qortal.crypto.Crypto; import org.qortal.crypto.Crypto;
import org.qortal.data.transaction.GroupInviteTransactionData; import org.qortal.data.transaction.GroupInviteTransactionData;
import org.qortal.data.transaction.TransactionData; import org.qortal.data.transaction.TransactionData;
@ -11,6 +12,7 @@ import org.qortal.repository.Repository;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Objects;
public class GroupInviteTransaction extends Transaction { public class GroupInviteTransaction extends Transaction {
@ -85,6 +87,16 @@ public class GroupInviteTransaction extends Transaction {
if (admin.getConfirmedBalance(Asset.QORT) < this.groupInviteTransactionData.getFee()) if (admin.getConfirmedBalance(Asset.QORT) < this.groupInviteTransactionData.getFee())
return ValidationResult.NO_BALANCE; return ValidationResult.NO_BALANCE;
// if null ownership group, then check for admin approval
if( this.repository.getBlockRepository().getBlockchainHeight() >= BlockChain.getInstance().getNullGroupMembershipHeight() ) {
String groupOwner = this.repository.getGroupRepository().getOwner(groupId);
boolean groupOwnedByNullAccount = Objects.equals(groupOwner, Group.NULL_OWNER_ADDRESS);
// Require approval if transaction relates to a group owned by the null account
if (groupOwnedByNullAccount && !this.needsGroupApproval())
return ValidationResult.GROUP_APPROVAL_REQUIRED;
}
return ValidationResult.OK; return ValidationResult.OK;
} }

View File

@ -3,6 +3,7 @@ package org.qortal.transaction;
import org.qortal.account.Account; import org.qortal.account.Account;
import org.qortal.account.PublicKeyAccount; import org.qortal.account.PublicKeyAccount;
import org.qortal.asset.Asset; import org.qortal.asset.Asset;
import org.qortal.block.BlockChain;
import org.qortal.crypto.Crypto; import org.qortal.crypto.Crypto;
import org.qortal.data.group.GroupData; import org.qortal.data.group.GroupData;
import org.qortal.data.transaction.GroupKickTransactionData; import org.qortal.data.transaction.GroupKickTransactionData;
@ -14,6 +15,7 @@ import org.qortal.repository.Repository;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Objects;
public class GroupKickTransaction extends Transaction { public class GroupKickTransaction extends Transaction {
@ -82,9 +84,26 @@ public class GroupKickTransaction extends Transaction {
if (!admin.getAddress().equals(groupData.getOwner()) && groupRepository.adminExists(groupId, member.getAddress())) if (!admin.getAddress().equals(groupData.getOwner()) && groupRepository.adminExists(groupId, member.getAddress()))
return ValidationResult.INVALID_GROUP_OWNER; return ValidationResult.INVALID_GROUP_OWNER;
// Can't kick if not group's current owner if( this.repository.getBlockRepository().getBlockchainHeight() < BlockChain.getInstance().getNullGroupMembershipHeight() ) {
if (!admin.getAddress().equals(groupData.getOwner())) // Can't kick if not group's current owner
return ValidationResult.INVALID_GROUP_OWNER; if (!admin.getAddress().equals(groupData.getOwner()))
return ValidationResult.INVALID_GROUP_OWNER;
}
// if( this.repository.getBlockRepository().getBlockchainHeight() >= BlockChain.getInstance().getNullGroupMembershipHeight() )
else {
String groupOwner = this.repository.getGroupRepository().getOwner(groupId);
boolean groupOwnedByNullAccount = Objects.equals(groupOwner, Group.NULL_OWNER_ADDRESS);
// if null ownership group, then check for admin approval
if(groupOwnedByNullAccount ) {
// Require approval if transaction relates to a group owned by the null account
if (!this.needsGroupApproval())
return ValidationResult.GROUP_APPROVAL_REQUIRED;
}
// Can't kick if not group's current owner
else if (!admin.getAddress().equals(groupData.getOwner()))
return ValidationResult.INVALID_GROUP_OWNER;
}
// Check creator has enough funds // Check creator has enough funds
if (admin.getConfirmedBalance(Asset.QORT) < this.groupKickTransactionData.getFee()) if (admin.getConfirmedBalance(Asset.QORT) < this.groupKickTransactionData.getFee())

View File

@ -65,11 +65,11 @@ public abstract class Transaction {
UPDATE_GROUP(23, true), UPDATE_GROUP(23, true),
ADD_GROUP_ADMIN(24, true), ADD_GROUP_ADMIN(24, true),
REMOVE_GROUP_ADMIN(25, true), REMOVE_GROUP_ADMIN(25, true),
GROUP_BAN(26, false), GROUP_BAN(26, true),
CANCEL_GROUP_BAN(27, false), CANCEL_GROUP_BAN(27, true),
GROUP_KICK(28, false), GROUP_KICK(28, true),
GROUP_INVITE(29, false), GROUP_INVITE(29, true),
CANCEL_GROUP_INVITE(30, false), CANCEL_GROUP_INVITE(30, true),
JOIN_GROUP(31, false), JOIN_GROUP(31, false),
LEAVE_GROUP(32, false), LEAVE_GROUP(32, false),
GROUP_APPROVAL(33, false), GROUP_APPROVAL(33, false),

View File

@ -115,7 +115,8 @@
"groupMemberCheckHeight": 1902700, "groupMemberCheckHeight": 1902700,
"fixBatchRewardHeight": 1945900, "fixBatchRewardHeight": 1945900,
"adminsReplaceFoundersHeight": 9999999, "adminsReplaceFoundersHeight": 9999999,
"onlineValidationFailSafeHeight": 9999999 "onlineValidationFailSafeHeight": 9999999,
"nullGroupMembershipHeight": 9999999
}, },
"checkpoints": [ "checkpoints": [
{ "height": 1136300, "signature": "3BbwawEF2uN8Ni5ofpJXkukoU8ctAPxYoFB7whq9pKfBnjfZcpfEJT4R95NvBDoTP8WDyWvsUvbfHbcr9qSZuYpSKZjUQTvdFf6eqznHGEwhZApWfvXu6zjGCxYCp65F4jsVYYJjkzbjmkCg5WAwN5voudngA23kMK6PpTNygapCzXt" } { "height": 1136300, "signature": "3BbwawEF2uN8Ni5ofpJXkukoU8ctAPxYoFB7whq9pKfBnjfZcpfEJT4R95NvBDoTP8WDyWvsUvbfHbcr9qSZuYpSKZjUQTvdFf6eqznHGEwhZApWfvXu6zjGCxYCp65F4jsVYYJjkzbjmkCg5WAwN5voudngA23kMK6PpTNygapCzXt" }

View File

@ -4,7 +4,10 @@ import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.qortal.account.PrivateKeyAccount; import org.qortal.account.PrivateKeyAccount;
import org.qortal.block.Block;
import org.qortal.block.BlockChain;
import org.qortal.data.transaction.*; import org.qortal.data.transaction.*;
import org.qortal.group.Group;
import org.qortal.repository.DataException; import org.qortal.repository.DataException;
import org.qortal.repository.Repository; import org.qortal.repository.Repository;
import org.qortal.repository.RepositoryManager; import org.qortal.repository.RepositoryManager;
@ -16,6 +19,8 @@ import org.qortal.test.common.transaction.TestTransaction;
import org.qortal.transaction.Transaction; import org.qortal.transaction.Transaction;
import org.qortal.transaction.Transaction.ValidationResult; import org.qortal.transaction.Transaction.ValidationResult;
import java.util.List;
import static org.junit.Assert.*; import static org.junit.Assert.*;
/** /**
@ -40,8 +45,14 @@ import static org.junit.Assert.*;
*/ */
public class DevGroupAdminTests extends Common { public class DevGroupAdminTests extends Common {
public static final int NULL_GROUP_MEMBERSHIP_HEIGHT = BlockChain.getInstance().getNullGroupMembershipHeight();
private static final int DEV_GROUP_ID = 1; private static final int DEV_GROUP_ID = 1;
public static final String ALICE = "alice";
public static final String BOB = "bob";
public static final String CHLOE = "chloe";
public static final String DILBERT = "dilbert";
@Before @Before
public void beforeTest() throws DataException { public void beforeTest() throws DataException {
Common.useDefaultSettings(); Common.useDefaultSettings();
@ -55,8 +66,8 @@ public class DevGroupAdminTests extends Common {
@Test @Test
public void testGroupKickMember() throws DataException { public void testGroupKickMember() throws DataException {
try (final Repository repository = RepositoryManager.getRepository()) { try (final Repository repository = RepositoryManager.getRepository()) {
PrivateKeyAccount alice = Common.getTestAccount(repository, "alice"); PrivateKeyAccount alice = Common.getTestAccount(repository, ALICE);
PrivateKeyAccount bob = Common.getTestAccount(repository, "bob"); PrivateKeyAccount bob = Common.getTestAccount(repository, BOB);
// Dev group // Dev group
int groupId = DEV_GROUP_ID; int groupId = DEV_GROUP_ID;
@ -80,16 +91,10 @@ public class DevGroupAdminTests extends Common {
// Attempt to kick Bob // Attempt to kick Bob
result = groupKick(repository, alice, groupId, bob.getAddress()); result = groupKick(repository, alice, groupId, bob.getAddress());
// Should be OK // Should not be OK, cannot kick member out of null owned group
assertEquals(ValidationResult.OK, result); assertNotSame(ValidationResult.OK, result);
// Confirm Bob no longer a member // Confirm Bob remains a member
assertFalse(isMember(repository, bob.getAddress(), groupId));
// Orphan last block
BlockUtils.orphanLastBlock(repository);
// Confirm Bob now a member
assertTrue(isMember(repository, bob.getAddress(), groupId)); assertTrue(isMember(repository, bob.getAddress(), groupId));
} }
} }
@ -97,8 +102,8 @@ public class DevGroupAdminTests extends Common {
@Test @Test
public void testGroupKickAdmin() throws DataException { public void testGroupKickAdmin() throws DataException {
try (final Repository repository = RepositoryManager.getRepository()) { try (final Repository repository = RepositoryManager.getRepository()) {
PrivateKeyAccount alice = Common.getTestAccount(repository, "alice"); PrivateKeyAccount alice = Common.getTestAccount(repository, ALICE);
PrivateKeyAccount bob = Common.getTestAccount(repository, "bob"); PrivateKeyAccount bob = Common.getTestAccount(repository, BOB);
// Dev group // Dev group
int groupId = DEV_GROUP_ID; int groupId = DEV_GROUP_ID;
@ -123,7 +128,7 @@ public class DevGroupAdminTests extends Common {
assertEquals("incorrect transaction approval status", Transaction.ApprovalStatus.PENDING, approvalStatus); assertEquals("incorrect transaction approval status", Transaction.ApprovalStatus.PENDING, approvalStatus);
// Have Alice approve Bob's approval-needed transaction // Have Alice approve Bob's approval-needed transaction
GroupUtils.approveTransaction(repository, "alice", addGroupAdminTransactionData.getSignature(), true); GroupUtils.approveTransaction(repository, ALICE, addGroupAdminTransactionData.getSignature(), true);
// Mint a block so that the transaction becomes approved // Mint a block so that the transaction becomes approved
BlockUtils.mintBlock(repository); BlockUtils.mintBlock(repository);
@ -167,8 +172,8 @@ public class DevGroupAdminTests extends Common {
@Test @Test
public void testGroupBanMember() throws DataException { public void testGroupBanMember() throws DataException {
try (final Repository repository = RepositoryManager.getRepository()) { try (final Repository repository = RepositoryManager.getRepository()) {
PrivateKeyAccount alice = Common.getTestAccount(repository, "alice"); PrivateKeyAccount alice = Common.getTestAccount(repository, ALICE);
PrivateKeyAccount bob = Common.getTestAccount(repository, "bob"); PrivateKeyAccount bob = Common.getTestAccount(repository, BOB);
// Dev group // Dev group
int groupId = DEV_GROUP_ID; int groupId = DEV_GROUP_ID;
@ -183,18 +188,13 @@ public class DevGroupAdminTests extends Common {
// Attempt to ban Bob // Attempt to ban Bob
result = groupBan(repository, alice, groupId, bob.getAddress()); result = groupBan(repository, alice, groupId, bob.getAddress());
// Should be OK // Should not be OK, cannot ban someone from a null owned group
assertEquals(ValidationResult.OK, result);
// Bob attempts to rejoin
result = joinGroup(repository, bob, groupId);
// Should NOT be OK
assertNotSame(ValidationResult.OK, result); assertNotSame(ValidationResult.OK, result);
// Orphan last block (Bob ban) // Bob attempts to join
BlockUtils.orphanLastBlock(repository); result = joinGroup(repository, bob, groupId);
// Delete unconfirmed group-ban transaction // Should be OK, but won't actually get him in the group
TransactionUtils.deleteUnconfirmedTransactions(repository); assertEquals(ValidationResult.OK, result);
// Confirm Bob is not a member // Confirm Bob is not a member
assertFalse(isMember(repository, bob.getAddress(), groupId)); assertFalse(isMember(repository, bob.getAddress(), groupId));
@ -204,65 +204,38 @@ public class DevGroupAdminTests extends Common {
// Bob to join // Bob to join
result = joinGroup(repository, bob, groupId); result = joinGroup(repository, bob, groupId);
// Should be OK // Should not be OK, bob should already be a member, he joined before the invite and
assertEquals(ValidationResult.OK, result); // the invite served as an approval
assertEquals(ValidationResult.ALREADY_GROUP_MEMBER, result);
// Confirm Bob now a member // Confirm Bob now a member, now that he got an invite
assertTrue(isMember(repository, bob.getAddress(), groupId)); assertTrue(isMember(repository, bob.getAddress(), groupId));
// Attempt to ban Bob // Attempt to ban Bob
result = groupBan(repository, alice, groupId, bob.getAddress()); result = groupBan(repository, alice, groupId, bob.getAddress());
// Should be OK // Should not be OK, because you can ban a member of a null owned group
assertEquals(ValidationResult.OK, result); assertNotSame(ValidationResult.OK, result);
// Confirm Bob no longer a member // Confirm Bob is still a member
assertFalse(isMember(repository, bob.getAddress(), groupId)); assertTrue(isMember(repository, bob.getAddress(), groupId));
// Bob attempts to rejoin // Bob attempts to rejoin
result = joinGroup(repository, bob, groupId); result = joinGroup(repository, bob, groupId);
// Should NOT be OK // Should NOT be OK, because he is already a member
assertNotSame(ValidationResult.OK, result); assertNotSame(ValidationResult.OK, result);
// Cancel Bob's ban // Cancel Bob's ban
result = cancelGroupBan(repository, alice, groupId, bob.getAddress()); result = cancelGroupBan(repository, alice, groupId, bob.getAddress());
// Should be OK // Should not be OK, because there was no ban to begin with
assertEquals(ValidationResult.OK, result);
// Bob attempts to rejoin
result = joinGroup(repository, bob, groupId);
// Should be OK
assertEquals(ValidationResult.OK, result);
// Orphan last block (Bob join)
BlockUtils.orphanLastBlock(repository);
// Delete unconfirmed join-group transaction
TransactionUtils.deleteUnconfirmedTransactions(repository);
// Orphan last block (Cancel Bob ban)
BlockUtils.orphanLastBlock(repository);
// Delete unconfirmed cancel-ban transaction
TransactionUtils.deleteUnconfirmedTransactions(repository);
// Bob attempts to rejoin
result = joinGroup(repository, bob, groupId);
// Should NOT be OK
assertNotSame(ValidationResult.OK, result); assertNotSame(ValidationResult.OK, result);
// Orphan last block (Bob ban)
BlockUtils.orphanLastBlock(repository);
// Delete unconfirmed group-ban transaction
TransactionUtils.deleteUnconfirmedTransactions(repository);
// Confirm Bob now a member
assertTrue(isMember(repository, bob.getAddress(), groupId));
} }
} }
@Test @Test
public void testGroupBanAdmin() throws DataException { public void testGroupBanAdmin() throws DataException {
try (final Repository repository = RepositoryManager.getRepository()) { try (final Repository repository = RepositoryManager.getRepository()) {
PrivateKeyAccount alice = Common.getTestAccount(repository, "alice"); PrivateKeyAccount alice = Common.getTestAccount(repository, ALICE);
PrivateKeyAccount bob = Common.getTestAccount(repository, "bob"); PrivateKeyAccount bob = Common.getTestAccount(repository, BOB);
// Dev group // Dev group
int groupId = DEV_GROUP_ID; int groupId = DEV_GROUP_ID;
@ -286,7 +259,7 @@ public class DevGroupAdminTests extends Common {
assertEquals("incorrect transaction approval status", Transaction.ApprovalStatus.PENDING, approvalStatus); assertEquals("incorrect transaction approval status", Transaction.ApprovalStatus.PENDING, approvalStatus);
// Have Alice approve Bob's approval-needed transaction // Have Alice approve Bob's approval-needed transaction
GroupUtils.approveTransaction(repository, "alice", addGroupAdminTransactionData.getSignature(), true); GroupUtils.approveTransaction(repository, ALICE, addGroupAdminTransactionData.getSignature(), true);
// Mint a block so that the transaction becomes approved // Mint a block so that the transaction becomes approved
BlockUtils.mintBlock(repository); BlockUtils.mintBlock(repository);
@ -321,6 +294,302 @@ public class DevGroupAdminTests extends Common {
} }
} }
@Test
public void testAddAdmin2of3() throws DataException {
try (final Repository repository = RepositoryManager.getRepository()) {
// establish accounts
PrivateKeyAccount alice = Common.getTestAccount(repository, ALICE);
PrivateKeyAccount bob = Common.getTestAccount(repository, BOB);
PrivateKeyAccount chloe = Common.getTestAccount(repository, CHLOE);
PrivateKeyAccount dilbert = Common.getTestAccount(repository, DILBERT);
// assert admin statuses
assertEquals(2, repository.getGroupRepository().countGroupAdmins(DEV_GROUP_ID).intValue());
assertTrue(isAdmin(repository, Group.NULL_OWNER_ADDRESS, DEV_GROUP_ID));
assertTrue(isAdmin(repository, alice.getAddress(), DEV_GROUP_ID));
assertFalse(isAdmin(repository, bob.getAddress(), DEV_GROUP_ID));
assertFalse(isAdmin(repository, chloe.getAddress(), DEV_GROUP_ID));
assertFalse(isAdmin(repository, dilbert.getAddress(), DEV_GROUP_ID));
// confirm Bob is not a member
assertFalse(isMember(repository, bob.getAddress(), DEV_GROUP_ID));
// alice invites bob
ValidationResult result = groupInvite(repository, alice, DEV_GROUP_ID, bob.getAddress(), 3600);
assertSame(ValidationResult.OK, result);
// bob joins
joinGroup(repository, bob, DEV_GROUP_ID);
// confirm Bob is a member now, but still not an admin
assertTrue(isMember(repository, bob.getAddress(), DEV_GROUP_ID));
assertFalse(isAdmin(repository, bob.getAddress(), DEV_GROUP_ID));
// bob creates transaction to add himself as an admin
TransactionData addGroupAdminTransactionData1 = addGroupAdmin(repository, bob, DEV_GROUP_ID, bob.getAddress());
// bob creates add admin transaction for himself, alice signs which is 50% approval while 40% is needed
signForGroupApproval(repository, addGroupAdminTransactionData1, List.of(alice));
// assert 3 admins in group and bob is an admin now
assertEquals(3, repository.getGroupRepository().countGroupAdmins(DEV_GROUP_ID).intValue() );
assertTrue(isAdmin(repository, bob.getAddress(), DEV_GROUP_ID));
// bob invites chloe
result = groupInvite(repository, bob, DEV_GROUP_ID, chloe.getAddress(), 3600);
assertSame(ValidationResult.OK, result);
// chloe joins
joinGroup(repository, chloe, DEV_GROUP_ID);
// confirm Chloe is a member now, but still not an admin
assertTrue(isMember(repository, chloe.getAddress(), DEV_GROUP_ID));
assertFalse(isAdmin(repository, chloe.getAddress(), DEV_GROUP_ID));
// chloe creates transaction to add herself as an admin
TransactionData addChloeAsGroupAdmin = addGroupAdmin(repository, chloe, DEV_GROUP_ID, chloe.getAddress());
// no one has signed, so it should be pending
Transaction.ApprovalStatus addChloeAsGroupAdminStatus1 = GroupUtils.getApprovalStatus(repository, addChloeAsGroupAdmin.getSignature());
assertEquals( Transaction.ApprovalStatus.PENDING, addChloeAsGroupAdminStatus1);
// signer 1
Transaction.ApprovalStatus addChloeAsGroupAdminStatus2 = signForGroupApproval(repository, addChloeAsGroupAdmin, List.of(alice));
// 1 out of 3 has signed, so it should be pending, because it is less than 40%
assertEquals( Transaction.ApprovalStatus.PENDING, addChloeAsGroupAdminStatus2);
// signer 2
Transaction.ApprovalStatus addChloeAsGroupAdminStatus3 = signForGroupApproval(repository, addChloeAsGroupAdmin, List.of(bob));
// 2 out of 3 has signed, so it should be approved, because it is more than 40%
assertEquals( Transaction.ApprovalStatus.APPROVED, addChloeAsGroupAdminStatus3);
}
}
@Test
public void testNullOwnershipMembership() throws DataException{
try (final Repository repository = RepositoryManager.getRepository()) {
Block block = BlockUtils.mintBlocks(repository, NULL_GROUP_MEMBERSHIP_HEIGHT);
assertEquals(NULL_GROUP_MEMBERSHIP_HEIGHT + 1, block.getBlockData().getHeight().intValue());
// establish accounts
PrivateKeyAccount alice = Common.getTestAccount(repository, ALICE);
PrivateKeyAccount bob = Common.getTestAccount(repository, BOB);
PrivateKeyAccount chloe = Common.getTestAccount(repository, CHLOE);
PrivateKeyAccount dilbert = Common.getTestAccount(repository, DILBERT);
// assert admin statuses
assertEquals(2, repository.getGroupRepository().countGroupAdmins(DEV_GROUP_ID).intValue());
assertTrue(isAdmin(repository, Group.NULL_OWNER_ADDRESS, DEV_GROUP_ID));
assertTrue(isAdmin(repository, alice.getAddress(), DEV_GROUP_ID));
assertFalse(isAdmin(repository, bob.getAddress(), DEV_GROUP_ID));
assertFalse(isAdmin(repository, chloe.getAddress(), DEV_GROUP_ID));
assertFalse(isAdmin(repository, dilbert.getAddress(), DEV_GROUP_ID));
// confirm Bob is not a member
assertFalse(isMember(repository, bob.getAddress(), DEV_GROUP_ID));
// alice invites bob, alice signs which is 50% approval while 40% is needed
TransactionData createInviteTransactionData = createGroupInviteForGroupApproval(repository, alice, DEV_GROUP_ID, bob.getAddress(), 3600);
Transaction.ApprovalStatus bobsInviteStatus = signForGroupApproval(repository, createInviteTransactionData, List.of(alice));
// assert approval
assertEquals(Transaction.ApprovalStatus.APPROVED, bobsInviteStatus);
// bob joins
joinGroup(repository, bob, DEV_GROUP_ID);
// confirm Bob is a member now, but still not an admin
assertTrue(isMember(repository, bob.getAddress(), DEV_GROUP_ID));
assertFalse(isAdmin(repository, bob.getAddress(), DEV_GROUP_ID));
// bob creates transaction to add himself as an admin
TransactionData addGroupAdminTransactionData1 = addGroupAdmin(repository, bob, DEV_GROUP_ID, bob.getAddress());
// bob creates add admin transaction for himself, alice signs which is 50% approval while 40% is needed
signForGroupApproval(repository, addGroupAdminTransactionData1, List.of(alice));
// assert 3 admins in group and bob is an admin now
assertEquals(3, repository.getGroupRepository().countGroupAdmins(DEV_GROUP_ID).intValue());
assertTrue(isAdmin(repository, bob.getAddress(), DEV_GROUP_ID));
// bob invites chloe, bob signs which is 33% approval while 40% is needed
TransactionData chloeInvite = createGroupInviteForGroupApproval(repository, bob, DEV_GROUP_ID, chloe.getAddress(), 3600);
Transaction.ApprovalStatus chloeInviteStatus = signForGroupApproval(repository, chloeInvite, List.of(bob));
// assert pending
assertEquals(Transaction.ApprovalStatus.PENDING, chloeInviteStatus);
// alice signs which is 66% approval while 40% is needed
chloeInviteStatus = signForGroupApproval(repository, chloeInvite, List.of(alice));
// assert approval
assertEquals(Transaction.ApprovalStatus.APPROVED, chloeInviteStatus);
// chloe joins
joinGroup(repository, chloe, DEV_GROUP_ID);
// assert chloe is in the group
assertTrue(isMember(repository, bob.getAddress(), DEV_GROUP_ID));
// alice kicks chloe, alice signs which is 33% approval while 40% is needed
TransactionData chloeKick = createGroupKickForGroupApproval(repository, alice, DEV_GROUP_ID, chloe.getAddress(),"testing chloe kick");
Transaction.ApprovalStatus chloeKickStatus = signForGroupApproval(repository, chloeKick, List.of(alice));
// assert pending
assertEquals(Transaction.ApprovalStatus.PENDING, chloeKickStatus);
// assert chloe is still in the group
assertTrue(isMember(repository, bob.getAddress(), DEV_GROUP_ID));
// bob signs which is 66% approval while 40% is needed
chloeKickStatus = signForGroupApproval(repository, chloeKick, List.of(bob));
// assert approval
assertEquals(Transaction.ApprovalStatus.APPROVED, chloeKickStatus);
// assert chloe is not in the group
assertFalse(isMember(repository, chloe.getAddress(), DEV_GROUP_ID));
// bob invites chloe, alice and bob signs which is 66% approval while 40% is needed
TransactionData chloeInviteAgain = createGroupInviteForGroupApproval(repository, bob, DEV_GROUP_ID, chloe.getAddress(), 3600);
Transaction.ApprovalStatus chloeInviteAgainStatus = signForGroupApproval(repository, chloeInviteAgain, List.of(alice, bob));
// assert approved
assertEquals(Transaction.ApprovalStatus.APPROVED, chloeInviteAgainStatus);
// chloe joins again
joinGroup(repository, chloe, DEV_GROUP_ID);
// assert chloe is in the group
assertTrue(isMember(repository, bob.getAddress(), DEV_GROUP_ID));
// alice bans chloe, alice signs which is 33% approval while 40% is needed
TransactionData chloeBan = createGroupBanForGroupApproval(repository, alice, DEV_GROUP_ID, chloe.getAddress(), "testing group ban", 3600);
Transaction.ApprovalStatus chloeBanStatus1 = signForGroupApproval(repository, chloeBan, List.of(alice));
// assert pending
assertEquals(Transaction.ApprovalStatus.PENDING, chloeBanStatus1);
// bob signs which 66% approval while 40% is needed
Transaction.ApprovalStatus chloeBanStatus2 = signForGroupApproval(repository, chloeBan, List.of(bob));
// assert approved
assertEquals(Transaction.ApprovalStatus.APPROVED, chloeBanStatus2);
// assert chloe is not in the group
assertFalse(isMember(repository, chloe.getAddress(), DEV_GROUP_ID));
// bob invites chloe, alice and bob signs which is 66% approval while 40% is needed
ValidationResult chloeInviteValidation = signAndImportGroupInvite(repository, bob, DEV_GROUP_ID, chloe.getAddress(), 3600);
// assert banned status on invite attempt
assertEquals(ValidationResult.BANNED_FROM_GROUP, chloeInviteValidation);
// bob cancel ban on chloe, bob signs which is 33% approval while 40% is needed
TransactionData chloeCancelBan = createCancelGroupBanForGroupApproval( repository, bob, DEV_GROUP_ID, chloe.getAddress());
Transaction.ApprovalStatus chloeCancelBanStatus1 = signForGroupApproval(repository, chloeCancelBan, List.of(bob));
// assert pending
assertEquals(Transaction.ApprovalStatus.PENDING, chloeCancelBanStatus1);
// alice signs which is 66% approval while 40% is needed
Transaction.ApprovalStatus chloeCancelBanStatus2 = signForGroupApproval(repository, chloeCancelBan, List.of(alice));
// assert approved
assertEquals(Transaction.ApprovalStatus.APPROVED, chloeCancelBanStatus2);
// bob invites chloe, alice and bob signs which is 66% approval while 40% is needed
TransactionData chloeInvite4 = createGroupInviteForGroupApproval(repository, bob, DEV_GROUP_ID, chloe.getAddress(), 3600);
Transaction.ApprovalStatus chloeInvite4Status = signForGroupApproval(repository, chloeInvite4, List.of(alice, bob));
// assert approved
assertEquals(Transaction.ApprovalStatus.APPROVED, chloeInvite4Status);
// chloe joins again
joinGroup(repository, chloe, DEV_GROUP_ID);
// assert chloe is in the group
assertTrue(isMember(repository, chloe.getAddress(), DEV_GROUP_ID));
// bob invites dilbert, alice and bob signs which is 66% approval while 40% is needed
TransactionData dilbertInvite1 = createGroupInviteForGroupApproval(repository, bob, DEV_GROUP_ID, dilbert.getAddress(), 3600);
Transaction.ApprovalStatus dibertInviteStatus1 = signForGroupApproval(repository, dilbertInvite1, List.of(alice, bob));
// assert approved
assertEquals(Transaction.ApprovalStatus.APPROVED, dibertInviteStatus1);
// alice cancels dilbert's invite, alice signs which is 33% approval while 40% is needed
TransactionData cancelDilbertInvite = createCancelInviteForGroupApproval(repository, alice, DEV_GROUP_ID, dilbert.getAddress());
Transaction.ApprovalStatus cancelDilbertInviteStatus1 = signForGroupApproval(repository, cancelDilbertInvite, List.of(alice));
// assert pending
assertEquals(Transaction.ApprovalStatus.PENDING, cancelDilbertInviteStatus1);
// dilbert joins before the group approves cancellation
joinGroup(repository, dilbert, DEV_GROUP_ID);
// assert dilbert is in the group
assertTrue(isMember(repository, dilbert.getAddress(), DEV_GROUP_ID));
// alice kicks out dilbert, alice and bob sign which is 66% approval while 40% is needed
TransactionData kickDilbert = createGroupKickForGroupApproval(repository, alice, DEV_GROUP_ID, dilbert.getAddress(), "he is sneaky");
Transaction.ApprovalStatus kickDilbertStatus = signForGroupApproval(repository, kickDilbert, List.of(alice, bob));
// assert approved
assertEquals(Transaction.ApprovalStatus.APPROVED, kickDilbertStatus);
// assert dilbert is out of the group
assertFalse(isMember(repository, dilbert.getAddress(), DEV_GROUP_ID));
// bob invites dilbert again, alice and bob signs which is 66% approval while 40% is needed
TransactionData dilbertInvite2 = createGroupInviteForGroupApproval(repository, bob, DEV_GROUP_ID, dilbert.getAddress(), 3600);
Transaction.ApprovalStatus dibertInviteStatus2 = signForGroupApproval(repository, dilbertInvite2, List.of(alice, bob));
// assert approved
assertEquals(Transaction.ApprovalStatus.APPROVED, dibertInviteStatus2);
// alice cancels dilbert's invite, alice and bob signs which is 66% approval while 40% is needed
TransactionData cancelDilbertInvite2 = createCancelInviteForGroupApproval(repository, alice, DEV_GROUP_ID, dilbert.getAddress());
Transaction.ApprovalStatus cancelDilbertInviteStatus2 = signForGroupApproval(repository, cancelDilbertInvite2, List.of(alice, bob));
// assert approved
assertEquals(Transaction.ApprovalStatus.APPROVED, cancelDilbertInviteStatus2);
// dilbert tries to join after the group approves cancellation
joinGroup(repository, dilbert, DEV_GROUP_ID);
// assert dilbert is not in the group
assertFalse(isMember(repository, dilbert.getAddress(), DEV_GROUP_ID));
}
}
private Transaction.ApprovalStatus signForGroupApproval(Repository repository, TransactionData data, List<PrivateKeyAccount> signers) throws DataException {
for (PrivateKeyAccount signer : signers) {
signTransactionDataForGroupApproval(repository, signer, data);
}
BlockUtils.mintBlocks(repository, 2);
// return approval status
return GroupUtils.getApprovalStatus(repository, data.getSignature());
}
private static void signTransactionDataForGroupApproval(Repository repository, PrivateKeyAccount signer, TransactionData transactionData) throws DataException {
byte[] reference = signer.getLastReference();
long timestamp = repository.getTransactionRepository().fromSignature(reference).getTimestamp() + 1;
BaseTransactionData baseTransactionData
= new BaseTransactionData(timestamp, Group.NO_GROUP, reference, signer.getPublicKey(), GroupUtils.fee, null);
TransactionData groupApprovalTransactionData
= new GroupApprovalTransactionData(baseTransactionData, transactionData.getSignature(), true);
TransactionUtils.signAndImportValid(repository, groupApprovalTransactionData, signer);
}
private ValidationResult joinGroup(Repository repository, PrivateKeyAccount joiner, int groupId) throws DataException { private ValidationResult joinGroup(Repository repository, PrivateKeyAccount joiner, int groupId) throws DataException {
JoinGroupTransactionData transactionData = new JoinGroupTransactionData(TestTransaction.generateBase(joiner), groupId); JoinGroupTransactionData transactionData = new JoinGroupTransactionData(TestTransaction.generateBase(joiner), groupId);
@ -332,9 +601,31 @@ public class DevGroupAdminTests extends Common {
return result; return result;
} }
private void groupInvite(Repository repository, PrivateKeyAccount admin, int groupId, String invitee, int timeToLive) throws DataException { private ValidationResult groupInvite(Repository repository, PrivateKeyAccount admin, int groupId, String invitee, int timeToLive) throws DataException {
GroupInviteTransactionData transactionData = new GroupInviteTransactionData(TestTransaction.generateBase(admin), groupId, invitee, timeToLive); GroupInviteTransactionData transactionData = new GroupInviteTransactionData(TestTransaction.generateBase(admin), groupId, invitee, timeToLive);
ValidationResult result = TransactionUtils.signAndImport(repository, transactionData, admin);
if (result == ValidationResult.OK)
BlockUtils.mintBlock(repository);
return result;
}
private TransactionData createGroupInviteForGroupApproval(Repository repository, PrivateKeyAccount admin, int groupId, String invitee, int timeToLive) throws DataException {
GroupInviteTransactionData transactionData = new GroupInviteTransactionData(TestTransaction.generateBase(admin, groupId), groupId, invitee, timeToLive);
TransactionUtils.signAndMint(repository, transactionData, admin); TransactionUtils.signAndMint(repository, transactionData, admin);
return transactionData;
}
private TransactionData createCancelInviteForGroupApproval(Repository repository, PrivateKeyAccount admin, int groupId, String inviteeToCancel) throws DataException {
CancelGroupInviteTransactionData transactionData = new CancelGroupInviteTransactionData(TestTransaction.generateBase(admin, groupId), groupId, inviteeToCancel);
TransactionUtils.signAndMint(repository, transactionData, admin);
return transactionData;
}
private ValidationResult signAndImportGroupInvite(Repository repository, PrivateKeyAccount admin, int groupId, String invitee, int timeToLive) throws DataException {
GroupInviteTransactionData transactionData = new GroupInviteTransactionData(TestTransaction.generateBase(admin, groupId), groupId, invitee, timeToLive);
return TransactionUtils.signAndImport(repository, transactionData, admin);
} }
private ValidationResult groupKick(Repository repository, PrivateKeyAccount admin, int groupId, String member) throws DataException { private ValidationResult groupKick(Repository repository, PrivateKeyAccount admin, int groupId, String member) throws DataException {
@ -347,6 +638,13 @@ public class DevGroupAdminTests extends Common {
return result; return result;
} }
private TransactionData createGroupKickForGroupApproval(Repository repository, PrivateKeyAccount admin, int groupId, String kicked, String reason) throws DataException {
GroupKickTransactionData transactionData = new GroupKickTransactionData(TestTransaction.generateBase(admin, groupId), groupId, kicked, reason);
TransactionUtils.signAndMint(repository, transactionData, admin);
return transactionData;
}
private ValidationResult groupBan(Repository repository, PrivateKeyAccount admin, int groupId, String member) throws DataException { private ValidationResult groupBan(Repository repository, PrivateKeyAccount admin, int groupId, String member) throws DataException {
GroupBanTransactionData transactionData = new GroupBanTransactionData(TestTransaction.generateBase(admin), groupId, member, "testing", 0); GroupBanTransactionData transactionData = new GroupBanTransactionData(TestTransaction.generateBase(admin), groupId, member, "testing", 0);
ValidationResult result = TransactionUtils.signAndImport(repository, transactionData, admin); ValidationResult result = TransactionUtils.signAndImport(repository, transactionData, admin);
@ -357,6 +655,13 @@ public class DevGroupAdminTests extends Common {
return result; return result;
} }
private TransactionData createGroupBanForGroupApproval(Repository repository, PrivateKeyAccount admin, int groupId, String banned, String reason, int timeToLive) throws DataException {
GroupBanTransactionData transactionData = new GroupBanTransactionData(TestTransaction.generateBase(admin, groupId), groupId, banned, reason, timeToLive);
TransactionUtils.signAndMint(repository, transactionData, admin);
return transactionData;
}
private ValidationResult cancelGroupBan(Repository repository, PrivateKeyAccount admin, int groupId, String member) throws DataException { private ValidationResult cancelGroupBan(Repository repository, PrivateKeyAccount admin, int groupId, String member) throws DataException {
CancelGroupBanTransactionData transactionData = new CancelGroupBanTransactionData(TestTransaction.generateBase(admin), groupId, member); CancelGroupBanTransactionData transactionData = new CancelGroupBanTransactionData(TestTransaction.generateBase(admin), groupId, member);
ValidationResult result = TransactionUtils.signAndImport(repository, transactionData, admin); ValidationResult result = TransactionUtils.signAndImport(repository, transactionData, admin);
@ -367,6 +672,14 @@ public class DevGroupAdminTests extends Common {
return result; return result;
} }
private TransactionData createCancelGroupBanForGroupApproval(Repository repository, PrivateKeyAccount admin, int groupId, String unbanned ) throws DataException {
CancelGroupBanTransactionData transactionData = new CancelGroupBanTransactionData( TestTransaction.generateBase(admin, groupId), groupId, unbanned);
TransactionUtils.signAndMint(repository, transactionData, admin);
return transactionData;
}
private TransactionData addGroupAdmin(Repository repository, PrivateKeyAccount owner, int groupId, String member) throws DataException { private TransactionData addGroupAdmin(Repository repository, PrivateKeyAccount owner, int groupId, String member) throws DataException {
AddGroupAdminTransactionData transactionData = new AddGroupAdminTransactionData(TestTransaction.generateBase(owner), groupId, member); AddGroupAdminTransactionData transactionData = new AddGroupAdminTransactionData(TestTransaction.generateBase(owner), groupId, member);
transactionData.setTxGroupId(groupId); transactionData.setTxGroupId(groupId);

View File

@ -106,7 +106,8 @@
"removeOnlyMintWithNameHeight": 9999999999999, "removeOnlyMintWithNameHeight": 9999999999999,
"fixBatchRewardHeight": 9999999999999, "fixBatchRewardHeight": 9999999999999,
"adminsReplaceFoundersHeight": 9999999999999, "adminsReplaceFoundersHeight": 9999999999999,
"onlineValidationFailSafeHeight": 9999999999999 "onlineValidationFailSafeHeight": 9999999999999,
"nullGroupMembershipHeight": 20
}, },
"genesisInfo": { "genesisInfo": {
"version": 4, "version": 4,