forked from Qortal/qortal
Removed "owner" from CREATE_GROUP and added Unicode homoglyph support.
Group owner now derived from CREATE_GROUP transaction creator's public key. Added 'reduced' group name to GroupData, with corresponding change to DB. Renamed GroupData.getIsOpen() to simply isOpen(). Tidied up CreateGroupTransactionData, adding 'reduced' group name. Renamed getIsOpen() to simply isOpen(). Added code to generated reduced group name when building genesis block. Added Group.MIN_NAME_SIZE of 3. DB tables changed to add reduced_group_name where appropriate, removing owner where necessary. Added GroupRepository.reducedGroupNameExists(String). Fixed up test blockchain configs in src/test/resources/test-chain-v2*.json.
This commit is contained in:
parent
a7b9215ace
commit
f1638aa9d9
@ -35,6 +35,11 @@ public class GroupData {
|
|||||||
@Schema(hidden = true)
|
@Schema(hidden = true)
|
||||||
private int creationGroupId;
|
private int creationGroupId;
|
||||||
|
|
||||||
|
// For internal use
|
||||||
|
@XmlTransient
|
||||||
|
@Schema(hidden = true)
|
||||||
|
private String reducedGroupName;
|
||||||
|
|
||||||
// We abuse GroupData for API purposes by adding this unrelated field. Not always present.
|
// We abuse GroupData for API purposes by adding this unrelated field. Not always present.
|
||||||
private Boolean isAdmin;
|
private Boolean isAdmin;
|
||||||
|
|
||||||
@ -45,10 +50,12 @@ public class GroupData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Constructs new GroupData with nullable groupId and nullable updated [timestamp] */
|
/** Constructs new GroupData with nullable groupId and nullable updated [timestamp] */
|
||||||
public GroupData(Integer groupId, String owner, String name, String description, long created, Long updated, boolean isOpen, ApprovalThreshold approvalThreshold, int minBlockDelay, int maxBlockDelay, byte[] reference, int creationGroupId) {
|
public GroupData(Integer groupId, String owner, String groupName, String description, long created, Long updated,
|
||||||
|
boolean isOpen, ApprovalThreshold approvalThreshold, int minBlockDelay, int maxBlockDelay, byte[] reference,
|
||||||
|
int creationGroupId, String reducedGroupName) {
|
||||||
this.groupId = groupId;
|
this.groupId = groupId;
|
||||||
this.owner = owner;
|
this.owner = owner;
|
||||||
this.groupName = name;
|
this.groupName = groupName;
|
||||||
this.description = description;
|
this.description = description;
|
||||||
this.created = created;
|
this.created = created;
|
||||||
this.updated = updated;
|
this.updated = updated;
|
||||||
@ -58,11 +65,15 @@ public class GroupData {
|
|||||||
this.minimumBlockDelay = minBlockDelay;
|
this.minimumBlockDelay = minBlockDelay;
|
||||||
this.maximumBlockDelay = maxBlockDelay;
|
this.maximumBlockDelay = maxBlockDelay;
|
||||||
this.creationGroupId = creationGroupId;
|
this.creationGroupId = creationGroupId;
|
||||||
|
this.reducedGroupName = reducedGroupName;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Constructs new GroupData with unassigned groupId */
|
/** Constructs new GroupData with unassigned groupId */
|
||||||
public GroupData(String owner, String name, String description, long created, boolean isOpen, ApprovalThreshold approvalThreshold, int minBlockDelay, int maxBlockDelay, byte[] reference, int creationGroupId) {
|
public GroupData(String owner, String groupName, String description, long created, boolean isOpen,
|
||||||
this(null, owner, name, description, created, null, isOpen, approvalThreshold, minBlockDelay, maxBlockDelay, reference, creationGroupId);
|
ApprovalThreshold approvalThreshold, int minBlockDelay, int maxBlockDelay, byte[] reference,
|
||||||
|
int creationGroupId, String reducedGroupName) {
|
||||||
|
this(null, owner, groupName, description, created, null, isOpen, approvalThreshold, minBlockDelay,
|
||||||
|
maxBlockDelay, reference, creationGroupId, reducedGroupName);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Getters / setters
|
// Getters / setters
|
||||||
@ -115,7 +126,7 @@ public class GroupData {
|
|||||||
this.reference = reference;
|
this.reference = reference;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean getIsOpen() {
|
public boolean isOpen() {
|
||||||
return this.isOpen;
|
return this.isOpen;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -143,6 +154,14 @@ public class GroupData {
|
|||||||
return this.creationGroupId;
|
return this.creationGroupId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getReducedGroupName() {
|
||||||
|
return this.reducedGroupName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setReducedGroupName(String reducedGroupName) {
|
||||||
|
this.reducedGroupName = reducedGroupName;
|
||||||
|
}
|
||||||
|
|
||||||
// This is for API call GET /groups/member/{address}
|
// This is for API call GET /groups/member/{address}
|
||||||
|
|
||||||
public Boolean isAdmin() {
|
public Boolean isAdmin() {
|
||||||
|
@ -1,10 +1,14 @@
|
|||||||
package org.qortal.data.transaction;
|
package org.qortal.data.transaction;
|
||||||
|
|
||||||
|
import javax.xml.bind.Unmarshaller;
|
||||||
import javax.xml.bind.annotation.XmlAccessType;
|
import javax.xml.bind.annotation.XmlAccessType;
|
||||||
import javax.xml.bind.annotation.XmlAccessorType;
|
import javax.xml.bind.annotation.XmlAccessorType;
|
||||||
import javax.xml.bind.annotation.XmlElement;
|
import javax.xml.bind.annotation.XmlElement;
|
||||||
|
import javax.xml.bind.annotation.XmlTransient;
|
||||||
|
|
||||||
import org.eclipse.persistence.oxm.annotations.XmlDiscriminatorValue;
|
import org.eclipse.persistence.oxm.annotations.XmlDiscriminatorValue;
|
||||||
|
import org.qortal.block.GenesisBlock;
|
||||||
|
import org.qortal.group.Group;
|
||||||
import org.qortal.group.Group.ApprovalThreshold;
|
import org.qortal.group.Group.ApprovalThreshold;
|
||||||
import org.qortal.transaction.Transaction.TransactionType;
|
import org.qortal.transaction.Transaction.TransactionType;
|
||||||
|
|
||||||
@ -24,40 +28,32 @@ public class CreateGroupTransactionData extends TransactionData {
|
|||||||
|
|
||||||
// Properties
|
// Properties
|
||||||
// groupId can be null but assigned during save() or during load from repository
|
// groupId can be null but assigned during save() or during load from repository
|
||||||
@Schema(
|
@Schema(accessMode = AccessMode.READ_ONLY, description = "assigned group ID")
|
||||||
accessMode = AccessMode.READ_ONLY,
|
|
||||||
description = "assigned group ID"
|
|
||||||
)
|
|
||||||
private Integer groupId = null;
|
private Integer groupId = null;
|
||||||
@Schema(
|
|
||||||
description = "group owner's address",
|
@Schema(description = "group name", example = "miner-group")
|
||||||
example = "QgV4s3xnzLhVBEJxcYui4u4q11yhUHsd9v"
|
|
||||||
)
|
|
||||||
private String owner;
|
|
||||||
@Schema(
|
|
||||||
description = "group name",
|
|
||||||
example = "miner-group"
|
|
||||||
)
|
|
||||||
private String groupName;
|
private String groupName;
|
||||||
@Schema(
|
|
||||||
description = "short description of group",
|
@Schema(description = "short description of group", example = "this group is for block miners")
|
||||||
example = "this group is for block miners"
|
|
||||||
)
|
|
||||||
private String description;
|
private String description;
|
||||||
@Schema(
|
|
||||||
description = "whether anyone can join group (open) or group is invite-only (closed)",
|
@Schema(description = "whether anyone can join group (open) or group is invite-only (closed)", example = "true")
|
||||||
example = "true"
|
|
||||||
)
|
|
||||||
private boolean isOpen;
|
private boolean isOpen;
|
||||||
@Schema(
|
|
||||||
description = "how many group admins are required to approve group member transactions"
|
@Schema(description = "how many group admins are required to approve group member transactions")
|
||||||
)
|
|
||||||
private ApprovalThreshold approvalThreshold;
|
private ApprovalThreshold approvalThreshold;
|
||||||
|
|
||||||
@Schema(description = "minimum block delay before approval takes effect")
|
@Schema(description = "minimum block delay before approval takes effect")
|
||||||
private int minimumBlockDelay;
|
private int minimumBlockDelay;
|
||||||
|
|
||||||
@Schema(description = "maximum block delay before which transaction approval must be reached")
|
@Schema(description = "maximum block delay before which transaction approval must be reached")
|
||||||
private int maximumBlockDelay;
|
private int maximumBlockDelay;
|
||||||
|
|
||||||
|
// For internal use
|
||||||
|
@XmlTransient
|
||||||
|
@Schema(hidden = true)
|
||||||
|
private String reducedGroupName;
|
||||||
|
|
||||||
// Constructors
|
// Constructors
|
||||||
|
|
||||||
// For JAXB
|
// For JAXB
|
||||||
@ -65,13 +61,22 @@ public class CreateGroupTransactionData extends TransactionData {
|
|||||||
super(TransactionType.CREATE_GROUP);
|
super(TransactionType.CREATE_GROUP);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void afterUnmarshal(Unmarshaller u, Object parent) {
|
||||||
|
/*
|
||||||
|
* If we're being constructed as part of the genesis block info inside blockchain config
|
||||||
|
* then we need to construct 'reduced' group name.
|
||||||
|
*/
|
||||||
|
if (parent instanceof GenesisBlock.GenesisInfo && this.reducedGroupName == null)
|
||||||
|
this.reducedGroupName = Group.reduceName(this.groupName);
|
||||||
|
}
|
||||||
|
|
||||||
/** From repository */
|
/** From repository */
|
||||||
public CreateGroupTransactionData(BaseTransactionData baseTransactionData,
|
public CreateGroupTransactionData(BaseTransactionData baseTransactionData,
|
||||||
String owner, String groupName, String description, boolean isOpen,
|
String groupName, String description, boolean isOpen,
|
||||||
ApprovalThreshold approvalThreshold, int minimumBlockDelay, int maximumBlockDelay, Integer groupId) {
|
ApprovalThreshold approvalThreshold, int minimumBlockDelay, int maximumBlockDelay,
|
||||||
|
Integer groupId, String reducedGroupName) {
|
||||||
super(TransactionType.CREATE_GROUP, baseTransactionData);
|
super(TransactionType.CREATE_GROUP, baseTransactionData);
|
||||||
|
|
||||||
this.owner = owner;
|
|
||||||
this.groupName = groupName;
|
this.groupName = groupName;
|
||||||
this.description = description;
|
this.description = description;
|
||||||
this.isOpen = isOpen;
|
this.isOpen = isOpen;
|
||||||
@ -79,21 +84,18 @@ public class CreateGroupTransactionData extends TransactionData {
|
|||||||
this.minimumBlockDelay = minimumBlockDelay;
|
this.minimumBlockDelay = minimumBlockDelay;
|
||||||
this.maximumBlockDelay = maximumBlockDelay;
|
this.maximumBlockDelay = maximumBlockDelay;
|
||||||
this.groupId = groupId;
|
this.groupId = groupId;
|
||||||
|
this.reducedGroupName = reducedGroupName;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** From network/API */
|
/** From network/API */
|
||||||
public CreateGroupTransactionData(BaseTransactionData baseTransactionData,
|
public CreateGroupTransactionData(BaseTransactionData baseTransactionData,
|
||||||
String owner, String groupName, String description, boolean isOpen,
|
String groupName, String description, boolean isOpen,
|
||||||
ApprovalThreshold approvalThreshold, int minimumBlockDelay, int maximumBlockDelay) {
|
ApprovalThreshold approvalThreshold, int minimumBlockDelay, int maximumBlockDelay) {
|
||||||
this(baseTransactionData, owner, groupName, description, isOpen, approvalThreshold, minimumBlockDelay, maximumBlockDelay, null);
|
this(baseTransactionData, groupName, description, isOpen, approvalThreshold, minimumBlockDelay, maximumBlockDelay, null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Getters / setters
|
// Getters / setters
|
||||||
|
|
||||||
public String getOwner() {
|
|
||||||
return this.owner;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getGroupName() {
|
public String getGroupName() {
|
||||||
return this.groupName;
|
return this.groupName;
|
||||||
}
|
}
|
||||||
@ -102,7 +104,7 @@ public class CreateGroupTransactionData extends TransactionData {
|
|||||||
return this.description;
|
return this.description;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean getIsOpen() {
|
public boolean isOpen() {
|
||||||
return this.isOpen;
|
return this.isOpen;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -126,27 +128,23 @@ public class CreateGroupTransactionData extends TransactionData {
|
|||||||
this.groupId = groupId;
|
this.groupId = groupId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getReducedGroupName() {
|
||||||
|
return this.reducedGroupName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setReducedGroupName(String reducedGroupName) {
|
||||||
|
this.reducedGroupName = reducedGroupName;
|
||||||
|
}
|
||||||
|
|
||||||
// Re-expose creatorPublicKey for this transaction type for JAXB
|
// Re-expose creatorPublicKey for this transaction type for JAXB
|
||||||
@XmlElement(
|
@XmlElement(name = "creatorPublicKey")
|
||||||
name = "creatorPublicKey"
|
@Schema(name = "creatorPublicKey", description = "group creator's public key", example = "2tiMr5LTpaWCgbRvkPK8TFd7k63DyHJMMFFsz9uBf1ZP")
|
||||||
)
|
|
||||||
@Schema(
|
|
||||||
name = "creatorPublicKey",
|
|
||||||
description = "group creator's public key",
|
|
||||||
example = "2tiMr5LTpaWCgbRvkPK8TFd7k63DyHJMMFFsz9uBf1ZP"
|
|
||||||
)
|
|
||||||
public byte[] getGroupCreatorPublicKey() {
|
public byte[] getGroupCreatorPublicKey() {
|
||||||
return this.creatorPublicKey;
|
return this.creatorPublicKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
@XmlElement(
|
@XmlElement(name = "creatorPublicKey")
|
||||||
name = "creatorPublicKey"
|
@Schema(name = "creatorPublicKey", description = "group creator's public key", example = "2tiMr5LTpaWCgbRvkPK8TFd7k63DyHJMMFFsz9uBf1ZP")
|
||||||
)
|
|
||||||
@Schema(
|
|
||||||
name = "creatorPublicKey",
|
|
||||||
description = "group creator's public key",
|
|
||||||
example = "2tiMr5LTpaWCgbRvkPK8TFd7k63DyHJMMFFsz9uBf1ZP"
|
|
||||||
)
|
|
||||||
public void setGroupCreatorPublicKey(byte[] creatorPublicKey) {
|
public void setGroupCreatorPublicKey(byte[] creatorPublicKey) {
|
||||||
this.creatorPublicKey = creatorPublicKey;
|
this.creatorPublicKey = creatorPublicKey;
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,7 @@ import java.util.Map;
|
|||||||
|
|
||||||
import org.qortal.account.Account;
|
import org.qortal.account.Account;
|
||||||
import org.qortal.account.PublicKeyAccount;
|
import org.qortal.account.PublicKeyAccount;
|
||||||
|
import org.qortal.crypto.Crypto;
|
||||||
import org.qortal.data.group.GroupAdminData;
|
import org.qortal.data.group.GroupAdminData;
|
||||||
import org.qortal.data.group.GroupBanData;
|
import org.qortal.data.group.GroupBanData;
|
||||||
import org.qortal.data.group.GroupData;
|
import org.qortal.data.group.GroupData;
|
||||||
@ -29,6 +30,7 @@ import org.qortal.data.transaction.UpdateGroupTransactionData;
|
|||||||
import org.qortal.repository.DataException;
|
import org.qortal.repository.DataException;
|
||||||
import org.qortal.repository.GroupRepository;
|
import org.qortal.repository.GroupRepository;
|
||||||
import org.qortal.repository.Repository;
|
import org.qortal.repository.Repository;
|
||||||
|
import org.qortal.utils.Unicode;
|
||||||
|
|
||||||
public class Group {
|
public class Group {
|
||||||
|
|
||||||
@ -78,6 +80,7 @@ public class Group {
|
|||||||
// Useful constants
|
// Useful constants
|
||||||
public static final int NO_GROUP = 0;
|
public static final int NO_GROUP = 0;
|
||||||
|
|
||||||
|
public static final int MIN_NAME_SIZE = 3;
|
||||||
public static final int MAX_NAME_SIZE = 32;
|
public static final int MAX_NAME_SIZE = 32;
|
||||||
public static final int MAX_DESCRIPTION_SIZE = 128;
|
public static final int MAX_DESCRIPTION_SIZE = 128;
|
||||||
/** Max size of kick/ban reason */
|
/** Max size of kick/ban reason */
|
||||||
@ -95,10 +98,14 @@ public class Group {
|
|||||||
this.repository = repository;
|
this.repository = repository;
|
||||||
this.groupRepository = repository.getGroupRepository();
|
this.groupRepository = repository.getGroupRepository();
|
||||||
|
|
||||||
this.groupData = new GroupData(createGroupTransactionData.getOwner(), createGroupTransactionData.getGroupName(),
|
String owner = Crypto.toAddress(createGroupTransactionData.getCreatorPublicKey());
|
||||||
createGroupTransactionData.getDescription(), createGroupTransactionData.getTimestamp(), createGroupTransactionData.getIsOpen(),
|
|
||||||
createGroupTransactionData.getApprovalThreshold(), createGroupTransactionData.getMinimumBlockDelay(),
|
this.groupData = new GroupData(owner, createGroupTransactionData.getGroupName(),
|
||||||
createGroupTransactionData.getMaximumBlockDelay(), createGroupTransactionData.getSignature(), createGroupTransactionData.getTxGroupId());
|
createGroupTransactionData.getDescription(), createGroupTransactionData.getTimestamp(),
|
||||||
|
createGroupTransactionData.isOpen(), createGroupTransactionData.getApprovalThreshold(),
|
||||||
|
createGroupTransactionData.getMinimumBlockDelay(), createGroupTransactionData.getMaximumBlockDelay(),
|
||||||
|
createGroupTransactionData.getSignature(), createGroupTransactionData.getTxGroupId(),
|
||||||
|
createGroupTransactionData.getReducedGroupName());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -123,6 +130,10 @@ public class Group {
|
|||||||
|
|
||||||
// Shortcuts to aid code clarity
|
// Shortcuts to aid code clarity
|
||||||
|
|
||||||
|
public static String reduceName(String name) {
|
||||||
|
return Unicode.sanitize(name);
|
||||||
|
}
|
||||||
|
|
||||||
// Membership
|
// Membership
|
||||||
|
|
||||||
private GroupMemberData getMember(String member) throws DataException {
|
private GroupMemberData getMember(String member) throws DataException {
|
||||||
@ -355,16 +366,20 @@ public class Group {
|
|||||||
throw new DataException("Unable to revert group transaction as referenced transaction not found in repository");
|
throw new DataException("Unable to revert group transaction as referenced transaction not found in repository");
|
||||||
|
|
||||||
switch (previousTransactionData.getType()) {
|
switch (previousTransactionData.getType()) {
|
||||||
case CREATE_GROUP:
|
case CREATE_GROUP: {
|
||||||
CreateGroupTransactionData previousCreateGroupTransactionData = (CreateGroupTransactionData) previousTransactionData;
|
CreateGroupTransactionData previousCreateGroupTransactionData = (CreateGroupTransactionData) previousTransactionData;
|
||||||
this.groupData.setOwner(previousCreateGroupTransactionData.getOwner());
|
|
||||||
|
String owner = Crypto.toAddress(previousCreateGroupTransactionData.getCreatorPublicKey());
|
||||||
|
|
||||||
|
this.groupData.setOwner(owner);
|
||||||
this.groupData.setDescription(previousCreateGroupTransactionData.getDescription());
|
this.groupData.setDescription(previousCreateGroupTransactionData.getDescription());
|
||||||
this.groupData.setIsOpen(previousCreateGroupTransactionData.getIsOpen());
|
this.groupData.setIsOpen(previousCreateGroupTransactionData.isOpen());
|
||||||
this.groupData.setApprovalThreshold(previousCreateGroupTransactionData.getApprovalThreshold());
|
this.groupData.setApprovalThreshold(previousCreateGroupTransactionData.getApprovalThreshold());
|
||||||
this.groupData.setUpdated(null);
|
this.groupData.setUpdated(null);
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case UPDATE_GROUP:
|
case UPDATE_GROUP: {
|
||||||
UpdateGroupTransactionData previousUpdateGroupTransactionData = (UpdateGroupTransactionData) previousTransactionData;
|
UpdateGroupTransactionData previousUpdateGroupTransactionData = (UpdateGroupTransactionData) previousTransactionData;
|
||||||
this.groupData.setOwner(previousUpdateGroupTransactionData.getNewOwner());
|
this.groupData.setOwner(previousUpdateGroupTransactionData.getNewOwner());
|
||||||
this.groupData.setDescription(previousUpdateGroupTransactionData.getNewDescription());
|
this.groupData.setDescription(previousUpdateGroupTransactionData.getNewDescription());
|
||||||
@ -372,6 +387,7 @@ public class Group {
|
|||||||
this.groupData.setApprovalThreshold(previousUpdateGroupTransactionData.getNewApprovalThreshold());
|
this.groupData.setApprovalThreshold(previousUpdateGroupTransactionData.getNewApprovalThreshold());
|
||||||
this.groupData.setUpdated(previousUpdateGroupTransactionData.getTimestamp());
|
this.groupData.setUpdated(previousUpdateGroupTransactionData.getTimestamp());
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw new IllegalStateException("Unable to revert group transaction due to unsupported referenced transaction");
|
throw new IllegalStateException("Unable to revert group transaction due to unsupported referenced transaction");
|
||||||
@ -722,7 +738,7 @@ public class Group {
|
|||||||
|
|
||||||
// If there is no invites and this group is "closed" (i.e. invite-only) then
|
// If there is no invites and this group is "closed" (i.e. invite-only) then
|
||||||
// this is now a pending "join request"
|
// this is now a pending "join request"
|
||||||
if (groupInviteData == null && !groupData.getIsOpen()) {
|
if (groupInviteData == null && !groupData.isOpen()) {
|
||||||
// Save join request
|
// Save join request
|
||||||
this.addJoinRequest(joiner.getAddress(), joinGroupTransactionData.getSignature());
|
this.addJoinRequest(joiner.getAddress(), joinGroupTransactionData.getSignature());
|
||||||
|
|
||||||
@ -761,7 +777,7 @@ public class Group {
|
|||||||
byte[] inviteReference = joinGroupTransactionData.getInviteReference();
|
byte[] inviteReference = joinGroupTransactionData.getInviteReference();
|
||||||
|
|
||||||
// Was this a join-request only?
|
// Was this a join-request only?
|
||||||
if (inviteReference == null && !groupData.getIsOpen()) {
|
if (inviteReference == null && !groupData.isOpen()) {
|
||||||
// Delete join request
|
// Delete join request
|
||||||
this.deleteJoinRequest(joiner.getAddress());
|
this.deleteJoinRequest(joiner.getAddress());
|
||||||
} else {
|
} else {
|
||||||
|
@ -21,6 +21,8 @@ public interface GroupRepository {
|
|||||||
|
|
||||||
public boolean groupExists(String groupName) throws DataException;
|
public boolean groupExists(String groupName) throws DataException;
|
||||||
|
|
||||||
|
public boolean reducedGroupNameExists(String reducedGroupName) throws DataException;
|
||||||
|
|
||||||
public List<GroupData> getAllGroups(Integer limit, Integer offset, Boolean reverse) throws DataException;
|
public List<GroupData> getAllGroups(Integer limit, Integer offset, Boolean reverse) throws DataException;
|
||||||
|
|
||||||
public default List<GroupData> getAllGroups() throws DataException {
|
public default List<GroupData> getAllGroups() throws DataException {
|
||||||
|
@ -444,12 +444,15 @@ public class HSQLDBDatabaseUpdates {
|
|||||||
|
|
||||||
case 12:
|
case 12:
|
||||||
// Groups
|
// Groups
|
||||||
stmt.execute("CREATE TABLE Groups (group_id GroupID, owner QortalAddress NOT NULL, group_name GroupName, "
|
stmt.execute("CREATE TABLE Groups (group_id GroupID, owner QortalAddress NOT NULL, group_name GroupName NOT NULL, "
|
||||||
+ "created_when EpochMillis NOT NULL, updated_when EpochMillis, is_open BOOLEAN NOT NULL, "
|
+ "created_when EpochMillis NOT NULL, updated_when EpochMillis, is_open BOOLEAN NOT NULL, "
|
||||||
+ "approval_threshold TINYINT NOT NULL, min_block_delay INTEGER NOT NULL, max_block_delay INTEGER NOT NULL, "
|
+ "approval_threshold TINYINT NOT NULL, min_block_delay INTEGER NOT NULL, max_block_delay INTEGER NOT NULL, "
|
||||||
+ "reference Signature, creation_group_id GroupID, description GenericDescription NOT NULL, PRIMARY KEY (group_id))");
|
+ "reference Signature, creation_group_id GroupID, reduced_group_name GroupName NOT NULL, "
|
||||||
// For when a user wants to lookup an group by name
|
+ "description GenericDescription NOT NULL, PRIMARY KEY (group_id))");
|
||||||
|
// For finding groups by name
|
||||||
stmt.execute("CREATE INDEX GroupNameIndex on Groups (group_name)");
|
stmt.execute("CREATE INDEX GroupNameIndex on Groups (group_name)");
|
||||||
|
// For finding groups by reduced name
|
||||||
|
stmt.execute("CREATE INDEX GroupReducedNameIndex on Groups (reduced_group_name)");
|
||||||
// For finding groups by owner
|
// For finding groups by owner
|
||||||
stmt.execute("CREATE INDEX GroupOwnerIndex ON Groups (owner)");
|
stmt.execute("CREATE INDEX GroupOwnerIndex ON Groups (owner)");
|
||||||
|
|
||||||
@ -499,7 +502,7 @@ public class HSQLDBDatabaseUpdates {
|
|||||||
// Group transactions
|
// Group transactions
|
||||||
// Create group
|
// Create group
|
||||||
stmt.execute("CREATE TABLE CreateGroupTransactions (signature Signature, creator QortalPublicKey NOT NULL, group_name GroupName NOT NULL, "
|
stmt.execute("CREATE TABLE CreateGroupTransactions (signature Signature, creator QortalPublicKey NOT NULL, group_name GroupName NOT NULL, "
|
||||||
+ "owner QortalAddress NOT NULL, is_open BOOLEAN NOT NULL, approval_threshold TINYINT NOT NULL, "
|
+ "is_open BOOLEAN NOT NULL, approval_threshold TINYINT NOT NULL, reduced_group_name GroupName NOT NULL, "
|
||||||
+ "min_block_delay INTEGER NOT NULL, max_block_delay INTEGER NOT NULL, group_id GroupID, description GenericDescription NOT NULL, "
|
+ "min_block_delay INTEGER NOT NULL, max_block_delay INTEGER NOT NULL, group_id GroupID, description GenericDescription NOT NULL, "
|
||||||
+ TRANSACTION_KEYS + ")");
|
+ TRANSACTION_KEYS + ")");
|
||||||
|
|
||||||
|
@ -28,7 +28,8 @@ public class HSQLDBGroupRepository implements GroupRepository {
|
|||||||
@Override
|
@Override
|
||||||
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 FROM Groups WHERE group_id = ?";
|
+ "approval_threshold, min_block_delay, max_block_delay, creation_group_id, reduced_group_name "
|
||||||
|
+ "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)
|
||||||
@ -53,9 +54,10 @@ public class HSQLDBGroupRepository implements GroupRepository {
|
|||||||
int maxBlockDelay = resultSet.getInt(10);
|
int maxBlockDelay = resultSet.getInt(10);
|
||||||
|
|
||||||
int creationGroupId = resultSet.getInt(11);
|
int creationGroupId = resultSet.getInt(11);
|
||||||
|
String reducedGroupName = resultSet.getString(12);
|
||||||
|
|
||||||
return new GroupData(groupId, owner, groupName, description, created, updated, isOpen,
|
return new GroupData(groupId, owner, groupName, description, created, updated, isOpen,
|
||||||
approvalThreshold, minBlockDelay, maxBlockDelay, reference, creationGroupId);
|
approvalThreshold, minBlockDelay, maxBlockDelay, reference, creationGroupId, reducedGroupName);
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
throw new DataException("Unable to fetch group info from repository", e);
|
throw new DataException("Unable to fetch group info from repository", e);
|
||||||
}
|
}
|
||||||
@ -64,7 +66,8 @@ public class HSQLDBGroupRepository implements GroupRepository {
|
|||||||
@Override
|
@Override
|
||||||
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 FROM Groups WHERE group_name = ?";
|
+ "approval_threshold, min_block_delay, max_block_delay, creation_group_id, reduced_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)
|
||||||
@ -89,9 +92,10 @@ public class HSQLDBGroupRepository implements GroupRepository {
|
|||||||
int maxBlockDelay = resultSet.getInt(10);
|
int maxBlockDelay = resultSet.getInt(10);
|
||||||
|
|
||||||
int creationGroupId = resultSet.getInt(11);
|
int creationGroupId = resultSet.getInt(11);
|
||||||
|
String reducedGroupName = resultSet.getString(12);
|
||||||
|
|
||||||
return new GroupData(groupId, owner, groupName, description, created, updated, isOpen,
|
return new GroupData(groupId, owner, groupName, description, created, updated, isOpen,
|
||||||
approvalThreshold, minBlockDelay, maxBlockDelay, reference, creationGroupId);
|
approvalThreshold, minBlockDelay, maxBlockDelay, reference, creationGroupId, reducedGroupName);
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
throw new DataException("Unable to fetch group info from repository", e);
|
throw new DataException("Unable to fetch group info from repository", e);
|
||||||
}
|
}
|
||||||
@ -115,12 +119,22 @@ public class HSQLDBGroupRepository implements GroupRepository {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean reducedGroupNameExists(String reducedGroupName) throws DataException {
|
||||||
|
try {
|
||||||
|
return this.repository.exists("Groups", "reduced_group_name = ?", reducedGroupName);
|
||||||
|
} catch (SQLException e) {
|
||||||
|
throw new DataException("Unable to check for reduced group name in repository", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<GroupData> getAllGroups(Integer limit, Integer offset, Boolean reverse) throws DataException {
|
public List<GroupData> getAllGroups(Integer limit, Integer offset, Boolean reverse) throws DataException {
|
||||||
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 FROM Groups ORDER BY group_name");
|
+ "approval_threshold, min_block_delay, max_block_delay, creation_group_id, reduced_group_name "
|
||||||
|
+ "FROM Groups ORDER BY group_name");
|
||||||
|
|
||||||
if (reverse != null && reverse)
|
if (reverse != null && reverse)
|
||||||
sql.append(" DESC");
|
sql.append(" DESC");
|
||||||
@ -154,9 +168,10 @@ public class HSQLDBGroupRepository implements GroupRepository {
|
|||||||
int maxBlockDelay = resultSet.getInt(11);
|
int maxBlockDelay = resultSet.getInt(11);
|
||||||
|
|
||||||
int creationGroupId = resultSet.getInt(12);
|
int creationGroupId = resultSet.getInt(12);
|
||||||
|
String reducedGroupName = resultSet.getString(13);
|
||||||
|
|
||||||
groups.add(new GroupData(groupId, owner, groupName, description, created, updated, isOpen,
|
groups.add(new GroupData(groupId, owner, groupName, description, created, updated, isOpen,
|
||||||
approvalThreshold, minBlockDelay, maxBlockDelay, reference, creationGroupId));
|
approvalThreshold, minBlockDelay, maxBlockDelay, reference, creationGroupId, reducedGroupName));
|
||||||
} while (resultSet.next());
|
} while (resultSet.next());
|
||||||
|
|
||||||
return groups;
|
return groups;
|
||||||
@ -170,7 +185,8 @@ public class HSQLDBGroupRepository implements GroupRepository {
|
|||||||
StringBuilder sql = new StringBuilder(512);
|
StringBuilder sql = new StringBuilder(512);
|
||||||
|
|
||||||
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 FROM Groups WHERE owner = ? ORDER BY group_name");
|
+ "approval_threshold, min_block_delay, max_block_delay, creation_group_id, reduced_group_name "
|
||||||
|
+ "FROM Groups WHERE owner = ? ORDER BY group_name");
|
||||||
|
|
||||||
if (reverse != null && reverse)
|
if (reverse != null && reverse)
|
||||||
sql.append(" DESC");
|
sql.append(" DESC");
|
||||||
@ -203,9 +219,10 @@ public class HSQLDBGroupRepository implements GroupRepository {
|
|||||||
int maxBlockDelay = resultSet.getInt(10);
|
int maxBlockDelay = resultSet.getInt(10);
|
||||||
|
|
||||||
int creationGroupId = resultSet.getInt(11);
|
int creationGroupId = resultSet.getInt(11);
|
||||||
|
String reducedGroupName = resultSet.getString(12);
|
||||||
|
|
||||||
groups.add(new GroupData(groupId, owner, groupName, description, created, updated, isOpen,
|
groups.add(new GroupData(groupId, owner, groupName, description, created, updated, isOpen,
|
||||||
approvalThreshold, minBlockDelay, maxBlockDelay, reference, creationGroupId));
|
approvalThreshold, minBlockDelay, maxBlockDelay, reference, creationGroupId, reducedGroupName));
|
||||||
} while (resultSet.next());
|
} while (resultSet.next());
|
||||||
|
|
||||||
return groups;
|
return groups;
|
||||||
@ -219,7 +236,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, 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");
|
||||||
@ -256,12 +273,13 @@ public class HSQLDBGroupRepository implements GroupRepository {
|
|||||||
int maxBlockDelay = resultSet.getInt(11);
|
int maxBlockDelay = resultSet.getInt(11);
|
||||||
|
|
||||||
int creationGroupId = resultSet.getInt(12);
|
int creationGroupId = resultSet.getInt(12);
|
||||||
|
String reducedGroupName = resultSet.getString(13);
|
||||||
|
|
||||||
resultSet.getString(13); // 'admin'
|
resultSet.getString(14); // 'admin'
|
||||||
boolean isAdmin = !resultSet.wasNull();
|
boolean isAdmin = !resultSet.wasNull();
|
||||||
|
|
||||||
GroupData groupData = new GroupData(groupId, owner, groupName, description, created, updated, isOpen,
|
GroupData groupData = new GroupData(groupId, owner, groupName, description, created, updated, isOpen,
|
||||||
approvalThreshold, minBlockDelay, maxBlockDelay, reference, creationGroupId);
|
approvalThreshold, minBlockDelay, maxBlockDelay, reference, creationGroupId, reducedGroupName);
|
||||||
|
|
||||||
groupData.setIsAdmin(isAdmin);
|
groupData.setIsAdmin(isAdmin);
|
||||||
|
|
||||||
@ -280,9 +298,9 @@ public class HSQLDBGroupRepository implements GroupRepository {
|
|||||||
|
|
||||||
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())
|
||||||
.bind("reference", groupData.getReference()).bind("is_open", groupData.getIsOpen()).bind("approval_threshold", groupData.getApprovalThreshold().value)
|
.bind("reference", groupData.getReference()).bind("is_open", groupData.isOpen()).bind("approval_threshold", groupData.getApprovalThreshold().value)
|
||||||
.bind("min_block_delay", groupData.getMinimumBlockDelay()).bind("max_block_delay", groupData.getMaximumBlockDelay())
|
.bind("min_block_delay", groupData.getMinimumBlockDelay()).bind("max_block_delay", groupData.getMaximumBlockDelay())
|
||||||
.bind("creation_group_id", groupData.getCreationGroupId());
|
.bind("creation_group_id", groupData.getCreationGroupId()).bind("reduced_group_name", groupData.getReducedGroupName());
|
||||||
|
|
||||||
try {
|
try {
|
||||||
saveHelper.execute(this.repository);
|
saveHelper.execute(this.repository);
|
||||||
|
@ -18,28 +18,30 @@ public class HSQLDBCreateGroupTransactionRepository extends HSQLDBTransactionRep
|
|||||||
}
|
}
|
||||||
|
|
||||||
TransactionData fromBase(BaseTransactionData baseTransactionData) throws DataException {
|
TransactionData fromBase(BaseTransactionData baseTransactionData) throws DataException {
|
||||||
String sql = "SELECT owner, group_name, description, is_open, approval_threshold, min_block_delay, max_block_delay, group_id FROM CreateGroupTransactions WHERE signature = ?";
|
String sql = "SELECT group_name, description, is_open, approval_threshold, min_block_delay, max_block_delay, group_id, reduced_group_name "
|
||||||
|
+ "FROM CreateGroupTransactions WHERE signature = ?";
|
||||||
|
|
||||||
try (ResultSet resultSet = this.repository.checkedExecute(sql, baseTransactionData.getSignature())) {
|
try (ResultSet resultSet = this.repository.checkedExecute(sql, baseTransactionData.getSignature())) {
|
||||||
if (resultSet == null)
|
if (resultSet == null)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
String owner = resultSet.getString(1);
|
String groupName = resultSet.getString(1);
|
||||||
String groupName = resultSet.getString(2);
|
String description = resultSet.getString(2);
|
||||||
String description = resultSet.getString(3);
|
boolean isOpen = resultSet.getBoolean(3);
|
||||||
boolean isOpen = resultSet.getBoolean(4);
|
|
||||||
|
|
||||||
ApprovalThreshold approvalThreshold = ApprovalThreshold.valueOf(resultSet.getInt(5));
|
ApprovalThreshold approvalThreshold = ApprovalThreshold.valueOf(resultSet.getInt(4));
|
||||||
|
|
||||||
int minBlockDelay = resultSet.getInt(6);
|
int minBlockDelay = resultSet.getInt(5);
|
||||||
int maxBlockDelay = resultSet.getInt(7);
|
int maxBlockDelay = resultSet.getInt(6);
|
||||||
|
|
||||||
Integer groupId = resultSet.getInt(8);
|
Integer groupId = resultSet.getInt(7);
|
||||||
if (groupId == 0 && resultSet.wasNull())
|
if (groupId == 0 && resultSet.wasNull())
|
||||||
groupId = null;
|
groupId = null;
|
||||||
|
|
||||||
return new CreateGroupTransactionData(baseTransactionData, owner, groupName, description, isOpen, approvalThreshold,
|
String reducedGroupName = resultSet.getString(8);
|
||||||
minBlockDelay, maxBlockDelay, groupId);
|
|
||||||
|
return new CreateGroupTransactionData(baseTransactionData, groupName, description, isOpen, approvalThreshold,
|
||||||
|
minBlockDelay, maxBlockDelay, groupId, reducedGroupName);
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
throw new DataException("Unable to fetch create group transaction from repository", e);
|
throw new DataException("Unable to fetch create group transaction from repository", e);
|
||||||
}
|
}
|
||||||
@ -52,8 +54,8 @@ public class HSQLDBCreateGroupTransactionRepository extends HSQLDBTransactionRep
|
|||||||
HSQLDBSaver saveHelper = new HSQLDBSaver("CreateGroupTransactions");
|
HSQLDBSaver saveHelper = new HSQLDBSaver("CreateGroupTransactions");
|
||||||
|
|
||||||
saveHelper.bind("signature", createGroupTransactionData.getSignature()).bind("creator", createGroupTransactionData.getCreatorPublicKey())
|
saveHelper.bind("signature", createGroupTransactionData.getSignature()).bind("creator", createGroupTransactionData.getCreatorPublicKey())
|
||||||
.bind("owner", createGroupTransactionData.getOwner()).bind("group_name", createGroupTransactionData.getGroupName())
|
.bind("group_name", createGroupTransactionData.getGroupName()).bind("reduced_group_name", createGroupTransactionData.getReducedGroupName())
|
||||||
.bind("description", createGroupTransactionData.getDescription()).bind("is_open", createGroupTransactionData.getIsOpen())
|
.bind("description", createGroupTransactionData.getDescription()).bind("is_open", createGroupTransactionData.isOpen())
|
||||||
.bind("approval_threshold", createGroupTransactionData.getApprovalThreshold().value)
|
.bind("approval_threshold", createGroupTransactionData.getApprovalThreshold().value)
|
||||||
.bind("min_block_delay", createGroupTransactionData.getMinimumBlockDelay())
|
.bind("min_block_delay", createGroupTransactionData.getMinimumBlockDelay())
|
||||||
.bind("max_block_delay", createGroupTransactionData.getMaximumBlockDelay()).bind("group_id", createGroupTransactionData.getGroupId());
|
.bind("max_block_delay", createGroupTransactionData.getMaximumBlockDelay()).bind("group_id", createGroupTransactionData.getGroupId());
|
||||||
|
@ -5,12 +5,12 @@ import java.util.List;
|
|||||||
|
|
||||||
import org.qortal.account.Account;
|
import org.qortal.account.Account;
|
||||||
import org.qortal.asset.Asset;
|
import org.qortal.asset.Asset;
|
||||||
import org.qortal.crypto.Crypto;
|
|
||||||
import org.qortal.data.transaction.CreateGroupTransactionData;
|
import org.qortal.data.transaction.CreateGroupTransactionData;
|
||||||
import org.qortal.data.transaction.TransactionData;
|
import org.qortal.data.transaction.TransactionData;
|
||||||
import org.qortal.group.Group;
|
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.utils.Unicode;
|
||||||
|
|
||||||
import com.google.common.base.Utf8;
|
import com.google.common.base.Utf8;
|
||||||
|
|
||||||
@ -31,7 +31,7 @@ public class CreateGroupTransaction extends Transaction {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<String> getRecipientAddresses() throws DataException {
|
public List<String> getRecipientAddresses() throws DataException {
|
||||||
return Collections.singletonList(this.createGroupTransactionData.getOwner());
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Navigation
|
// Navigation
|
||||||
@ -40,14 +40,19 @@ public class CreateGroupTransaction extends Transaction {
|
|||||||
return this.getCreator();
|
return this.getCreator();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private synchronized String getReducedGroupName() {
|
||||||
|
if (this.createGroupTransactionData.getReducedGroupName() == null) {
|
||||||
|
String reducedGroupName = Group.reduceName(this.createGroupTransactionData.getGroupName());
|
||||||
|
this.createGroupTransactionData.setReducedGroupName(reducedGroupName);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.createGroupTransactionData.getReducedGroupName();
|
||||||
|
}
|
||||||
|
|
||||||
// Processing
|
// Processing
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ValidationResult isValid() throws DataException {
|
public ValidationResult isValid() throws DataException {
|
||||||
// Check owner address is valid
|
|
||||||
if (!Crypto.isValidAddress(this.createGroupTransactionData.getOwner()))
|
|
||||||
return ValidationResult.INVALID_ADDRESS;
|
|
||||||
|
|
||||||
// Check approval threshold is valid
|
// Check approval threshold is valid
|
||||||
if (this.createGroupTransactionData.getApprovalThreshold() == null)
|
if (this.createGroupTransactionData.getApprovalThreshold() == null)
|
||||||
return ValidationResult.INVALID_GROUP_APPROVAL_THRESHOLD;
|
return ValidationResult.INVALID_GROUP_APPROVAL_THRESHOLD;
|
||||||
@ -62,9 +67,11 @@ public class CreateGroupTransaction extends Transaction {
|
|||||||
if (this.createGroupTransactionData.getMaximumBlockDelay() < this.createGroupTransactionData.getMinimumBlockDelay())
|
if (this.createGroupTransactionData.getMaximumBlockDelay() < this.createGroupTransactionData.getMinimumBlockDelay())
|
||||||
return ValidationResult.INVALID_GROUP_BLOCK_DELAY;
|
return ValidationResult.INVALID_GROUP_BLOCK_DELAY;
|
||||||
|
|
||||||
|
String groupName = this.createGroupTransactionData.getGroupName();
|
||||||
|
|
||||||
// Check group name size bounds
|
// Check group name size bounds
|
||||||
int groupNameLength = Utf8.encodedLength(this.createGroupTransactionData.getGroupName());
|
int groupNameLength = Utf8.encodedLength(groupName);
|
||||||
if (groupNameLength < 1 || groupNameLength > Group.MAX_NAME_SIZE)
|
if (groupNameLength < Group.MIN_NAME_SIZE || groupNameLength > Group.MAX_NAME_SIZE)
|
||||||
return ValidationResult.INVALID_NAME_LENGTH;
|
return ValidationResult.INVALID_NAME_LENGTH;
|
||||||
|
|
||||||
// Check description size bounds
|
// Check description size bounds
|
||||||
@ -72,8 +79,8 @@ public class CreateGroupTransaction extends Transaction {
|
|||||||
if (descriptionLength < 1 || descriptionLength > Group.MAX_DESCRIPTION_SIZE)
|
if (descriptionLength < 1 || descriptionLength > Group.MAX_DESCRIPTION_SIZE)
|
||||||
return ValidationResult.INVALID_DESCRIPTION_LENGTH;
|
return ValidationResult.INVALID_DESCRIPTION_LENGTH;
|
||||||
|
|
||||||
// Check group name is lowercase
|
// Check name is in normalized form (no leading/trailing whitespace, etc.)
|
||||||
if (!this.createGroupTransactionData.getGroupName().equals(this.createGroupTransactionData.getGroupName().toLowerCase()))
|
if (!groupName.equals(Unicode.normalize(groupName)))
|
||||||
return ValidationResult.NAME_NOT_LOWER_CASE;
|
return ValidationResult.NAME_NOT_LOWER_CASE;
|
||||||
|
|
||||||
Account creator = getCreator();
|
Account creator = getCreator();
|
||||||
@ -82,13 +89,16 @@ public class CreateGroupTransaction extends Transaction {
|
|||||||
if (creator.getConfirmedBalance(Asset.QORT) < this.createGroupTransactionData.getFee())
|
if (creator.getConfirmedBalance(Asset.QORT) < this.createGroupTransactionData.getFee())
|
||||||
return ValidationResult.NO_BALANCE;
|
return ValidationResult.NO_BALANCE;
|
||||||
|
|
||||||
|
// Fill in missing reduced name. Caller is likely to save this as next step.
|
||||||
|
getReducedGroupName();
|
||||||
|
|
||||||
return ValidationResult.OK;
|
return ValidationResult.OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ValidationResult isProcessable() throws DataException {
|
public ValidationResult isProcessable() throws DataException {
|
||||||
// Check the group name isn't already taken
|
// Check the group name isn't already taken
|
||||||
if (this.repository.getGroupRepository().groupExists(this.createGroupTransactionData.getGroupName()))
|
if (this.repository.getGroupRepository().reducedGroupNameExists(getReducedGroupName()))
|
||||||
return ValidationResult.GROUP_ALREADY_EXISTS;
|
return ValidationResult.GROUP_ALREADY_EXISTS;
|
||||||
|
|
||||||
return ValidationResult.OK;
|
return ValidationResult.OK;
|
||||||
|
@ -20,14 +20,13 @@ import com.google.common.primitives.Longs;
|
|||||||
public class CreateGroupTransactionTransformer extends TransactionTransformer {
|
public class CreateGroupTransactionTransformer extends TransactionTransformer {
|
||||||
|
|
||||||
// Property lengths
|
// Property lengths
|
||||||
private static final int OWNER_LENGTH = ADDRESS_LENGTH;
|
|
||||||
private static final int NAME_SIZE_LENGTH = INT_LENGTH;
|
private static final int NAME_SIZE_LENGTH = INT_LENGTH;
|
||||||
private static final int DESCRIPTION_SIZE_LENGTH = INT_LENGTH;
|
private static final int DESCRIPTION_SIZE_LENGTH = INT_LENGTH;
|
||||||
private static final int IS_OPEN_LENGTH = BOOLEAN_LENGTH;
|
private static final int IS_OPEN_LENGTH = BOOLEAN_LENGTH;
|
||||||
private static final int APPROVAL_THRESHOLD_LENGTH = BYTE_LENGTH;
|
private static final int APPROVAL_THRESHOLD_LENGTH = BYTE_LENGTH;
|
||||||
private static final int BLOCK_DELAY_LENGTH = INT_LENGTH;
|
private static final int BLOCK_DELAY_LENGTH = INT_LENGTH;
|
||||||
|
|
||||||
private static final int EXTRAS_LENGTH = OWNER_LENGTH + NAME_SIZE_LENGTH + DESCRIPTION_SIZE_LENGTH + IS_OPEN_LENGTH
|
private static final int EXTRAS_LENGTH = NAME_SIZE_LENGTH + DESCRIPTION_SIZE_LENGTH + IS_OPEN_LENGTH
|
||||||
+ APPROVAL_THRESHOLD_LENGTH + BLOCK_DELAY_LENGTH + BLOCK_DELAY_LENGTH;
|
+ APPROVAL_THRESHOLD_LENGTH + BLOCK_DELAY_LENGTH + BLOCK_DELAY_LENGTH;
|
||||||
|
|
||||||
protected static final TransactionLayout layout;
|
protected static final TransactionLayout layout;
|
||||||
@ -39,7 +38,6 @@ public class CreateGroupTransactionTransformer extends TransactionTransformer {
|
|||||||
layout.add("transaction's groupID", TransformationType.INT);
|
layout.add("transaction's groupID", TransformationType.INT);
|
||||||
layout.add("reference", TransformationType.SIGNATURE);
|
layout.add("reference", TransformationType.SIGNATURE);
|
||||||
layout.add("group creator's public key", TransformationType.PUBLIC_KEY);
|
layout.add("group creator's public key", TransformationType.PUBLIC_KEY);
|
||||||
layout.add("group owner's address", TransformationType.ADDRESS);
|
|
||||||
layout.add("group's name length", TransformationType.INT);
|
layout.add("group's name length", TransformationType.INT);
|
||||||
layout.add("group's name", TransformationType.STRING);
|
layout.add("group's name", TransformationType.STRING);
|
||||||
layout.add("group's description length", TransformationType.INT);
|
layout.add("group's description length", TransformationType.INT);
|
||||||
@ -62,8 +60,6 @@ public class CreateGroupTransactionTransformer extends TransactionTransformer {
|
|||||||
|
|
||||||
byte[] creatorPublicKey = Serialization.deserializePublicKey(byteBuffer);
|
byte[] creatorPublicKey = Serialization.deserializePublicKey(byteBuffer);
|
||||||
|
|
||||||
String owner = Serialization.deserializeAddress(byteBuffer);
|
|
||||||
|
|
||||||
String groupName = Serialization.deserializeSizedString(byteBuffer, Group.MAX_NAME_SIZE);
|
String groupName = Serialization.deserializeSizedString(byteBuffer, Group.MAX_NAME_SIZE);
|
||||||
|
|
||||||
String description = Serialization.deserializeSizedString(byteBuffer, Group.MAX_DESCRIPTION_SIZE);
|
String description = Serialization.deserializeSizedString(byteBuffer, Group.MAX_DESCRIPTION_SIZE);
|
||||||
@ -83,7 +79,7 @@ public class CreateGroupTransactionTransformer extends TransactionTransformer {
|
|||||||
|
|
||||||
BaseTransactionData baseTransactionData = new BaseTransactionData(timestamp, txGroupId, reference, creatorPublicKey, fee, signature);
|
BaseTransactionData baseTransactionData = new BaseTransactionData(timestamp, txGroupId, reference, creatorPublicKey, fee, signature);
|
||||||
|
|
||||||
return new CreateGroupTransactionData(baseTransactionData, owner, groupName, description, isOpen, approvalThreshold, minBlockDelay, maxBlockDelay);
|
return new CreateGroupTransactionData(baseTransactionData, groupName, description, isOpen, approvalThreshold, minBlockDelay, maxBlockDelay);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static int getDataLength(TransactionData transactionData) throws TransformationException {
|
public static int getDataLength(TransactionData transactionData) throws TransformationException {
|
||||||
@ -101,13 +97,11 @@ public class CreateGroupTransactionTransformer extends TransactionTransformer {
|
|||||||
|
|
||||||
transformCommonBytes(transactionData, bytes);
|
transformCommonBytes(transactionData, bytes);
|
||||||
|
|
||||||
Serialization.serializeAddress(bytes, createGroupTransactionData.getOwner());
|
|
||||||
|
|
||||||
Serialization.serializeSizedString(bytes, createGroupTransactionData.getGroupName());
|
Serialization.serializeSizedString(bytes, createGroupTransactionData.getGroupName());
|
||||||
|
|
||||||
Serialization.serializeSizedString(bytes, createGroupTransactionData.getDescription());
|
Serialization.serializeSizedString(bytes, createGroupTransactionData.getDescription());
|
||||||
|
|
||||||
bytes.write((byte) (createGroupTransactionData.getIsOpen() ? 1 : 0));
|
bytes.write((byte) (createGroupTransactionData.isOpen() ? 1 : 0));
|
||||||
|
|
||||||
bytes.write((byte) createGroupTransactionData.getApprovalThreshold().value);
|
bytes.write((byte) createGroupTransactionData.getApprovalThreshold().value);
|
||||||
|
|
||||||
|
@ -27,7 +27,7 @@ public class GroupUtils {
|
|||||||
String groupDescription = groupName + " (test group)";
|
String groupDescription = groupName + " (test group)";
|
||||||
|
|
||||||
BaseTransactionData baseTransactionData = new BaseTransactionData(timestamp, Group.NO_GROUP, reference, account.getPublicKey(), GroupUtils.fee, null);
|
BaseTransactionData baseTransactionData = new BaseTransactionData(timestamp, Group.NO_GROUP, reference, account.getPublicKey(), GroupUtils.fee, null);
|
||||||
TransactionData transactionData = new CreateGroupTransactionData(baseTransactionData, account.getAddress(), groupName, groupDescription, isOpen, approvalThreshold, minimumBlockDelay, maximumBlockDelay);
|
TransactionData transactionData = new CreateGroupTransactionData(baseTransactionData, groupName, groupDescription, isOpen, approvalThreshold, minimumBlockDelay, maximumBlockDelay);
|
||||||
|
|
||||||
TransactionUtils.signAndMint(repository, transactionData, account);
|
TransactionUtils.signAndMint(repository, transactionData, account);
|
||||||
|
|
||||||
|
@ -14,7 +14,6 @@ public class CreateGroupTestTransaction extends TestTransaction {
|
|||||||
public static TransactionData randomTransaction(Repository repository, PrivateKeyAccount account, boolean wantValid) throws DataException {
|
public static TransactionData randomTransaction(Repository repository, PrivateKeyAccount account, boolean wantValid) throws DataException {
|
||||||
Random random = new Random();
|
Random random = new Random();
|
||||||
|
|
||||||
String owner = account.getAddress();
|
|
||||||
String groupName = "test group " + random.nextInt(1_000_000);
|
String groupName = "test group " + random.nextInt(1_000_000);
|
||||||
String description = "random test group";
|
String description = "random test group";
|
||||||
final boolean isOpen = false;
|
final boolean isOpen = false;
|
||||||
@ -22,7 +21,7 @@ public class CreateGroupTestTransaction extends TestTransaction {
|
|||||||
final int minimumBlockDelay = 5;
|
final int minimumBlockDelay = 5;
|
||||||
final int maximumBlockDelay = 20;
|
final int maximumBlockDelay = 20;
|
||||||
|
|
||||||
return new CreateGroupTransactionData(generateBase(account), owner, groupName, description, isOpen, approvalThreshold, minimumBlockDelay, maximumBlockDelay);
|
return new CreateGroupTransactionData(generateBase(account), groupName, description, isOpen, approvalThreshold, minimumBlockDelay, maximumBlockDelay);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -60,13 +60,12 @@ public class GroupBlockDelayTests extends Common {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private CreateGroupTransaction buildCreateGroupWithDelays(Repository repository, PrivateKeyAccount account, int minimumBlockDelay, int maximumBlockDelay) throws DataException {
|
private CreateGroupTransaction buildCreateGroupWithDelays(Repository repository, PrivateKeyAccount account, int minimumBlockDelay, int maximumBlockDelay) throws DataException {
|
||||||
String owner = account.getAddress();
|
|
||||||
String groupName = "test group";
|
String groupName = "test group";
|
||||||
String description = "random test group";
|
String description = "random test group";
|
||||||
final boolean isOpen = false;
|
final boolean isOpen = false;
|
||||||
ApprovalThreshold approvalThreshold = ApprovalThreshold.PCT40;
|
ApprovalThreshold approvalThreshold = ApprovalThreshold.PCT40;
|
||||||
|
|
||||||
CreateGroupTransactionData transactionData = new CreateGroupTransactionData(TestTransaction.generateBase(account), owner, groupName, description, isOpen, approvalThreshold, minimumBlockDelay, maximumBlockDelay);
|
CreateGroupTransactionData transactionData = new CreateGroupTransactionData(TestTransaction.generateBase(account), groupName, description, isOpen, approvalThreshold, minimumBlockDelay, maximumBlockDelay);
|
||||||
return new CreateGroupTransaction(repository, transactionData);
|
return new CreateGroupTransaction(repository, transactionData);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
59
src/test/java/org/qortal/test/group/MiscTests.java
Normal file
59
src/test/java/org/qortal/test/group/MiscTests.java
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
package org.qortal.test.group;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
|
import org.junit.After;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.qortal.account.PrivateKeyAccount;
|
||||||
|
import org.qortal.data.transaction.CreateGroupTransactionData;
|
||||||
|
import org.qortal.group.Group.ApprovalThreshold;
|
||||||
|
import org.qortal.repository.DataException;
|
||||||
|
import org.qortal.repository.Repository;
|
||||||
|
import org.qortal.repository.RepositoryManager;
|
||||||
|
import org.qortal.test.common.Common;
|
||||||
|
import org.qortal.test.common.TransactionUtils;
|
||||||
|
import org.qortal.test.common.transaction.TestTransaction;
|
||||||
|
import org.qortal.transaction.Transaction;
|
||||||
|
import org.qortal.transaction.Transaction.ValidationResult;
|
||||||
|
|
||||||
|
public class MiscTests extends Common {
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void beforeTest() throws DataException {
|
||||||
|
Common.useDefaultSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
public void afterTest() throws DataException {
|
||||||
|
Common.orphanCheck();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCreateGroupWithExistingName() throws DataException {
|
||||||
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
|
// Register-name
|
||||||
|
PrivateKeyAccount alice = Common.getTestAccount(repository, "alice");
|
||||||
|
String groupName = "test-group";
|
||||||
|
String description = "test group";
|
||||||
|
|
||||||
|
final boolean isOpen = false;
|
||||||
|
ApprovalThreshold approvalThreshold = ApprovalThreshold.PCT40;
|
||||||
|
int minimumBlockDelay = 10;
|
||||||
|
int maximumBlockDelay = 1440;
|
||||||
|
|
||||||
|
CreateGroupTransactionData transactionData = new CreateGroupTransactionData(TestTransaction.generateBase(alice), groupName, description, isOpen, approvalThreshold, minimumBlockDelay, maximumBlockDelay);
|
||||||
|
TransactionUtils.signAndMint(repository, transactionData, alice);
|
||||||
|
|
||||||
|
// duplicate
|
||||||
|
String duplicateGroupName = "TEST-gr0up";
|
||||||
|
transactionData = new CreateGroupTransactionData(TestTransaction.generateBase(alice), duplicateGroupName, description, isOpen, approvalThreshold, minimumBlockDelay, maximumBlockDelay);
|
||||||
|
Transaction transaction = Transaction.fromData(repository, transactionData);
|
||||||
|
transaction.sign(alice);
|
||||||
|
|
||||||
|
ValidationResult result = transaction.importAsUnconfirmed();
|
||||||
|
assertTrue("Transaction should be invalid", ValidationResult.OK != result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -62,7 +62,7 @@
|
|||||||
{ "type": "GENESIS", "recipient": "QaUpHNhT3Ygx6avRiKobuLdusppR5biXjL", "amount": "637557960.49687541", "assetId": 1 },
|
{ "type": "GENESIS", "recipient": "QaUpHNhT3Ygx6avRiKobuLdusppR5biXjL", "amount": "637557960.49687541", "assetId": 1 },
|
||||||
{ "type": "GENESIS", "recipient": "Qci5m9k4rcwe4ruKrZZQKka4FzUUMut3er", "amount": "0.666", "assetId": 1 },
|
{ "type": "GENESIS", "recipient": "Qci5m9k4rcwe4ruKrZZQKka4FzUUMut3er", "amount": "0.666", "assetId": 1 },
|
||||||
|
|
||||||
{ "type": "CREATE_GROUP", "creatorPublicKey": "2tiMr5LTpaWCgbRvkPK8TFd7k63DyHJMMFFsz9uBf1ZP", "owner": "QgV4s3xnzLhVBEJxcYui4u4q11yhUHsd9v", "groupName": "dev-group", "description": "developer group", "isOpen": false, "approvalThreshold": "PCT100", "minimumBlockDelay": 0, "maximumBlockDelay": 1440 },
|
{ "type": "CREATE_GROUP", "creatorPublicKey": "2tiMr5LTpaWCgbRvkPK8TFd7k63DyHJMMFFsz9uBf1ZP", "groupName": "dev-group", "description": "developer group", "isOpen": false, "approvalThreshold": "PCT100", "minimumBlockDelay": 0, "maximumBlockDelay": 1440 },
|
||||||
|
|
||||||
{ "type": "ISSUE_ASSET", "issuerPublicKey": "2tiMr5LTpaWCgbRvkPK8TFd7k63DyHJMMFFsz9uBf1ZP", "assetName": "TEST", "description": "test asset", "data": "", "quantity": 1000000, "isDivisible": true, "fee": 0 },
|
{ "type": "ISSUE_ASSET", "issuerPublicKey": "2tiMr5LTpaWCgbRvkPK8TFd7k63DyHJMMFFsz9uBf1ZP", "assetName": "TEST", "description": "test asset", "data": "", "quantity": 1000000, "isDivisible": true, "fee": 0 },
|
||||||
{ "type": "ISSUE_ASSET", "issuerPublicKey": "C6wuddsBV3HzRrXUtezE7P5MoRXp5m3mEDokRDGZB6ry", "assetName": "OTHER", "description": "other test asset", "data": "", "quantity": 1000000, "isDivisible": true, "fee": 0 },
|
{ "type": "ISSUE_ASSET", "issuerPublicKey": "C6wuddsBV3HzRrXUtezE7P5MoRXp5m3mEDokRDGZB6ry", "assetName": "OTHER", "description": "other test asset", "data": "", "quantity": 1000000, "isDivisible": true, "fee": 0 },
|
||||||
|
@ -62,7 +62,7 @@
|
|||||||
{ "type": "GENESIS", "recipient": "QaUpHNhT3Ygx6avRiKobuLdusppR5biXjL", "amount": "10000", "assetId": 1 },
|
{ "type": "GENESIS", "recipient": "QaUpHNhT3Ygx6avRiKobuLdusppR5biXjL", "amount": "10000", "assetId": 1 },
|
||||||
{ "type": "GENESIS", "recipient": "Qci5m9k4rcwe4ruKrZZQKka4FzUUMut3er", "amount": "8", "assetId": 1 },
|
{ "type": "GENESIS", "recipient": "Qci5m9k4rcwe4ruKrZZQKka4FzUUMut3er", "amount": "8", "assetId": 1 },
|
||||||
|
|
||||||
{ "type": "CREATE_GROUP", "creatorPublicKey": "2tiMr5LTpaWCgbRvkPK8TFd7k63DyHJMMFFsz9uBf1ZP", "owner": "QgV4s3xnzLhVBEJxcYui4u4q11yhUHsd9v", "groupName": "dev-group", "description": "developer group", "isOpen": false, "approvalThreshold": "PCT100", "minimumBlockDelay": 0, "maximumBlockDelay": 1440 },
|
{ "type": "CREATE_GROUP", "creatorPublicKey": "2tiMr5LTpaWCgbRvkPK8TFd7k63DyHJMMFFsz9uBf1ZP", "groupName": "dev-group", "description": "developer group", "isOpen": false, "approvalThreshold": "PCT100", "minimumBlockDelay": 0, "maximumBlockDelay": 1440 },
|
||||||
|
|
||||||
{ "type": "ISSUE_ASSET", "issuerPublicKey": "2tiMr5LTpaWCgbRvkPK8TFd7k63DyHJMMFFsz9uBf1ZP", "assetName": "TEST", "description": "test asset", "data": "", "quantity": 1000000, "isDivisible": true, "fee": 0 },
|
{ "type": "ISSUE_ASSET", "issuerPublicKey": "2tiMr5LTpaWCgbRvkPK8TFd7k63DyHJMMFFsz9uBf1ZP", "assetName": "TEST", "description": "test asset", "data": "", "quantity": 1000000, "isDivisible": true, "fee": 0 },
|
||||||
{ "type": "ISSUE_ASSET", "issuerPublicKey": "C6wuddsBV3HzRrXUtezE7P5MoRXp5m3mEDokRDGZB6ry", "assetName": "OTHER", "description": "other test asset", "data": "", "quantity": 1000000, "isDivisible": true, "fee": 0 },
|
{ "type": "ISSUE_ASSET", "issuerPublicKey": "C6wuddsBV3HzRrXUtezE7P5MoRXp5m3mEDokRDGZB6ry", "assetName": "OTHER", "description": "other test asset", "data": "", "quantity": 1000000, "isDivisible": true, "fee": 0 },
|
||||||
|
@ -59,7 +59,7 @@
|
|||||||
{ "type": "GENESIS", "recipient": "QaUpHNhT3Ygx6avRiKobuLdusppR5biXjL", "amount": "1000000" },
|
{ "type": "GENESIS", "recipient": "QaUpHNhT3Ygx6avRiKobuLdusppR5biXjL", "amount": "1000000" },
|
||||||
{ "type": "GENESIS", "recipient": "Qci5m9k4rcwe4ruKrZZQKka4FzUUMut3er", "amount": "1000000" },
|
{ "type": "GENESIS", "recipient": "Qci5m9k4rcwe4ruKrZZQKka4FzUUMut3er", "amount": "1000000" },
|
||||||
|
|
||||||
{ "type": "CREATE_GROUP", "creatorPublicKey": "2tiMr5LTpaWCgbRvkPK8TFd7k63DyHJMMFFsz9uBf1ZP", "owner": "QgV4s3xnzLhVBEJxcYui4u4q11yhUHsd9v", "groupName": "dev-group", "description": "developer group", "isOpen": false, "approvalThreshold": "PCT100", "minimumBlockDelay": 0, "maximumBlockDelay": 1440 },
|
{ "type": "CREATE_GROUP", "creatorPublicKey": "2tiMr5LTpaWCgbRvkPK8TFd7k63DyHJMMFFsz9uBf1ZP", "groupName": "dev-group", "description": "developer group", "isOpen": false, "approvalThreshold": "PCT100", "minimumBlockDelay": 0, "maximumBlockDelay": 1440 },
|
||||||
|
|
||||||
{ "type": "ISSUE_ASSET", "issuerPublicKey": "2tiMr5LTpaWCgbRvkPK8TFd7k63DyHJMMFFsz9uBf1ZP", "assetName": "TEST", "description": "test asset", "data": "", "quantity": "1000000", "isDivisible": true, "fee": 0 },
|
{ "type": "ISSUE_ASSET", "issuerPublicKey": "2tiMr5LTpaWCgbRvkPK8TFd7k63DyHJMMFFsz9uBf1ZP", "assetName": "TEST", "description": "test asset", "data": "", "quantity": "1000000", "isDivisible": true, "fee": 0 },
|
||||||
{ "type": "ISSUE_ASSET", "issuerPublicKey": "C6wuddsBV3HzRrXUtezE7P5MoRXp5m3mEDokRDGZB6ry", "assetName": "OTHER", "description": "other test asset", "data": "", "quantity": "1000000", "isDivisible": true, "fee": 0 },
|
{ "type": "ISSUE_ASSET", "issuerPublicKey": "C6wuddsBV3HzRrXUtezE7P5MoRXp5m3mEDokRDGZB6ry", "assetName": "OTHER", "description": "other test asset", "data": "", "quantity": "1000000", "isDivisible": true, "fee": 0 },
|
||||||
|
Loading…
Reference in New Issue
Block a user