diff --git a/src/main/java/org/qora/api/resource/GroupsResource.java b/src/main/java/org/qora/api/resource/GroupsResource.java index e7be882e..f33fe661 100644 --- a/src/main/java/org/qora/api/resource/GroupsResource.java +++ b/src/main/java/org/qora/api/resource/GroupsResource.java @@ -28,6 +28,7 @@ import org.qora.api.ApiExceptionFactory; import org.qora.api.model.GroupWithMemberInfo; import org.qora.crypto.Crypto; import org.qora.data.group.GroupAdminData; +import org.qora.data.group.GroupBanData; import org.qora.data.group.GroupData; import org.qora.data.group.GroupInviteData; import org.qora.data.group.GroupJoinRequestData; @@ -35,8 +36,10 @@ import org.qora.data.group.GroupMemberData; import org.qora.data.transaction.AddGroupAdminTransactionData; import org.qora.data.transaction.CancelGroupInviteTransactionData; import org.qora.data.transaction.CreateGroupTransactionData; +import org.qora.data.transaction.GroupBanTransactionData; import org.qora.data.transaction.GroupInviteTransactionData; import org.qora.data.transaction.GroupKickTransactionData; +import org.qora.data.transaction.GroupUnbanTransactionData; import org.qora.data.transaction.JoinGroupTransactionData; import org.qora.data.transaction.LeaveGroupTransactionData; import org.qora.data.transaction.RemoveGroupAdminTransactionData; @@ -50,8 +53,10 @@ import org.qora.transform.TransformationException; import org.qora.transform.transaction.AddGroupAdminTransactionTransformer; import org.qora.transform.transaction.CancelGroupInviteTransactionTransformer; import org.qora.transform.transaction.CreateGroupTransactionTransformer; +import org.qora.transform.transaction.GroupBanTransactionTransformer; import org.qora.transform.transaction.GroupInviteTransactionTransformer; import org.qora.transform.transaction.GroupKickTransactionTransformer; +import org.qora.transform.transaction.GroupUnbanTransactionTransformer; import org.qora.transform.transaction.JoinGroupTransactionTransformer; import org.qora.transform.transaction.LeaveGroupTransactionTransformer; import org.qora.transform.transaction.RemoveGroupAdminTransactionTransformer; @@ -344,6 +349,92 @@ public class GroupsResource { } } + @POST + @Path("/ban") + @Operation( + summary = "Build raw, unsigned, GROUP_BAN transaction", + requestBody = @RequestBody( + required = true, + content = @Content( + mediaType = MediaType.APPLICATION_JSON, + schema = @Schema( + implementation = GroupBanTransactionData.class + ) + ) + ), + responses = { + @ApiResponse( + description = "raw, unsigned, GROUP_BAN transaction encoded in Base58", + content = @Content( + mediaType = MediaType.TEXT_PLAIN, + schema = @Schema( + type = "string" + ) + ) + ) + } + ) + @ApiErrors({ApiError.TRANSACTION_INVALID, ApiError.TRANSFORMATION_ERROR, ApiError.REPOSITORY_ISSUE}) + public String groupBan(GroupBanTransactionData transactionData) { + try (final Repository repository = RepositoryManager.getRepository()) { + Transaction transaction = Transaction.fromData(repository, transactionData); + + ValidationResult result = transaction.isValidUnconfirmed(); + if (result != ValidationResult.OK) + throw TransactionsResource.createTransactionInvalidException(request, result); + + byte[] bytes = GroupBanTransactionTransformer.toBytes(transactionData); + return Base58.encode(bytes); + } catch (TransformationException e) { + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.TRANSFORMATION_ERROR, e); + } catch (DataException e) { + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e); + } + } + + @POST + @Path("/unban") + @Operation( + summary = "Build raw, unsigned, GROUP_UNBAN transaction", + requestBody = @RequestBody( + required = true, + content = @Content( + mediaType = MediaType.APPLICATION_JSON, + schema = @Schema( + implementation = GroupUnbanTransactionData.class + ) + ) + ), + responses = { + @ApiResponse( + description = "raw, unsigned, GROUP_UNBAN transaction encoded in Base58", + content = @Content( + mediaType = MediaType.TEXT_PLAIN, + schema = @Schema( + type = "string" + ) + ) + ) + } + ) + @ApiErrors({ApiError.TRANSACTION_INVALID, ApiError.TRANSFORMATION_ERROR, ApiError.REPOSITORY_ISSUE}) + public String groupBan(GroupUnbanTransactionData transactionData) { + try (final Repository repository = RepositoryManager.getRepository()) { + Transaction transaction = Transaction.fromData(repository, transactionData); + + ValidationResult result = transaction.isValidUnconfirmed(); + if (result != ValidationResult.OK) + throw TransactionsResource.createTransactionInvalidException(request, result); + + byte[] bytes = GroupUnbanTransactionTransformer.toBytes(transactionData); + return Base58.encode(bytes); + } catch (TransformationException e) { + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.TRANSFORMATION_ERROR, e); + } catch (DataException e) { + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e); + } + } + @POST @Path("/kick") @Operation( @@ -588,7 +679,7 @@ public class GroupsResource { summary = "Pending group join requests", responses = { @ApiResponse( - description = "group jon requests", + description = "group join requests", content = @Content( mediaType = MediaType.APPLICATION_JSON, schema = @Schema(implementation = GroupJoinRequestData.class) @@ -605,4 +696,27 @@ public class GroupsResource { } } + @GET + @Path("/bans/{groupname}") + @Operation( + summary = "Current group join bans", + responses = { + @ApiResponse( + description = "group bans", + content = @Content( + mediaType = MediaType.APPLICATION_JSON, + schema = @Schema(implementation = GroupJoinRequestData.class) + ) + ) + } + ) + @ApiErrors({ApiError.REPOSITORY_ISSUE}) + public List getBans(@PathParam("groupname") String groupName) { + try (final Repository repository = RepositoryManager.getRepository()) { + return repository.getGroupRepository().getGroupBans(groupName); + } catch (DataException e) { + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e); + } + } + } \ No newline at end of file diff --git a/src/main/java/org/qora/data/group/GroupBanData.java b/src/main/java/org/qora/data/group/GroupBanData.java new file mode 100644 index 00000000..207d8f10 --- /dev/null +++ b/src/main/java/org/qora/data/group/GroupBanData.java @@ -0,0 +1,72 @@ +package org.qora.data.group; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlTransient; + +// All properties to be converted to JSON via JAX-RS +@XmlAccessorType(XmlAccessType.FIELD) +public class GroupBanData { + + // Properties + private String groupName; + private String offender; + private String admin; + private long banned; + private String reason; + private Long expiry; + // No need to ever expose this via API + @XmlTransient + private byte[] reference; + + // Constructors + + // necessary for JAX-RS serialization + protected GroupBanData() { + } + + public GroupBanData(String groupName, String offender, String admin, long banned, String reason, Long expiry, byte[] reference) { + this.groupName = groupName; + this.offender = offender; + this.admin = admin; + this.banned = banned; + this.reason = reason; + this.expiry = expiry; + this.reference = reference; + } + + // Getters / setters + + public String getGroupName() { + return this.groupName; + } + + public String getOffender() { + return this.offender; + } + + public String getAdmin() { + return this.admin; + } + + public long getBanned() { + return this.banned; + } + + public String getReason() { + return this.reason; + } + + public Long getExpiry() { + return this.expiry; + } + + public byte[] getReference() { + return this.reference; + } + + public void setReference(byte[] reference) { + this.reference = reference; + } + +} diff --git a/src/main/java/org/qora/data/transaction/GroupBanTransactionData.java b/src/main/java/org/qora/data/transaction/GroupBanTransactionData.java new file mode 100644 index 00000000..f8013ad1 --- /dev/null +++ b/src/main/java/org/qora/data/transaction/GroupBanTransactionData.java @@ -0,0 +1,110 @@ +package org.qora.data.transaction; + +import java.math.BigDecimal; + +import javax.xml.bind.Unmarshaller; +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlTransient; + +import org.qora.transaction.Transaction.TransactionType; + +import io.swagger.v3.oas.annotations.media.Schema; + +// All properties to be converted to JSON via JAX-RS +@XmlAccessorType(XmlAccessType.FIELD) +@Schema(allOf = { TransactionData.class }) +public class GroupBanTransactionData extends TransactionData { + + // Properties + @Schema(description = "admin's public key", example = "2tiMr5LTpaWCgbRvkPK8TFd7k63DyHJMMFFsz9uBf1ZP") + private byte[] adminPublicKey; + @Schema(description = "group name", example = "my-group") + private String groupName; + @Schema(description = "offender to ban from group", example = "QixPbJUwsaHsVEofJdozU9zgVqkK6aYhrK") + private String offender; + @Schema(description = "reason for ban") + private String reason; + @Schema(description = "ban lifetime in seconds") + private int timeToLive; + // No need to ever expose this via API + @XmlTransient + private byte[] memberReference; + // No need to ever expose this via API + @XmlTransient + private byte[] adminReference; + + // Constructors + + // For JAX-RS + protected GroupBanTransactionData() { + super(TransactionType.GROUP_BAN); + } + + public void afterUnmarshal(Unmarshaller u, Object parent) { + this.creatorPublicKey = this.adminPublicKey; + } + + public GroupBanTransactionData(byte[] adminPublicKey, String groupName, String member, String reason, int timeToLive, byte[] memberReference, byte[] adminReference, BigDecimal fee, long timestamp, byte[] reference, byte[] signature) { + super(TransactionType.GROUP_BAN, fee, adminPublicKey, timestamp, reference, signature); + + this.adminPublicKey = adminPublicKey; + this.groupName = groupName; + this.offender = member; + this.reason = reason; + this.timeToLive = timeToLive; + this.memberReference = memberReference; + this.adminReference = adminReference; + } + + public GroupBanTransactionData(byte[] adminPublicKey, String groupName, String offender, String reason, int timeToLive, BigDecimal fee, long timestamp, byte[] reference, byte[] signature) { + this(adminPublicKey, groupName, offender, reason, timeToLive, null, null, fee, timestamp, reference, signature); + } + + public GroupBanTransactionData(byte[] adminPublicKey, String groupName, String offender, String reason, int timeToLive, byte[] memberReference, byte[] adminReference, BigDecimal fee, long timestamp, byte[] reference) { + this(adminPublicKey, groupName, offender, reason, timeToLive, memberReference, adminReference, fee, timestamp, reference, null); + } + + public GroupBanTransactionData(byte[] adminPublicKey, String groupName, String offender, String reason, int timeToLive, BigDecimal fee, long timestamp, byte[] reference) { + this(adminPublicKey, groupName, offender, reason, timeToLive, null, null, fee, timestamp, reference, null); + } + + // Getters / setters + + public byte[] getAdminPublicKey() { + return this.adminPublicKey; + } + + public String getGroupName() { + return this.groupName; + } + + public String getOffender() { + return this.offender; + } + + public String getReason() { + return this.reason; + } + + public int getTimeToLive() { + return this.timeToLive; + } + + public byte[] getMemberReference() { + return this.memberReference; + } + + public void setMemberReference(byte[] memberReference) { + this.memberReference = memberReference; + } + + public byte[] getAdminReference() { + return this.adminReference; + } + + public void setAdminReference(byte[] adminReference) { + this.adminReference = adminReference; + } + +} diff --git a/src/main/java/org/qora/data/transaction/GroupUnbanTransactionData.java b/src/main/java/org/qora/data/transaction/GroupUnbanTransactionData.java new file mode 100644 index 00000000..c1ed94f1 --- /dev/null +++ b/src/main/java/org/qora/data/transaction/GroupUnbanTransactionData.java @@ -0,0 +1,84 @@ +package org.qora.data.transaction; + +import java.math.BigDecimal; + +import javax.xml.bind.Unmarshaller; +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlTransient; + +import org.qora.transaction.Transaction.TransactionType; + +import io.swagger.v3.oas.annotations.media.Schema; + +// All properties to be converted to JSON via JAX-RS +@XmlAccessorType(XmlAccessType.FIELD) +@Schema(allOf = { TransactionData.class }) +public class GroupUnbanTransactionData extends TransactionData { + + // Properties + @Schema(description = "admin's public key", example = "2tiMr5LTpaWCgbRvkPK8TFd7k63DyHJMMFFsz9uBf1ZP") + private byte[] adminPublicKey; + @Schema(description = "group name", example = "my-group") + private String groupName; + @Schema(description = "member to unban from group", example = "QixPbJUwsaHsVEofJdozU9zgVqkK6aYhrK") + private String member; + // No need to ever expose this via API + @XmlTransient + private byte[] groupReference; + + // Constructors + + // For JAX-RS + protected GroupUnbanTransactionData() { + super(TransactionType.GROUP_UNBAN); + } + + public void afterUnmarshal(Unmarshaller u, Object parent) { + this.creatorPublicKey = this.adminPublicKey; + } + + public GroupUnbanTransactionData(byte[] adminPublicKey, String groupName, String member, byte[] groupReference, BigDecimal fee, long timestamp, byte[] reference, byte[] signature) { + super(TransactionType.GROUP_UNBAN, fee, adminPublicKey, timestamp, reference, signature); + + this.adminPublicKey = adminPublicKey; + this.groupName = groupName; + this.member = member; + this.groupReference = groupReference; + } + + public GroupUnbanTransactionData(byte[] adminPublicKey, String groupName, String member, byte[] groupReference, BigDecimal fee, long timestamp, byte[] reference) { + this(adminPublicKey, groupName, member, groupReference, fee, timestamp, reference, null); + } + + public GroupUnbanTransactionData(byte[] adminPublicKey, String groupName, String member, BigDecimal fee, long timestamp, byte[] reference, byte[] signature) { + this(adminPublicKey, groupName, member, null, fee, timestamp, reference, signature); + } + + public GroupUnbanTransactionData(byte[] adminPublicKey, String groupName, String member, BigDecimal fee, long timestamp, byte[] reference) { + this(adminPublicKey, groupName, member, null, fee, timestamp, reference, null); + } + + // Getters / setters + + public byte[] getAdminPublicKey() { + return this.adminPublicKey; + } + + public String getGroupName() { + return this.groupName; + } + + public String getMember() { + return this.member; + } + + public byte[] getGroupReference() { + return this.groupReference; + } + + public void setGroupReference(byte[] groupReference) { + this.groupReference = groupReference; + } + +} diff --git a/src/main/java/org/qora/data/transaction/TransactionData.java b/src/main/java/org/qora/data/transaction/TransactionData.java index 32d07eb1..0b474752 100644 --- a/src/main/java/org/qora/data/transaction/TransactionData.java +++ b/src/main/java/org/qora/data/transaction/TransactionData.java @@ -35,6 +35,7 @@ import io.swagger.v3.oas.annotations.media.Schema.AccessMode; MultiPaymentTransactionData.class, DeployATTransactionData.class, MessageTransactionData.class, ATTransactionData.class, CreateGroupTransactionData.class, UpdateGroupTransactionData.class, AddGroupAdminTransactionData.class, RemoveGroupAdminTransactionData.class, + GroupBanTransactionData.class, GroupUnbanTransactionData.class, GroupKickTransactionData.class, GroupInviteTransactionData.class, JoinGroupTransactionData.class, LeaveGroupTransactionData.class }) diff --git a/src/main/java/org/qora/group/Group.java b/src/main/java/org/qora/group/Group.java index 6237a7e7..e5ae41bb 100644 --- a/src/main/java/org/qora/group/Group.java +++ b/src/main/java/org/qora/group/Group.java @@ -6,6 +6,7 @@ import java.util.List; import org.qora.account.Account; import org.qora.account.PublicKeyAccount; import org.qora.data.group.GroupAdminData; +import org.qora.data.group.GroupBanData; import org.qora.data.group.GroupData; import org.qora.data.group.GroupInviteData; import org.qora.data.group.GroupJoinRequestData; @@ -13,8 +14,10 @@ import org.qora.data.group.GroupMemberData; import org.qora.data.transaction.AddGroupAdminTransactionData; import org.qora.data.transaction.CancelGroupInviteTransactionData; import org.qora.data.transaction.CreateGroupTransactionData; +import org.qora.data.transaction.GroupBanTransactionData; import org.qora.data.transaction.GroupInviteTransactionData; import org.qora.data.transaction.GroupKickTransactionData; +import org.qora.data.transaction.GroupUnbanTransactionData; import org.qora.data.transaction.JoinGroupTransactionData; import org.qora.data.transaction.LeaveGroupTransactionData; import org.qora.data.transaction.RemoveGroupAdminTransactionData; @@ -65,16 +68,30 @@ public class Group { // Processing + /* + * GroupData records can be changed by CREATE_GROUP or UPDATE_GROUP transactions. + * + * GroupData stores the signature of the last transaction that caused a change to its contents + * in a field called "reference". + * + * During orphaning, "reference" is used to fetch the previous GroupData-changing transaction + * and that transaction's contents are used to restore the previous GroupData state. + */ + + // CREATE GROUP + public void create(CreateGroupTransactionData createGroupTransactionData) throws DataException { + // Note: this.groupData already populated by our constructor above this.repository.getGroupRepository().save(this.groupData); - // Add owner as admin too - this.repository.getGroupRepository() - .save(new GroupAdminData(this.groupData.getGroupName(), this.groupData.getOwner(), createGroupTransactionData.getSignature())); + // Add owner as admin + GroupAdminData groupAdminData = new GroupAdminData(this.groupData.getGroupName(), this.groupData.getOwner(), createGroupTransactionData.getSignature()); + this.repository.getGroupRepository().save(groupAdminData); - // Add owner as member too - this.repository.getGroupRepository().save(new GroupMemberData(this.groupData.getGroupName(), this.groupData.getOwner(), this.groupData.getCreated(), - createGroupTransactionData.getSignature())); + // Add owner as member + GroupMemberData groupMemberData = new GroupMemberData(this.groupData.getGroupName(), this.groupData.getOwner(), this.groupData.getCreated(), + createGroupTransactionData.getSignature()); + this.repository.getGroupRepository().save(groupMemberData); } public void uncreate() throws DataException { @@ -82,43 +99,22 @@ public class Group { this.repository.getGroupRepository().delete(this.groupData.getGroupName()); } - private void revert() throws DataException { - TransactionData previousTransactionData = this.repository.getTransactionRepository().fromSignature(this.groupData.getReference()); - if (previousTransactionData == null) - throw new DataException("Unable to revert group transaction as referenced transaction not found in repository"); + // UPDATE GROUP - switch (previousTransactionData.getType()) { - case CREATE_GROUP: - CreateGroupTransactionData previousCreateGroupTransactionData = (CreateGroupTransactionData) previousTransactionData; - this.groupData.setOwner(previousCreateGroupTransactionData.getOwner()); - this.groupData.setDescription(previousCreateGroupTransactionData.getDescription()); - this.groupData.setIsOpen(previousCreateGroupTransactionData.getIsOpen()); - this.groupData.setUpdated(null); - break; + /* + * In UPDATE_GROUP transactions we store the current GroupData's "reference" in the + * transaction's field "group_reference" and update GroupData's "reference" to + * our transaction's signature to form an undo chain. + */ - case UPDATE_GROUP: - UpdateGroupTransactionData previousUpdateGroupTransactionData = (UpdateGroupTransactionData) previousTransactionData; - this.groupData.setOwner(previousUpdateGroupTransactionData.getNewOwner()); - this.groupData.setDescription(previousUpdateGroupTransactionData.getNewDescription()); - this.groupData.setIsOpen(previousUpdateGroupTransactionData.getNewIsOpen()); - this.groupData.setUpdated(previousUpdateGroupTransactionData.getTimestamp()); - break; - - default: - throw new IllegalStateException("Unable to revert group transaction due to unsupported referenced transaction"); - } - - // Previous owner will still be admin and member at this point - } - - public void update(UpdateGroupTransactionData updateGroupTransactionData) throws DataException { + public void updateGroup(UpdateGroupTransactionData updateGroupTransactionData) throws DataException { GroupRepository groupRepository = this.repository.getGroupRepository(); String groupName = updateGroupTransactionData.getGroupName(); - // Update reference in transaction data + // Save GroupData's reference in our transaction data updateGroupTransactionData.setGroupReference(this.groupData.getReference()); - // New group reference is this transaction's signature + // Update GroupData's reference to this transaction's signature this.groupData.setReference(updateGroupTransactionData.getSignature()); // Update Group's owner and description @@ -148,7 +144,7 @@ public class Group { // Previous owner retained as admin and member } - public void revert(UpdateGroupTransactionData updateGroupTransactionData) throws DataException { + public void unupdateGroup(UpdateGroupTransactionData updateGroupTransactionData) throws DataException { GroupRepository groupRepository = this.repository.getGroupRepository(); String groupName = updateGroupTransactionData.getGroupName(); @@ -156,7 +152,7 @@ public class Group { this.groupData.setReference(updateGroupTransactionData.getGroupReference()); // Previous Group's owner and/or description taken from referenced transaction - this.revert(); + this.revertGroupUpdate(); // Save reverted group data groupRepository.save(this.groupData); @@ -178,6 +174,35 @@ public class Group { } } + private void revertGroupUpdate() throws DataException { + TransactionData previousTransactionData = this.repository.getTransactionRepository().fromSignature(this.groupData.getReference()); + if (previousTransactionData == null) + throw new DataException("Unable to revert group transaction as referenced transaction not found in repository"); + + switch (previousTransactionData.getType()) { + case CREATE_GROUP: + CreateGroupTransactionData previousCreateGroupTransactionData = (CreateGroupTransactionData) previousTransactionData; + this.groupData.setOwner(previousCreateGroupTransactionData.getOwner()); + this.groupData.setDescription(previousCreateGroupTransactionData.getDescription()); + this.groupData.setIsOpen(previousCreateGroupTransactionData.getIsOpen()); + this.groupData.setUpdated(null); + break; + + case UPDATE_GROUP: + UpdateGroupTransactionData previousUpdateGroupTransactionData = (UpdateGroupTransactionData) previousTransactionData; + this.groupData.setOwner(previousUpdateGroupTransactionData.getNewOwner()); + this.groupData.setDescription(previousUpdateGroupTransactionData.getNewDescription()); + this.groupData.setIsOpen(previousUpdateGroupTransactionData.getNewIsOpen()); + this.groupData.setUpdated(previousUpdateGroupTransactionData.getTimestamp()); + break; + + default: + throw new IllegalStateException("Unable to revert group transaction due to unsupported referenced transaction"); + } + + // Previous owner will still be admin and member at this point + } + public void promoteToAdmin(AddGroupAdminTransactionData addGroupAdminTransactionData) throws DataException { GroupAdminData groupAdminData = new GroupAdminData(addGroupAdminTransactionData.getGroupName(), addGroupAdminTransactionData.getMember(), addGroupAdminTransactionData.getSignature()); @@ -216,12 +241,29 @@ public class Group { String groupName = groupKickTransactionData.getGroupName(); String member = groupKickTransactionData.getMember(); + // If pending join request then this is a essentially a deny response so delete join request and exit + if (groupRepository.joinRequestExists(groupName, member)) { + // Delete join request + groupRepository.deleteJoinRequest(groupName, member); + + // Make sure kick transaction's member/admin-references are null to indicate that there + // was no existing member but actually only a join request. This should prevent orphaning code + // from trying to incorrectly recreate a member/admin. + groupKickTransactionData.setMemberReference(null); + groupKickTransactionData.setAdminReference(null); + + return; + } + // Store membership and (optionally) adminship transactions for orphaning purposes GroupAdminData groupAdminData = groupRepository.getAdmin(groupName, member); if (groupAdminData != null) { groupKickTransactionData.setAdminReference(groupAdminData.getGroupReference()); groupRepository.deleteAdmin(groupName, member); + } else { + // Not an admin + groupKickTransactionData.setAdminReference(null); } GroupMemberData groupMemberData = groupRepository.getMember(groupName, member); @@ -235,6 +277,15 @@ public class Group { String groupName = groupKickTransactionData.getGroupName(); String member = groupKickTransactionData.getMember(); + // If there's no member-reference then there wasn't an actual member, only a join request + if (groupKickTransactionData.getMemberReference() == null) { + // Rebuild join-request + GroupJoinRequestData groupJoinRequestData = new GroupJoinRequestData(groupName, member); + groupRepository.save(groupJoinRequestData); + + return; + } + // Rebuild member entry using stored transaction reference TransactionData membershipTransactionData = this.repository.getTransactionRepository().fromSignature(groupKickTransactionData.getMemberReference()); GroupMemberData groupMemberData = new GroupMemberData(groupName, member, membershipTransactionData.getTimestamp(), @@ -248,6 +299,105 @@ public class Group { } } + public void ban(GroupBanTransactionData groupBanTransactionData) throws DataException { + GroupRepository groupRepository = this.repository.getGroupRepository(); + String groupName = groupBanTransactionData.getGroupName(); + String offender = groupBanTransactionData.getOffender(); + + // Kick if member + if (groupRepository.memberExists(groupName, offender)) { + // Store membership and (optionally) adminship transactions for orphaning purposes + GroupAdminData groupAdminData = groupRepository.getAdmin(groupName, offender); + if (groupAdminData != null) { + groupBanTransactionData.setAdminReference(groupAdminData.getGroupReference()); + + groupRepository.deleteAdmin(groupName, offender); + } else { + // Not an admin + groupBanTransactionData.setAdminReference(null); + } + + GroupMemberData groupMemberData = groupRepository.getMember(groupName, offender); + groupBanTransactionData.setMemberReference(groupMemberData.getGroupReference()); + + groupRepository.deleteMember(groupName, offender); + } else { + groupBanTransactionData.setMemberReference(null); + + // XXX maybe set join-request reference here? + // XXX what about invites? + } + + // XXX Delete pending join request + // XXX Delete pending invites + + // Ban + Account admin = new PublicKeyAccount(this.repository, groupBanTransactionData.getAdminPublicKey()); + long banned = groupBanTransactionData.getTimestamp(); + String reason = groupBanTransactionData.getReason(); + + Long expiry = null; + int timeToLive = groupBanTransactionData.getTimeToLive(); + if (timeToLive != 0) + expiry = groupBanTransactionData.getTimestamp() + timeToLive * 1000; + + // Save reference to banning transaction for orphaning purposes + byte[] reference = groupBanTransactionData.getSignature(); + + GroupBanData groupBanData = new GroupBanData(groupName, offender, admin.getAddress(), banned, reason, expiry, reference); + groupRepository.save(groupBanData); + } + + public void unban(GroupBanTransactionData groupBanTransactionData) throws DataException { + // Orphaning version of "ban" - not actual "unban" + GroupRepository groupRepository = this.repository.getGroupRepository(); + String groupName = groupBanTransactionData.getGroupName(); + String offender = groupBanTransactionData.getOffender(); + + // If was kicked as part of ban then reinstate + if (groupBanTransactionData.getMemberReference() != null) { + // Rebuild member entry using stored transaction reference + TransactionData membershipTransactionData = this.repository.getTransactionRepository().fromSignature(groupBanTransactionData.getMemberReference()); + GroupMemberData groupMemberData = new GroupMemberData(groupName, offender, membershipTransactionData.getTimestamp(), + membershipTransactionData.getSignature()); + groupRepository.save(groupMemberData); + + if (groupBanTransactionData.getAdminReference() != null) { + // Rebuild admin entry using stored transaction reference + GroupAdminData groupAdminData = new GroupAdminData(groupName, offender, groupBanTransactionData.getAdminReference()); + groupRepository.save(groupAdminData); + } + } + + // XXX Reinstate pending join request + // XXX Reinstate pending invites + + // Delete ban + groupRepository.deleteBan(groupName, offender); + } + + public void cancelBan(GroupUnbanTransactionData groupUnbanTransactionData) throws DataException { + GroupRepository groupRepository = this.repository.getGroupRepository(); + String groupName = groupUnbanTransactionData.getGroupName(); + String member = groupUnbanTransactionData.getMember(); + + GroupBanData groupBanData = groupRepository.getBan(groupName, member); + + // Save reference to banning transaction for orphaning purposes + groupUnbanTransactionData.setGroupReference(groupBanData.getReference()); + + // Delete ban + groupRepository.deleteBan(groupName, member); + } + + public void uncancelBan(GroupUnbanTransactionData groupUnbanTransactionData) throws DataException { + // Reinstate ban + TransactionData transactionData = this.repository.getTransactionRepository().fromSignature(groupUnbanTransactionData.getGroupReference()); + ban((GroupBanTransactionData) transactionData); + + groupUnbanTransactionData.setGroupReference(null); + } + public void invite(GroupInviteTransactionData groupInviteTransactionData) throws DataException { GroupRepository groupRepository = this.repository.getGroupRepository(); String groupName = groupInviteTransactionData.getGroupName(); @@ -265,12 +415,10 @@ public class Group { return; } - Long expiry; + Long expiry = null; int timeToLive = groupInviteTransactionData.getTimeToLive(); - if (timeToLive == 0) - expiry = null; - else - expiry = groupInviteTransactionData.getTimestamp() + timeToLive; + if (timeToLive != 0) + expiry = groupInviteTransactionData.getTimestamp() + timeToLive * 1000; GroupInviteData groupInviteData = new GroupInviteData(groupName, inviter.getAddress(), groupInviteTransactionData.getInvitee(), expiry, groupInviteTransactionData.getSignature()); diff --git a/src/main/java/org/qora/repository/GroupRepository.java b/src/main/java/org/qora/repository/GroupRepository.java index d352581a..ad9ca555 100644 --- a/src/main/java/org/qora/repository/GroupRepository.java +++ b/src/main/java/org/qora/repository/GroupRepository.java @@ -3,6 +3,7 @@ package org.qora.repository; import java.util.List; import org.qora.data.group.GroupAdminData; +import org.qora.data.group.GroupBanData; import org.qora.data.group.GroupData; import org.qora.data.group.GroupInviteData; import org.qora.data.group.GroupJoinRequestData; @@ -55,7 +56,7 @@ public interface GroupRepository { public GroupInviteData getInvite(String groupName, String inviter, String invitee) throws DataException; - public boolean hasInvite(String groupName, String invitee) throws DataException; + public boolean inviteExists(String groupName, String invitee) throws DataException; public boolean inviteExists(String groupName, String inviter, String invitee) throws DataException; @@ -77,4 +78,16 @@ public interface GroupRepository { public void deleteJoinRequest(String groupName, String joiner) throws DataException; + // Group Bans + + public GroupBanData getBan(String groupName, String member) throws DataException; + + public boolean banExists(String groupName, String offender) throws DataException; + + public List getGroupBans(String groupName) throws DataException; + + public void save(GroupBanData groupBanData) throws DataException; + + public void deleteBan(String groupName, String offender) throws DataException; + } \ No newline at end of file diff --git a/src/main/java/org/qora/repository/hsqldb/HSQLDBDatabaseUpdates.java b/src/main/java/org/qora/repository/hsqldb/HSQLDBDatabaseUpdates.java index 812acb0b..4997addd 100644 --- a/src/main/java/org/qora/repository/hsqldb/HSQLDBDatabaseUpdates.java +++ b/src/main/java/org/qora/repository/hsqldb/HSQLDBDatabaseUpdates.java @@ -429,7 +429,8 @@ public class HSQLDBDatabaseUpdates { // Bans // NULL expiry means does not expire! stmt.execute("CREATE TABLE AccountGroupBans (group_name GroupName, offender QoraAddress, admin QoraAddress NOT NULL, banned TIMESTAMP WITH TIME ZONE NOT NULL, " - + "reason GenericDescription NOT NULL, expiry TIMESTAMP WITH TIME ZONE, PRIMARY KEY (group_name, offender))"); + + "reason GenericDescription NOT NULL, expiry TIMESTAMP WITH TIME ZONE, reference Signature NOT NULL, " + + "PRIMARY KEY (group_name, offender))"); // For expiry maintenance stmt.execute("CREATE INDEX AccountGroupBanExpiryIndex ON AccountGroupBans (expiry)"); break; @@ -470,6 +471,13 @@ public class HSQLDBDatabaseUpdates { // Cancel group invite stmt.execute("CREATE TABLE CancelGroupInviteTransactions (signature Signature, admin QoraPublicKey NOT NULL, group_name GroupName NOT NULL, invitee QoraAddress NOT NULL, " + "group_reference Signature, PRIMARY KEY (signature), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)"); + + // Account ban/unban transactions + stmt.execute("CREATE TABLE GroupBanTransactions (signature Signature, admin QoraPublicKey NOT NULL, group_name GroupName NOT NULL, address QoraAddress NOT NULL, " + + "reason VARCHAR(400), time_to_live INTEGER NOT NULL, member_reference Signature, admin_reference Signature, " + + "PRIMARY KEY (signature), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)"); + stmt.execute("CREATE TABLE GroupUnbanTransactions (signature Signature, admin QoraPublicKey NOT NULL, group_name GroupName NOT NULL, address QoraAddress NOT NULL, " + + "group_reference Signature, PRIMARY KEY (signature), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)"); break; default: diff --git a/src/main/java/org/qora/repository/hsqldb/HSQLDBGroupRepository.java b/src/main/java/org/qora/repository/hsqldb/HSQLDBGroupRepository.java index 2f0367d7..493abd8e 100644 --- a/src/main/java/org/qora/repository/hsqldb/HSQLDBGroupRepository.java +++ b/src/main/java/org/qora/repository/hsqldb/HSQLDBGroupRepository.java @@ -8,6 +8,7 @@ import java.util.Calendar; import java.util.List; import org.qora.data.group.GroupAdminData; +import org.qora.data.group.GroupBanData; import org.qora.data.group.GroupData; import org.qora.data.group.GroupInviteData; import org.qora.data.group.GroupJoinRequestData; @@ -317,8 +318,7 @@ public class HSQLDBGroupRepository implements GroupRepository { @Override public GroupInviteData getInvite(String groupName, String inviter, String invitee) throws DataException { - try (ResultSet resultSet = this.repository.checkedExecute("SELECT expiry, reference FROM AccountGroupInvites WHERE group_name = ?", - groupName)) { + try (ResultSet resultSet = this.repository.checkedExecute("SELECT expiry, reference FROM AccountGroupInvites WHERE group_name = ?", groupName)) { if (resultSet == null) return null; @@ -334,7 +334,7 @@ public class HSQLDBGroupRepository implements GroupRepository { } @Override - public boolean hasInvite(String groupName, String invitee) throws DataException { + public boolean inviteExists(String groupName, String invitee) throws DataException { try { return this.repository.exists("AccountGroupInvites", "group_name = ? AND invitee = ?", groupName, invitee); } catch (SQLException e) { @@ -408,10 +408,8 @@ public class HSQLDBGroupRepository implements GroupRepository { public void save(GroupInviteData groupInviteData) throws DataException { HSQLDBSaver saveHelper = new HSQLDBSaver("AccountGroupInvites"); - Timestamp expiryTimestamp; - if (groupInviteData.getExpiry() == null) - expiryTimestamp = null; - else + Timestamp expiryTimestamp = null; + if (groupInviteData.getExpiry() != null) expiryTimestamp = new Timestamp(groupInviteData.getExpiry()); saveHelper.bind("group_name", groupInviteData.getGroupName()).bind("inviter", groupInviteData.getInviter()) @@ -448,8 +446,7 @@ public class HSQLDBGroupRepository implements GroupRepository { public List getGroupJoinRequests(String groupName) throws DataException { List joinRequests = new ArrayList<>(); - try (ResultSet resultSet = this.repository - .checkedExecute("SELECT joiner FROM AccountGroupJoinRequests WHERE group_name = ?", groupName)) { + try (ResultSet resultSet = this.repository.checkedExecute("SELECT joiner FROM AccountGroupJoinRequests WHERE group_name = ?", groupName)) { if (resultSet == null) return joinRequests; @@ -487,4 +484,92 @@ public class HSQLDBGroupRepository implements GroupRepository { } } + // Group Bans + + @Override + public GroupBanData getBan(String groupName, String member) throws DataException { + try (ResultSet resultSet = this.repository + .checkedExecute("SELECT offender, admin, banned, reason, expiry, reference FROM AccountGroupBans WHERE group_name = ?", groupName)) { + String offender = resultSet.getString(1); + String admin = resultSet.getString(2); + long banned = resultSet.getTimestamp(3, Calendar.getInstance(HSQLDBRepository.UTC)).getTime(); + String reason = resultSet.getString(4); + + Timestamp expiryTimestamp = resultSet.getTimestamp(5, Calendar.getInstance(HSQLDBRepository.UTC)); + Long expiry = expiryTimestamp == null ? null : expiryTimestamp.getTime(); + + byte[] reference = resultSet.getBytes(6); + + return new GroupBanData(groupName, offender, admin, banned, reason, expiry, reference); + } catch (SQLException e) { + throw new DataException("Unable to fetch group bans from repository", e); + } + } + + @Override + public boolean banExists(String groupName, String offender) throws DataException { + try { + return this.repository.exists("AccountGroupBans", "group_name = ? AND offender = ?", groupName, offender); + } catch (SQLException e) { + throw new DataException("Unable to check for group ban in repository", e); + } + } + + @Override + public List getGroupBans(String groupName) throws DataException { + List bans = new ArrayList<>(); + + try (ResultSet resultSet = this.repository + .checkedExecute("SELECT offender, admin, banned, reason, expiry, reference FROM AccountGroupBans WHERE group_name = ?", groupName)) { + if (resultSet == null) + return bans; + + do { + String offender = resultSet.getString(1); + String admin = resultSet.getString(2); + long banned = resultSet.getTimestamp(3, Calendar.getInstance(HSQLDBRepository.UTC)).getTime(); + String reason = resultSet.getString(4); + + Timestamp expiryTimestamp = resultSet.getTimestamp(5, Calendar.getInstance(HSQLDBRepository.UTC)); + Long expiry = expiryTimestamp == null ? null : expiryTimestamp.getTime(); + + byte[] reference = resultSet.getBytes(6); + + bans.add(new GroupBanData(groupName, offender, admin, banned, reason, expiry, reference)); + } while (resultSet.next()); + + return bans; + } catch (SQLException e) { + throw new DataException("Unable to fetch group bans from repository", e); + } + } + + @Override + public void save(GroupBanData groupBanData) throws DataException { + HSQLDBSaver saveHelper = new HSQLDBSaver("AccountGroupBans"); + + Timestamp expiryTimestamp = null; + if (groupBanData.getExpiry() != null) + expiryTimestamp = new Timestamp(groupBanData.getExpiry()); + + saveHelper.bind("group_name", groupBanData.getGroupName()).bind("offender", groupBanData.getOffender()).bind("admin", groupBanData.getAdmin()) + .bind("banned", new Timestamp(groupBanData.getBanned())).bind("reason", groupBanData.getReason()).bind("expiry", expiryTimestamp) + .bind("reference", groupBanData.getReference()); + + try { + saveHelper.execute(this.repository); + } catch (SQLException e) { + throw new DataException("Unable to save group ban into repository", e); + } + } + + @Override + public void deleteBan(String groupName, String offender) throws DataException { + try { + this.repository.delete("AccountGroupBans", "group_name = ? AND offender = ?", groupName, offender); + } catch (SQLException e) { + throw new DataException("Unable to delete group ban from repository", e); + } + } + } diff --git a/src/main/java/org/qora/repository/hsqldb/transaction/HSQLDBGroupBanTransactionRepository.java b/src/main/java/org/qora/repository/hsqldb/transaction/HSQLDBGroupBanTransactionRepository.java new file mode 100644 index 00000000..51dd2cc7 --- /dev/null +++ b/src/main/java/org/qora/repository/hsqldb/transaction/HSQLDBGroupBanTransactionRepository.java @@ -0,0 +1,58 @@ +package org.qora.repository.hsqldb.transaction; + +import java.math.BigDecimal; +import java.sql.ResultSet; +import java.sql.SQLException; + +import org.qora.data.transaction.GroupBanTransactionData; +import org.qora.data.transaction.TransactionData; +import org.qora.repository.DataException; +import org.qora.repository.hsqldb.HSQLDBRepository; +import org.qora.repository.hsqldb.HSQLDBSaver; + +public class HSQLDBGroupBanTransactionRepository extends HSQLDBTransactionRepository { + + public HSQLDBGroupBanTransactionRepository(HSQLDBRepository repository) { + this.repository = repository; + } + + TransactionData fromBase(byte[] signature, byte[] reference, byte[] creatorPublicKey, long timestamp, BigDecimal fee) throws DataException { + try (ResultSet resultSet = this.repository.checkedExecute( + "SELECT group_name, address, reason, time_to_live, member_reference, admin_reference FROM GroupBanTransactions WHERE signature = ?", + signature)) { + if (resultSet == null) + return null; + + String groupName = resultSet.getString(1); + String offender = resultSet.getString(2); + String reason = resultSet.getString(3); + int timeToLive = resultSet.getInt(4); + byte[] memberReference = resultSet.getBytes(5); + byte[] adminReference = resultSet.getBytes(6); + + return new GroupBanTransactionData(creatorPublicKey, groupName, offender, reason, timeToLive, memberReference, adminReference, fee, timestamp, + reference, signature); + } catch (SQLException e) { + throw new DataException("Unable to fetch group ban transaction from repository", e); + } + } + + @Override + public void save(TransactionData transactionData) throws DataException { + GroupBanTransactionData groupBanTransactionData = (GroupBanTransactionData) transactionData; + + HSQLDBSaver saveHelper = new HSQLDBSaver("GroupBanTransactions"); + + saveHelper.bind("signature", groupBanTransactionData.getSignature()).bind("admin", groupBanTransactionData.getAdminPublicKey()) + .bind("group_name", groupBanTransactionData.getGroupName()).bind("address", groupBanTransactionData.getOffender()) + .bind("reason", groupBanTransactionData.getReason()).bind("time_to_live", groupBanTransactionData.getTimeToLive()) + .bind("member_reference", groupBanTransactionData.getMemberReference()).bind("admin_reference", groupBanTransactionData.getAdminReference()); + + try { + saveHelper.execute(this.repository); + } catch (SQLException e) { + throw new DataException("Unable to save group ban transaction into repository", e); + } + } + +} diff --git a/src/main/java/org/qora/repository/hsqldb/transaction/HSQLDBGroupUnbanTransactionRepository.java b/src/main/java/org/qora/repository/hsqldb/transaction/HSQLDBGroupUnbanTransactionRepository.java new file mode 100644 index 00000000..148896f1 --- /dev/null +++ b/src/main/java/org/qora/repository/hsqldb/transaction/HSQLDBGroupUnbanTransactionRepository.java @@ -0,0 +1,52 @@ +package org.qora.repository.hsqldb.transaction; + +import java.math.BigDecimal; +import java.sql.ResultSet; +import java.sql.SQLException; + +import org.qora.data.transaction.GroupUnbanTransactionData; +import org.qora.data.transaction.TransactionData; +import org.qora.repository.DataException; +import org.qora.repository.hsqldb.HSQLDBRepository; +import org.qora.repository.hsqldb.HSQLDBSaver; + +public class HSQLDBGroupUnbanTransactionRepository extends HSQLDBTransactionRepository { + + public HSQLDBGroupUnbanTransactionRepository(HSQLDBRepository repository) { + this.repository = repository; + } + + TransactionData fromBase(byte[] signature, byte[] reference, byte[] creatorPublicKey, long timestamp, BigDecimal fee) throws DataException { + try (ResultSet resultSet = this.repository.checkedExecute("SELECT group_name, address, group_reference FROM GroupUnbanTransactions WHERE signature = ?", + signature)) { + if (resultSet == null) + return null; + + String groupName = resultSet.getString(1); + String member = resultSet.getString(2); + byte[] groupReference = resultSet.getBytes(3); + + return new GroupUnbanTransactionData(creatorPublicKey, groupName, member, groupReference, fee, timestamp, reference, signature); + } catch (SQLException e) { + throw new DataException("Unable to fetch group unban transaction from repository", e); + } + } + + @Override + public void save(TransactionData transactionData) throws DataException { + GroupUnbanTransactionData groupUnbanTransactionData = (GroupUnbanTransactionData) transactionData; + + HSQLDBSaver saveHelper = new HSQLDBSaver("GroupUnbanTransactions"); + + saveHelper.bind("signature", groupUnbanTransactionData.getSignature()).bind("admin", groupUnbanTransactionData.getAdminPublicKey()) + .bind("group_name", groupUnbanTransactionData.getGroupName()).bind("address", groupUnbanTransactionData.getMember()) + .bind("group_reference", groupUnbanTransactionData.getGroupReference()); + + try { + saveHelper.execute(this.repository); + } catch (SQLException e) { + throw new DataException("Unable to save group unban transaction into repository", e); + } + } + +} diff --git a/src/main/java/org/qora/repository/hsqldb/transaction/HSQLDBTransactionRepository.java b/src/main/java/org/qora/repository/hsqldb/transaction/HSQLDBTransactionRepository.java index 74f13ea8..44c450b0 100644 --- a/src/main/java/org/qora/repository/hsqldb/transaction/HSQLDBTransactionRepository.java +++ b/src/main/java/org/qora/repository/hsqldb/transaction/HSQLDBTransactionRepository.java @@ -46,6 +46,8 @@ public class HSQLDBTransactionRepository implements TransactionRepository { private HSQLDBUpdateGroupTransactionRepository updateGroupTransactionRepository; private HSQLDBAddGroupAdminTransactionRepository addGroupAdminTransactionRepository; private HSQLDBRemoveGroupAdminTransactionRepository removeGroupAdminTransactionRepository; + private HSQLDBGroupBanTransactionRepository groupBanTransactionRepository; + private HSQLDBGroupUnbanTransactionRepository groupUnbanTransactionRepository; private HSQLDBGroupKickTransactionRepository groupKickTransactionRepository; private HSQLDBGroupInviteTransactionRepository groupInviteTransactionRepository; private HSQLDBCancelGroupInviteTransactionRepository cancelGroupInviteTransactionRepository; @@ -76,6 +78,8 @@ public class HSQLDBTransactionRepository implements TransactionRepository { this.updateGroupTransactionRepository = new HSQLDBUpdateGroupTransactionRepository(repository); this.addGroupAdminTransactionRepository = new HSQLDBAddGroupAdminTransactionRepository(repository); this.removeGroupAdminTransactionRepository = new HSQLDBRemoveGroupAdminTransactionRepository(repository); + this.groupBanTransactionRepository = new HSQLDBGroupBanTransactionRepository(repository); + this.groupUnbanTransactionRepository = new HSQLDBGroupUnbanTransactionRepository(repository); this.groupKickTransactionRepository = new HSQLDBGroupKickTransactionRepository(repository); this.groupInviteTransactionRepository = new HSQLDBGroupInviteTransactionRepository(repository); this.cancelGroupInviteTransactionRepository = new HSQLDBCancelGroupInviteTransactionRepository(repository); @@ -219,6 +223,12 @@ public class HSQLDBTransactionRepository implements TransactionRepository { case REMOVE_GROUP_ADMIN: return this.removeGroupAdminTransactionRepository.fromBase(signature, reference, creatorPublicKey, timestamp, fee); + case GROUP_BAN: + return this.groupBanTransactionRepository.fromBase(signature, reference, creatorPublicKey, timestamp, fee); + + case GROUP_UNBAN: + return this.groupUnbanTransactionRepository.fromBase(signature, reference, creatorPublicKey, timestamp, fee); + case GROUP_KICK: return this.groupKickTransactionRepository.fromBase(signature, reference, creatorPublicKey, timestamp, fee); @@ -570,6 +580,14 @@ public class HSQLDBTransactionRepository implements TransactionRepository { this.removeGroupAdminTransactionRepository.save(transactionData); break; + case GROUP_BAN: + this.groupBanTransactionRepository.save(transactionData); + break; + + case GROUP_UNBAN: + this.groupUnbanTransactionRepository.save(transactionData); + break; + case GROUP_KICK: this.groupKickTransactionRepository.save(transactionData); break; diff --git a/src/main/java/org/qora/transaction/GroupBanTransaction.java b/src/main/java/org/qora/transaction/GroupBanTransaction.java new file mode 100644 index 00000000..911b6a9a --- /dev/null +++ b/src/main/java/org/qora/transaction/GroupBanTransaction.java @@ -0,0 +1,161 @@ +package org.qora.transaction; + +import java.math.BigDecimal; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import org.qora.account.Account; +import org.qora.account.PublicKeyAccount; +import org.qora.asset.Asset; +import org.qora.crypto.Crypto; +import org.qora.data.transaction.GroupBanTransactionData; +import org.qora.data.group.GroupData; +import org.qora.data.transaction.TransactionData; +import org.qora.group.Group; +import org.qora.repository.DataException; +import org.qora.repository.GroupRepository; +import org.qora.repository.Repository; + +import com.google.common.base.Utf8; + +public class GroupBanTransaction extends Transaction { + + // Properties + private GroupBanTransactionData groupBanTransactionData; + + // Constructors + + public GroupBanTransaction(Repository repository, TransactionData transactionData) { + super(repository, transactionData); + + this.groupBanTransactionData = (GroupBanTransactionData) this.transactionData; + } + + // More information + + @Override + public List getRecipientAccounts() throws DataException { + return Collections.emptyList(); + } + + @Override + public boolean isInvolved(Account account) throws DataException { + String address = account.getAddress(); + + if (address.equals(this.getAdmin().getAddress())) + return true; + + if (address.equals(this.getOffender().getAddress())) + return true; + + return false; + } + + @Override + public BigDecimal getAmount(Account account) throws DataException { + String address = account.getAddress(); + BigDecimal amount = BigDecimal.ZERO.setScale(8); + + if (address.equals(this.getAdmin().getAddress())) + amount = amount.subtract(this.transactionData.getFee()); + + return amount; + } + + // Navigation + + public Account getAdmin() throws DataException { + return new PublicKeyAccount(this.repository, this.groupBanTransactionData.getAdminPublicKey()); + } + + public Account getOffender() throws DataException { + return new Account(this.repository, this.groupBanTransactionData.getOffender()); + } + + // Processing + + @Override + public ValidationResult isValid() throws DataException { + GroupRepository groupRepository = this.repository.getGroupRepository(); + String groupName = groupBanTransactionData.getGroupName(); + + // Check member address is valid + if (!Crypto.isValidAddress(groupBanTransactionData.getOffender())) + return ValidationResult.INVALID_ADDRESS; + + // Check group name size bounds + int groupNameLength = Utf8.encodedLength(groupName); + if (groupNameLength < 1 || groupNameLength > Group.MAX_NAME_SIZE) + return ValidationResult.INVALID_NAME_LENGTH; + + // Check group name is lowercase + if (!groupName.equals(groupName.toLowerCase())) + return ValidationResult.NAME_NOT_LOWER_CASE; + + GroupData groupData = groupRepository.fromGroupName(groupName); + + // Check group exists + if (groupData == null) + return ValidationResult.GROUP_DOES_NOT_EXIST; + + Account admin = getAdmin(); + Account member = getOffender(); + + // Can't ban if not an admin + if (!groupRepository.adminExists(groupName, admin.getAddress())) + return ValidationResult.NOT_GROUP_ADMIN; + + // Can't ban another admin unless the group owner + if (!admin.getAddress().equals(groupData.getOwner()) && groupRepository.adminExists(groupName, member.getAddress())) + return ValidationResult.INVALID_GROUP_OWNER; + + // Check fee is positive + if (groupBanTransactionData.getFee().compareTo(BigDecimal.ZERO) <= 0) + return ValidationResult.NEGATIVE_FEE; + + if (!Arrays.equals(admin.getLastReference(), groupBanTransactionData.getReference())) + return ValidationResult.INVALID_REFERENCE; + + // Check creator has enough funds + if (admin.getConfirmedBalance(Asset.QORA).compareTo(groupBanTransactionData.getFee()) < 0) + return ValidationResult.NO_BALANCE; + + return ValidationResult.OK; + } + + @Override + public void process() throws DataException { + // Update Group Membership + Group group = new Group(this.repository, groupBanTransactionData.getGroupName()); + group.ban(groupBanTransactionData); + + // Save this transaction with updated member/admin references to transactions that can help restore state + this.repository.getTransactionRepository().save(groupBanTransactionData); + + // Update admin's balance + Account admin = getAdmin(); + admin.setConfirmedBalance(Asset.QORA, admin.getConfirmedBalance(Asset.QORA).subtract(groupBanTransactionData.getFee())); + + // Update admin's reference + admin.setLastReference(groupBanTransactionData.getSignature()); + } + + @Override + public void orphan() throws DataException { + // Revert group membership + Group group = new Group(this.repository, groupBanTransactionData.getGroupName()); + group.unban(groupBanTransactionData); + + // Delete this transaction itself + this.repository.getTransactionRepository().delete(groupBanTransactionData); + + // Update admin's balance + Account admin = getAdmin(); + admin.setConfirmedBalance(Asset.QORA, admin.getConfirmedBalance(Asset.QORA).add(groupBanTransactionData.getFee())); + + // Update admin's reference + admin.setLastReference(groupBanTransactionData.getReference()); + } + +} diff --git a/src/main/java/org/qora/transaction/GroupKickTransaction.java b/src/main/java/org/qora/transaction/GroupKickTransaction.java index 2517b79d..f781a5b9 100644 --- a/src/main/java/org/qora/transaction/GroupKickTransaction.java +++ b/src/main/java/org/qora/transaction/GroupKickTransaction.java @@ -106,8 +106,8 @@ public class GroupKickTransaction extends Transaction { if (!groupRepository.adminExists(groupName, admin.getAddress())) return ValidationResult.NOT_GROUP_ADMIN; - // Check member actually in group - if (!groupRepository.memberExists(groupName, member.getAddress())) + // Check member actually in group UNLESS there's a pending join request + if (!groupRepository.joinRequestExists(groupName, member.getAddress()) && !groupRepository.memberExists(groupName, member.getAddress())) return ValidationResult.NOT_GROUP_MEMBER; // Can't kick another admin unless the group owner diff --git a/src/main/java/org/qora/transaction/GroupUnbanTransaction.java b/src/main/java/org/qora/transaction/GroupUnbanTransaction.java new file mode 100644 index 00000000..641bacbd --- /dev/null +++ b/src/main/java/org/qora/transaction/GroupUnbanTransaction.java @@ -0,0 +1,161 @@ +package org.qora.transaction; + +import java.math.BigDecimal; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import org.qora.account.Account; +import org.qora.account.PublicKeyAccount; +import org.qora.asset.Asset; +import org.qora.crypto.Crypto; +import org.qora.data.transaction.GroupUnbanTransactionData; +import org.qora.data.group.GroupData; +import org.qora.data.transaction.TransactionData; +import org.qora.group.Group; +import org.qora.repository.DataException; +import org.qora.repository.GroupRepository; +import org.qora.repository.Repository; + +import com.google.common.base.Utf8; + +public class GroupUnbanTransaction extends Transaction { + + // Properties + private GroupUnbanTransactionData groupUnbanTransactionData; + + // Constructors + + public GroupUnbanTransaction(Repository repository, TransactionData transactionData) { + super(repository, transactionData); + + this.groupUnbanTransactionData = (GroupUnbanTransactionData) this.transactionData; + } + + // More information + + @Override + public List getRecipientAccounts() throws DataException { + return Collections.emptyList(); + } + + @Override + public boolean isInvolved(Account account) throws DataException { + String address = account.getAddress(); + + if (address.equals(this.getAdmin().getAddress())) + return true; + + if (address.equals(this.getMember().getAddress())) + return true; + + return false; + } + + @Override + public BigDecimal getAmount(Account account) throws DataException { + String address = account.getAddress(); + BigDecimal amount = BigDecimal.ZERO.setScale(8); + + if (address.equals(this.getAdmin().getAddress())) + amount = amount.subtract(this.transactionData.getFee()); + + return amount; + } + + // Navigation + + public Account getAdmin() throws DataException { + return new PublicKeyAccount(this.repository, this.groupUnbanTransactionData.getAdminPublicKey()); + } + + public Account getMember() throws DataException { + return new Account(this.repository, this.groupUnbanTransactionData.getMember()); + } + + // Processing + + @Override + public ValidationResult isValid() throws DataException { + GroupRepository groupRepository = this.repository.getGroupRepository(); + String groupName = groupUnbanTransactionData.getGroupName(); + + // Check member address is valid + if (!Crypto.isValidAddress(groupUnbanTransactionData.getMember())) + return ValidationResult.INVALID_ADDRESS; + + // Check group name size bounds + int groupNameLength = Utf8.encodedLength(groupName); + if (groupNameLength < 1 || groupNameLength > Group.MAX_NAME_SIZE) + return ValidationResult.INVALID_NAME_LENGTH; + + // Check group name is lowercase + if (!groupName.equals(groupName.toLowerCase())) + return ValidationResult.NAME_NOT_LOWER_CASE; + + GroupData groupData = groupRepository.fromGroupName(groupName); + + // Check group exists + if (groupData == null) + return ValidationResult.GROUP_DOES_NOT_EXIST; + + Account admin = getAdmin(); + Account member = getMember(); + + // Can't unban if not an admin + if (!groupRepository.adminExists(groupName, admin.getAddress())) + return ValidationResult.NOT_GROUP_ADMIN; + + // Check ban actually exists + if (!groupRepository.banExists(groupName, member.getAddress())) + return ValidationResult.BAN_UNKNOWN; + + // Check fee is positive + if (groupUnbanTransactionData.getFee().compareTo(BigDecimal.ZERO) <= 0) + return ValidationResult.NEGATIVE_FEE; + + if (!Arrays.equals(admin.getLastReference(), groupUnbanTransactionData.getReference())) + return ValidationResult.INVALID_REFERENCE; + + // Check creator has enough funds + if (admin.getConfirmedBalance(Asset.QORA).compareTo(groupUnbanTransactionData.getFee()) < 0) + return ValidationResult.NO_BALANCE; + + return ValidationResult.OK; + } + + @Override + public void process() throws DataException { + // Update Group Membership + Group group = new Group(this.repository, groupUnbanTransactionData.getGroupName()); + group.cancelBan(groupUnbanTransactionData); + + // Save this transaction with updated member/admin references to transactions that can help restore state + this.repository.getTransactionRepository().save(groupUnbanTransactionData); + + // Update admin's balance + Account admin = getAdmin(); + admin.setConfirmedBalance(Asset.QORA, admin.getConfirmedBalance(Asset.QORA).subtract(groupUnbanTransactionData.getFee())); + + // Update admin's reference + admin.setLastReference(groupUnbanTransactionData.getSignature()); + } + + @Override + public void orphan() throws DataException { + // Revert group membership + Group group = new Group(this.repository, groupUnbanTransactionData.getGroupName()); + group.uncancelBan(groupUnbanTransactionData); + + // Delete this transaction itself + this.repository.getTransactionRepository().delete(groupUnbanTransactionData); + + // Update admin's balance + Account admin = getAdmin(); + admin.setConfirmedBalance(Asset.QORA, admin.getConfirmedBalance(Asset.QORA).add(groupUnbanTransactionData.getFee())); + + // Update admin's reference + admin.setLastReference(groupUnbanTransactionData.getReference()); + } + +} diff --git a/src/main/java/org/qora/transaction/JoinGroupTransaction.java b/src/main/java/org/qora/transaction/JoinGroupTransaction.java index 25dc71fa..204419f6 100644 --- a/src/main/java/org/qora/transaction/JoinGroupTransaction.java +++ b/src/main/java/org/qora/transaction/JoinGroupTransaction.java @@ -88,7 +88,9 @@ public class JoinGroupTransaction extends Transaction { if (this.repository.getGroupRepository().memberExists(joinGroupTransactionData.getGroupName(), joiner.getAddress())) return ValidationResult.ALREADY_GROUP_MEMBER; - // XXX Check member is not banned + // Check member is not banned + if (this.repository.getGroupRepository().banExists(joinGroupTransactionData.getGroupName(), joiner.getAddress())) + return ValidationResult.BANNED_FROM_GROUP; // Check fee is positive if (joinGroupTransactionData.getFee().compareTo(BigDecimal.ZERO) <= 0) diff --git a/src/main/java/org/qora/transaction/Transaction.java b/src/main/java/org/qora/transaction/Transaction.java index 0d383797..ab59f079 100644 --- a/src/main/java/org/qora/transaction/Transaction.java +++ b/src/main/java/org/qora/transaction/Transaction.java @@ -135,6 +135,9 @@ public abstract class Transaction { NOT_GROUP_ADMIN(55), INVALID_LIFETIME(56), INVITE_UNKNOWN(57), + BAN_EXISTS(58), + BAN_UNKNOWN(59), + BANNED_FROM_GROUP(60), NOT_YET_RELEASED(1000); public final int value; @@ -246,6 +249,12 @@ public abstract class Transaction { case REMOVE_GROUP_ADMIN: return new RemoveGroupAdminTransaction(repository, transactionData); + case GROUP_BAN: + return new GroupBanTransaction(repository, transactionData); + + case GROUP_UNBAN: + return new GroupUnbanTransaction(repository, transactionData); + case GROUP_KICK: return new GroupKickTransaction(repository, transactionData); diff --git a/src/main/java/org/qora/transaction/UpdateGroupTransaction.java b/src/main/java/org/qora/transaction/UpdateGroupTransaction.java index a5f5e7d1..3af19fe7 100644 --- a/src/main/java/org/qora/transaction/UpdateGroupTransaction.java +++ b/src/main/java/org/qora/transaction/UpdateGroupTransaction.java @@ -126,7 +126,7 @@ public class UpdateGroupTransaction extends Transaction { public void process() throws DataException { // Update Group Group group = new Group(this.repository, updateGroupTransactionData.getGroupName()); - group.update(updateGroupTransactionData); + group.updateGroup(updateGroupTransactionData); // Save this transaction, now with updated "group reference" to previous transaction that updated group this.repository.getTransactionRepository().save(updateGroupTransactionData); @@ -143,7 +143,7 @@ public class UpdateGroupTransaction extends Transaction { public void orphan() throws DataException { // Revert Group update Group group = new Group(this.repository, updateGroupTransactionData.getGroupName()); - group.revert(updateGroupTransactionData); + group.unupdateGroup(updateGroupTransactionData); // Delete this transaction itself this.repository.getTransactionRepository().delete(updateGroupTransactionData); diff --git a/src/main/java/org/qora/transform/transaction/GroupBanTransactionTransformer.java b/src/main/java/org/qora/transform/transaction/GroupBanTransactionTransformer.java new file mode 100644 index 00000000..82cbdb43 --- /dev/null +++ b/src/main/java/org/qora/transform/transaction/GroupBanTransactionTransformer.java @@ -0,0 +1,115 @@ +package org.qora.transform.transaction; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.math.BigDecimal; +import java.nio.ByteBuffer; + +import org.json.simple.JSONObject; +import org.qora.account.PublicKeyAccount; +import org.qora.data.transaction.GroupBanTransactionData; +import org.qora.data.transaction.TransactionData; +import org.qora.group.Group; +import org.qora.transform.TransformationException; +import org.qora.utils.Serialization; + +import com.google.common.base.Utf8; +import com.google.common.hash.HashCode; +import com.google.common.primitives.Ints; +import com.google.common.primitives.Longs; + +public class GroupBanTransactionTransformer extends TransactionTransformer { + + // Property lengths + private static final int ADMIN_LENGTH = PUBLIC_KEY_LENGTH; + private static final int NAME_SIZE_LENGTH = INT_LENGTH; + private static final int MEMBER_LENGTH = ADDRESS_LENGTH; + private static final int REASON_SIZE_LENGTH = INT_LENGTH; + private static final int TTL_LENGTH = INT_LENGTH; + + private static final int TYPELESS_DATALESS_LENGTH = BASE_TYPELESS_LENGTH + ADMIN_LENGTH + NAME_SIZE_LENGTH + MEMBER_LENGTH + REASON_SIZE_LENGTH + TTL_LENGTH; + + static TransactionData fromByteBuffer(ByteBuffer byteBuffer) throws TransformationException { + long timestamp = byteBuffer.getLong(); + + byte[] reference = new byte[REFERENCE_LENGTH]; + byteBuffer.get(reference); + + byte[] adminPublicKey = Serialization.deserializePublicKey(byteBuffer); + + String groupName = Serialization.deserializeSizedString(byteBuffer, Group.MAX_NAME_SIZE); + + String offender = Serialization.deserializeAddress(byteBuffer); + + String reason = Serialization.deserializeSizedString(byteBuffer, Group.MAX_REASON_SIZE); + + int timeToLive = byteBuffer.getInt(); + + BigDecimal fee = Serialization.deserializeBigDecimal(byteBuffer); + + byte[] signature = new byte[SIGNATURE_LENGTH]; + byteBuffer.get(signature); + + return new GroupBanTransactionData(adminPublicKey, groupName, offender, reason, timeToLive, fee, timestamp, reference, signature); + } + + public static int getDataLength(TransactionData transactionData) throws TransformationException { + GroupBanTransactionData groupBanTransactionData = (GroupBanTransactionData) transactionData; + + int dataLength = TYPE_LENGTH + TYPELESS_DATALESS_LENGTH + Utf8.encodedLength(groupBanTransactionData.getGroupName()) + + Utf8.encodedLength(groupBanTransactionData.getReason()); + + return dataLength; + } + + public static byte[] toBytes(TransactionData transactionData) throws TransformationException { + try { + GroupBanTransactionData groupBanTransactionData = (GroupBanTransactionData) transactionData; + + ByteArrayOutputStream bytes = new ByteArrayOutputStream(); + + bytes.write(Ints.toByteArray(groupBanTransactionData.getType().value)); + bytes.write(Longs.toByteArray(groupBanTransactionData.getTimestamp())); + bytes.write(groupBanTransactionData.getReference()); + + bytes.write(groupBanTransactionData.getCreatorPublicKey()); + Serialization.serializeSizedString(bytes, groupBanTransactionData.getGroupName()); + Serialization.serializeAddress(bytes, groupBanTransactionData.getOffender()); + Serialization.serializeSizedString(bytes, groupBanTransactionData.getReason()); + bytes.write(Ints.toByteArray(groupBanTransactionData.getTimeToLive())); + + Serialization.serializeBigDecimal(bytes, groupBanTransactionData.getFee()); + + if (groupBanTransactionData.getSignature() != null) + bytes.write(groupBanTransactionData.getSignature()); + + return bytes.toByteArray(); + } catch (IOException | ClassCastException e) { + throw new TransformationException(e); + } + } + + @SuppressWarnings("unchecked") + public static JSONObject toJSON(TransactionData transactionData) throws TransformationException { + JSONObject json = TransactionTransformer.getBaseJSON(transactionData); + + try { + GroupBanTransactionData groupBanTransactionData = (GroupBanTransactionData) transactionData; + + byte[] adminPublicKey = groupBanTransactionData.getAdminPublicKey(); + + json.put("admin", PublicKeyAccount.getAddress(adminPublicKey)); + json.put("adminPublicKey", HashCode.fromBytes(adminPublicKey).toString()); + + json.put("groupName", groupBanTransactionData.getGroupName()); + json.put("offender", groupBanTransactionData.getOffender()); + json.put("reason", groupBanTransactionData.getReason()); + json.put("timeToLive", groupBanTransactionData.getTimeToLive()); + } catch (ClassCastException e) { + throw new TransformationException(e); + } + + return json; + } + +} diff --git a/src/main/java/org/qora/transform/transaction/GroupUnbanTransactionTransformer.java b/src/main/java/org/qora/transform/transaction/GroupUnbanTransactionTransformer.java new file mode 100644 index 00000000..4061982b --- /dev/null +++ b/src/main/java/org/qora/transform/transaction/GroupUnbanTransactionTransformer.java @@ -0,0 +1,104 @@ +package org.qora.transform.transaction; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.math.BigDecimal; +import java.nio.ByteBuffer; + +import org.json.simple.JSONObject; +import org.qora.account.PublicKeyAccount; +import org.qora.data.transaction.GroupUnbanTransactionData; +import org.qora.data.transaction.TransactionData; +import org.qora.group.Group; +import org.qora.transform.TransformationException; +import org.qora.utils.Serialization; + +import com.google.common.base.Utf8; +import com.google.common.hash.HashCode; +import com.google.common.primitives.Ints; +import com.google.common.primitives.Longs; + +public class GroupUnbanTransactionTransformer extends TransactionTransformer { + + // Property lengths + private static final int ADMIN_LENGTH = PUBLIC_KEY_LENGTH; + private static final int NAME_SIZE_LENGTH = INT_LENGTH; + private static final int MEMBER_LENGTH = ADDRESS_LENGTH; + + private static final int TYPELESS_DATALESS_LENGTH = BASE_TYPELESS_LENGTH + ADMIN_LENGTH + NAME_SIZE_LENGTH + MEMBER_LENGTH; + + static TransactionData fromByteBuffer(ByteBuffer byteBuffer) throws TransformationException { + long timestamp = byteBuffer.getLong(); + + byte[] reference = new byte[REFERENCE_LENGTH]; + byteBuffer.get(reference); + + byte[] adminPublicKey = Serialization.deserializePublicKey(byteBuffer); + + String groupName = Serialization.deserializeSizedString(byteBuffer, Group.MAX_NAME_SIZE); + + String member = Serialization.deserializeAddress(byteBuffer); + + BigDecimal fee = Serialization.deserializeBigDecimal(byteBuffer); + + byte[] signature = new byte[SIGNATURE_LENGTH]; + byteBuffer.get(signature); + + return new GroupUnbanTransactionData(adminPublicKey, groupName, member, fee, timestamp, reference, signature); + } + + public static int getDataLength(TransactionData transactionData) throws TransformationException { + GroupUnbanTransactionData groupUnbanTransactionData = (GroupUnbanTransactionData) transactionData; + + int dataLength = TYPE_LENGTH + TYPELESS_DATALESS_LENGTH + Utf8.encodedLength(groupUnbanTransactionData.getGroupName()); + + return dataLength; + } + + public static byte[] toBytes(TransactionData transactionData) throws TransformationException { + try { + GroupUnbanTransactionData groupUnbanTransactionData = (GroupUnbanTransactionData) transactionData; + + ByteArrayOutputStream bytes = new ByteArrayOutputStream(); + + bytes.write(Ints.toByteArray(groupUnbanTransactionData.getType().value)); + bytes.write(Longs.toByteArray(groupUnbanTransactionData.getTimestamp())); + bytes.write(groupUnbanTransactionData.getReference()); + + bytes.write(groupUnbanTransactionData.getCreatorPublicKey()); + Serialization.serializeSizedString(bytes, groupUnbanTransactionData.getGroupName()); + Serialization.serializeAddress(bytes, groupUnbanTransactionData.getMember()); + + Serialization.serializeBigDecimal(bytes, groupUnbanTransactionData.getFee()); + + if (groupUnbanTransactionData.getSignature() != null) + bytes.write(groupUnbanTransactionData.getSignature()); + + return bytes.toByteArray(); + } catch (IOException | ClassCastException e) { + throw new TransformationException(e); + } + } + + @SuppressWarnings("unchecked") + public static JSONObject toJSON(TransactionData transactionData) throws TransformationException { + JSONObject json = TransactionTransformer.getBaseJSON(transactionData); + + try { + GroupUnbanTransactionData groupUnbanTransactionData = (GroupUnbanTransactionData) transactionData; + + byte[] adminPublicKey = groupUnbanTransactionData.getAdminPublicKey(); + + json.put("admin", PublicKeyAccount.getAddress(adminPublicKey)); + json.put("adminPublicKey", HashCode.fromBytes(adminPublicKey).toString()); + + json.put("groupName", groupUnbanTransactionData.getGroupName()); + json.put("member", groupUnbanTransactionData.getMember()); + } catch (ClassCastException e) { + throw new TransformationException(e); + } + + return json; + } + +} diff --git a/src/main/java/org/qora/transform/transaction/TransactionTransformer.java b/src/main/java/org/qora/transform/transaction/TransactionTransformer.java index eb5c3018..a66b3e08 100644 --- a/src/main/java/org/qora/transform/transaction/TransactionTransformer.java +++ b/src/main/java/org/qora/transform/transaction/TransactionTransformer.java @@ -106,6 +106,12 @@ public class TransactionTransformer extends Transformer { case REMOVE_GROUP_ADMIN: return RemoveGroupAdminTransactionTransformer.fromByteBuffer(byteBuffer); + case GROUP_BAN: + return GroupBanTransactionTransformer.fromByteBuffer(byteBuffer); + + case GROUP_UNBAN: + return GroupUnbanTransactionTransformer.fromByteBuffer(byteBuffer); + case GROUP_KICK: return GroupKickTransactionTransformer.fromByteBuffer(byteBuffer); @@ -194,6 +200,12 @@ public class TransactionTransformer extends Transformer { case REMOVE_GROUP_ADMIN: return RemoveGroupAdminTransactionTransformer.getDataLength(transactionData); + case GROUP_BAN: + return GroupBanTransactionTransformer.getDataLength(transactionData); + + case GROUP_UNBAN: + return GroupUnbanTransactionTransformer.getDataLength(transactionData); + case GROUP_KICK: return GroupKickTransactionTransformer.getDataLength(transactionData); @@ -279,6 +291,12 @@ public class TransactionTransformer extends Transformer { case REMOVE_GROUP_ADMIN: return RemoveGroupAdminTransactionTransformer.toBytes(transactionData); + case GROUP_BAN: + return GroupBanTransactionTransformer.toBytes(transactionData); + + case GROUP_UNBAN: + return GroupUnbanTransactionTransformer.toBytes(transactionData); + case GROUP_KICK: return GroupKickTransactionTransformer.toBytes(transactionData); @@ -373,6 +391,12 @@ public class TransactionTransformer extends Transformer { case REMOVE_GROUP_ADMIN: return RemoveGroupAdminTransactionTransformer.toBytesForSigningImpl(transactionData); + case GROUP_BAN: + return GroupBanTransactionTransformer.toBytesForSigningImpl(transactionData); + + case GROUP_UNBAN: + return GroupUnbanTransactionTransformer.toBytesForSigningImpl(transactionData); + case GROUP_KICK: return GroupKickTransactionTransformer.toBytesForSigningImpl(transactionData); @@ -479,6 +503,12 @@ public class TransactionTransformer extends Transformer { case REMOVE_GROUP_ADMIN: return RemoveGroupAdminTransactionTransformer.toJSON(transactionData); + case GROUP_BAN: + return GroupBanTransactionTransformer.toJSON(transactionData); + + case GROUP_UNBAN: + return GroupUnbanTransactionTransformer.toJSON(transactionData); + case GROUP_KICK: return GroupKickTransactionTransformer.toJSON(transactionData);