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)
|
||||
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.
|
||||
private Boolean isAdmin;
|
||||
|
||||
@ -45,10 +50,12 @@ public class GroupData {
|
||||
}
|
||||
|
||||
/** 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.owner = owner;
|
||||
this.groupName = name;
|
||||
this.groupName = groupName;
|
||||
this.description = description;
|
||||
this.created = created;
|
||||
this.updated = updated;
|
||||
@ -58,11 +65,15 @@ public class GroupData {
|
||||
this.minimumBlockDelay = minBlockDelay;
|
||||
this.maximumBlockDelay = maxBlockDelay;
|
||||
this.creationGroupId = creationGroupId;
|
||||
this.reducedGroupName = reducedGroupName;
|
||||
}
|
||||
|
||||
/** 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) {
|
||||
this(null, owner, name, description, created, null, isOpen, approvalThreshold, minBlockDelay, maxBlockDelay, reference, creationGroupId);
|
||||
public GroupData(String owner, String groupName, String description, long created, boolean isOpen,
|
||||
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
|
||||
@ -115,7 +126,7 @@ public class GroupData {
|
||||
this.reference = reference;
|
||||
}
|
||||
|
||||
public boolean getIsOpen() {
|
||||
public boolean isOpen() {
|
||||
return this.isOpen;
|
||||
}
|
||||
|
||||
@ -143,6 +154,14 @@ public class GroupData {
|
||||
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}
|
||||
|
||||
public Boolean isAdmin() {
|
||||
|
@ -1,10 +1,14 @@
|
||||
package org.qortal.data.transaction;
|
||||
|
||||
import javax.xml.bind.Unmarshaller;
|
||||
import javax.xml.bind.annotation.XmlAccessType;
|
||||
import javax.xml.bind.annotation.XmlAccessorType;
|
||||
import javax.xml.bind.annotation.XmlElement;
|
||||
import javax.xml.bind.annotation.XmlTransient;
|
||||
|
||||
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.transaction.Transaction.TransactionType;
|
||||
|
||||
@ -24,40 +28,32 @@ public class CreateGroupTransactionData extends TransactionData {
|
||||
|
||||
// Properties
|
||||
// groupId can be null but assigned during save() or during load from repository
|
||||
@Schema(
|
||||
accessMode = AccessMode.READ_ONLY,
|
||||
description = "assigned group ID"
|
||||
)
|
||||
@Schema(accessMode = AccessMode.READ_ONLY, description = "assigned group ID")
|
||||
private Integer groupId = null;
|
||||
@Schema(
|
||||
description = "group owner's address",
|
||||
example = "QgV4s3xnzLhVBEJxcYui4u4q11yhUHsd9v"
|
||||
)
|
||||
private String owner;
|
||||
@Schema(
|
||||
description = "group name",
|
||||
example = "miner-group"
|
||||
)
|
||||
|
||||
@Schema(description = "group name", example = "miner-group")
|
||||
private String groupName;
|
||||
@Schema(
|
||||
description = "short description of group",
|
||||
example = "this group is for block miners"
|
||||
)
|
||||
|
||||
@Schema(description = "short description of group", example = "this group is for block miners")
|
||||
private String description;
|
||||
@Schema(
|
||||
description = "whether anyone can join group (open) or group is invite-only (closed)",
|
||||
example = "true"
|
||||
)
|
||||
|
||||
@Schema(description = "whether anyone can join group (open) or group is invite-only (closed)", example = "true")
|
||||
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;
|
||||
|
||||
@Schema(description = "minimum block delay before approval takes effect")
|
||||
private int minimumBlockDelay;
|
||||
|
||||
@Schema(description = "maximum block delay before which transaction approval must be reached")
|
||||
private int maximumBlockDelay;
|
||||
|
||||
// For internal use
|
||||
@XmlTransient
|
||||
@Schema(hidden = true)
|
||||
private String reducedGroupName;
|
||||
|
||||
// Constructors
|
||||
|
||||
// For JAXB
|
||||
@ -65,13 +61,22 @@ public class CreateGroupTransactionData extends TransactionData {
|
||||
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 */
|
||||
public CreateGroupTransactionData(BaseTransactionData baseTransactionData,
|
||||
String owner, String groupName, String description, boolean isOpen,
|
||||
ApprovalThreshold approvalThreshold, int minimumBlockDelay, int maximumBlockDelay, Integer groupId) {
|
||||
String groupName, String description, boolean isOpen,
|
||||
ApprovalThreshold approvalThreshold, int minimumBlockDelay, int maximumBlockDelay,
|
||||
Integer groupId, String reducedGroupName) {
|
||||
super(TransactionType.CREATE_GROUP, baseTransactionData);
|
||||
|
||||
this.owner = owner;
|
||||
this.groupName = groupName;
|
||||
this.description = description;
|
||||
this.isOpen = isOpen;
|
||||
@ -79,21 +84,18 @@ public class CreateGroupTransactionData extends TransactionData {
|
||||
this.minimumBlockDelay = minimumBlockDelay;
|
||||
this.maximumBlockDelay = maximumBlockDelay;
|
||||
this.groupId = groupId;
|
||||
this.reducedGroupName = reducedGroupName;
|
||||
}
|
||||
|
||||
/** From network/API */
|
||||
public CreateGroupTransactionData(BaseTransactionData baseTransactionData,
|
||||
String owner, String groupName, String description, boolean isOpen,
|
||||
String groupName, String description, boolean isOpen,
|
||||
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
|
||||
|
||||
public String getOwner() {
|
||||
return this.owner;
|
||||
}
|
||||
|
||||
public String getGroupName() {
|
||||
return this.groupName;
|
||||
}
|
||||
@ -102,7 +104,7 @@ public class CreateGroupTransactionData extends TransactionData {
|
||||
return this.description;
|
||||
}
|
||||
|
||||
public boolean getIsOpen() {
|
||||
public boolean isOpen() {
|
||||
return this.isOpen;
|
||||
}
|
||||
|
||||
@ -126,27 +128,23 @@ public class CreateGroupTransactionData extends TransactionData {
|
||||
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
|
||||
@XmlElement(
|
||||
name = "creatorPublicKey"
|
||||
)
|
||||
@Schema(
|
||||
name = "creatorPublicKey",
|
||||
description = "group creator's public key",
|
||||
example = "2tiMr5LTpaWCgbRvkPK8TFd7k63DyHJMMFFsz9uBf1ZP"
|
||||
)
|
||||
@XmlElement(name = "creatorPublicKey")
|
||||
@Schema(name = "creatorPublicKey", description = "group creator's public key", example = "2tiMr5LTpaWCgbRvkPK8TFd7k63DyHJMMFFsz9uBf1ZP")
|
||||
public byte[] getGroupCreatorPublicKey() {
|
||||
return this.creatorPublicKey;
|
||||
}
|
||||
|
||||
@XmlElement(
|
||||
name = "creatorPublicKey"
|
||||
)
|
||||
@Schema(
|
||||
name = "creatorPublicKey",
|
||||
description = "group creator's public key",
|
||||
example = "2tiMr5LTpaWCgbRvkPK8TFd7k63DyHJMMFFsz9uBf1ZP"
|
||||
)
|
||||
@XmlElement(name = "creatorPublicKey")
|
||||
@Schema(name = "creatorPublicKey", description = "group creator's public key", example = "2tiMr5LTpaWCgbRvkPK8TFd7k63DyHJMMFFsz9uBf1ZP")
|
||||
public void setGroupCreatorPublicKey(byte[] creatorPublicKey) {
|
||||
this.creatorPublicKey = creatorPublicKey;
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ import java.util.Map;
|
||||
|
||||
import org.qortal.account.Account;
|
||||
import org.qortal.account.PublicKeyAccount;
|
||||
import org.qortal.crypto.Crypto;
|
||||
import org.qortal.data.group.GroupAdminData;
|
||||
import org.qortal.data.group.GroupBanData;
|
||||
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.GroupRepository;
|
||||
import org.qortal.repository.Repository;
|
||||
import org.qortal.utils.Unicode;
|
||||
|
||||
public class Group {
|
||||
|
||||
@ -78,6 +80,7 @@ public class Group {
|
||||
// Useful constants
|
||||
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_DESCRIPTION_SIZE = 128;
|
||||
/** Max size of kick/ban reason */
|
||||
@ -95,10 +98,14 @@ public class Group {
|
||||
this.repository = repository;
|
||||
this.groupRepository = repository.getGroupRepository();
|
||||
|
||||
this.groupData = new GroupData(createGroupTransactionData.getOwner(), createGroupTransactionData.getGroupName(),
|
||||
createGroupTransactionData.getDescription(), createGroupTransactionData.getTimestamp(), createGroupTransactionData.getIsOpen(),
|
||||
createGroupTransactionData.getApprovalThreshold(), createGroupTransactionData.getMinimumBlockDelay(),
|
||||
createGroupTransactionData.getMaximumBlockDelay(), createGroupTransactionData.getSignature(), createGroupTransactionData.getTxGroupId());
|
||||
String owner = Crypto.toAddress(createGroupTransactionData.getCreatorPublicKey());
|
||||
|
||||
this.groupData = new GroupData(owner, createGroupTransactionData.getGroupName(),
|
||||
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
|
||||
|
||||
public static String reduceName(String name) {
|
||||
return Unicode.sanitize(name);
|
||||
}
|
||||
|
||||
// Membership
|
||||
|
||||
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");
|
||||
|
||||
switch (previousTransactionData.getType()) {
|
||||
case CREATE_GROUP:
|
||||
case CREATE_GROUP: {
|
||||
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.setIsOpen(previousCreateGroupTransactionData.getIsOpen());
|
||||
this.groupData.setIsOpen(previousCreateGroupTransactionData.isOpen());
|
||||
this.groupData.setApprovalThreshold(previousCreateGroupTransactionData.getApprovalThreshold());
|
||||
this.groupData.setUpdated(null);
|
||||
break;
|
||||
}
|
||||
|
||||
case UPDATE_GROUP:
|
||||
case UPDATE_GROUP: {
|
||||
UpdateGroupTransactionData previousUpdateGroupTransactionData = (UpdateGroupTransactionData) previousTransactionData;
|
||||
this.groupData.setOwner(previousUpdateGroupTransactionData.getNewOwner());
|
||||
this.groupData.setDescription(previousUpdateGroupTransactionData.getNewDescription());
|
||||
@ -372,6 +387,7 @@ public class Group {
|
||||
this.groupData.setApprovalThreshold(previousUpdateGroupTransactionData.getNewApprovalThreshold());
|
||||
this.groupData.setUpdated(previousUpdateGroupTransactionData.getTimestamp());
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
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
|
||||
// this is now a pending "join request"
|
||||
if (groupInviteData == null && !groupData.getIsOpen()) {
|
||||
if (groupInviteData == null && !groupData.isOpen()) {
|
||||
// Save join request
|
||||
this.addJoinRequest(joiner.getAddress(), joinGroupTransactionData.getSignature());
|
||||
|
||||
@ -761,7 +777,7 @@ public class Group {
|
||||
byte[] inviteReference = joinGroupTransactionData.getInviteReference();
|
||||
|
||||
// Was this a join-request only?
|
||||
if (inviteReference == null && !groupData.getIsOpen()) {
|
||||
if (inviteReference == null && !groupData.isOpen()) {
|
||||
// Delete join request
|
||||
this.deleteJoinRequest(joiner.getAddress());
|
||||
} else {
|
||||
|
@ -21,6 +21,8 @@ public interface GroupRepository {
|
||||
|
||||
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 default List<GroupData> getAllGroups() throws DataException {
|
||||
|
@ -444,12 +444,15 @@ public class HSQLDBDatabaseUpdates {
|
||||
|
||||
case 12:
|
||||
// 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, "
|
||||
+ "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))");
|
||||
// For when a user wants to lookup an group by name
|
||||
+ "reference Signature, creation_group_id GroupID, reduced_group_name GroupName NOT NULL, "
|
||||
+ "description GenericDescription NOT NULL, PRIMARY KEY (group_id))");
|
||||
// For finding groups by 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
|
||||
stmt.execute("CREATE INDEX GroupOwnerIndex ON Groups (owner)");
|
||||
|
||||
@ -499,7 +502,7 @@ public class HSQLDBDatabaseUpdates {
|
||||
// Group transactions
|
||||
// Create group
|
||||
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, "
|
||||
+ TRANSACTION_KEYS + ")");
|
||||
|
||||
|
@ -28,7 +28,8 @@ public class HSQLDBGroupRepository implements GroupRepository {
|
||||
@Override
|
||||
public GroupData fromGroupId(int groupId) throws DataException {
|
||||
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)) {
|
||||
if (resultSet == null)
|
||||
@ -53,9 +54,10 @@ public class HSQLDBGroupRepository implements GroupRepository {
|
||||
int maxBlockDelay = resultSet.getInt(10);
|
||||
|
||||
int creationGroupId = resultSet.getInt(11);
|
||||
String reducedGroupName = resultSet.getString(12);
|
||||
|
||||
return new GroupData(groupId, owner, groupName, description, created, updated, isOpen,
|
||||
approvalThreshold, minBlockDelay, maxBlockDelay, reference, creationGroupId);
|
||||
approvalThreshold, minBlockDelay, maxBlockDelay, reference, creationGroupId, reducedGroupName);
|
||||
} catch (SQLException e) {
|
||||
throw new DataException("Unable to fetch group info from repository", e);
|
||||
}
|
||||
@ -64,7 +66,8 @@ public class HSQLDBGroupRepository implements GroupRepository {
|
||||
@Override
|
||||
public GroupData fromGroupName(String groupName) throws DataException {
|
||||
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)) {
|
||||
if (resultSet == null)
|
||||
@ -89,9 +92,10 @@ public class HSQLDBGroupRepository implements GroupRepository {
|
||||
int maxBlockDelay = resultSet.getInt(10);
|
||||
|
||||
int creationGroupId = resultSet.getInt(11);
|
||||
String reducedGroupName = resultSet.getString(12);
|
||||
|
||||
return new GroupData(groupId, owner, groupName, description, created, updated, isOpen,
|
||||
approvalThreshold, minBlockDelay, maxBlockDelay, reference, creationGroupId);
|
||||
approvalThreshold, minBlockDelay, maxBlockDelay, reference, creationGroupId, reducedGroupName);
|
||||
} catch (SQLException 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
|
||||
public List<GroupData> getAllGroups(Integer limit, Integer offset, Boolean reverse) throws DataException {
|
||||
StringBuilder sql = new StringBuilder(512);
|
||||
|
||||
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)
|
||||
sql.append(" DESC");
|
||||
@ -154,9 +168,10 @@ public class HSQLDBGroupRepository implements GroupRepository {
|
||||
int maxBlockDelay = resultSet.getInt(11);
|
||||
|
||||
int creationGroupId = resultSet.getInt(12);
|
||||
String reducedGroupName = resultSet.getString(13);
|
||||
|
||||
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());
|
||||
|
||||
return groups;
|
||||
@ -170,7 +185,8 @@ public class HSQLDBGroupRepository implements GroupRepository {
|
||||
StringBuilder sql = new StringBuilder(512);
|
||||
|
||||
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)
|
||||
sql.append(" DESC");
|
||||
@ -203,9 +219,10 @@ public class HSQLDBGroupRepository implements GroupRepository {
|
||||
int maxBlockDelay = resultSet.getInt(10);
|
||||
|
||||
int creationGroupId = resultSet.getInt(11);
|
||||
String reducedGroupName = resultSet.getString(12);
|
||||
|
||||
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());
|
||||
|
||||
return groups;
|
||||
@ -219,7 +236,7 @@ public class HSQLDBGroupRepository implements GroupRepository {
|
||||
StringBuilder sql = new StringBuilder(512);
|
||||
|
||||
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) "
|
||||
+ "LEFT OUTER JOIN GroupAdmins ON GroupAdmins.group_id = GroupMembers.group_id AND GroupAdmins.admin = GroupMembers.address "
|
||||
+ "WHERE address = ? ORDER BY group_name");
|
||||
@ -256,12 +273,13 @@ public class HSQLDBGroupRepository implements GroupRepository {
|
||||
int maxBlockDelay = resultSet.getInt(11);
|
||||
|
||||
int creationGroupId = resultSet.getInt(12);
|
||||
String reducedGroupName = resultSet.getString(13);
|
||||
|
||||
resultSet.getString(13); // 'admin'
|
||||
resultSet.getString(14); // 'admin'
|
||||
boolean isAdmin = !resultSet.wasNull();
|
||||
|
||||
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);
|
||||
|
||||
@ -280,9 +298,9 @@ public class HSQLDBGroupRepository implements GroupRepository {
|
||||
|
||||
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("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("creation_group_id", groupData.getCreationGroupId());
|
||||
.bind("creation_group_id", groupData.getCreationGroupId()).bind("reduced_group_name", groupData.getReducedGroupName());
|
||||
|
||||
try {
|
||||
saveHelper.execute(this.repository);
|
||||
|
@ -18,28 +18,30 @@ public class HSQLDBCreateGroupTransactionRepository extends HSQLDBTransactionRep
|
||||
}
|
||||
|
||||
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())) {
|
||||
if (resultSet == null)
|
||||
return null;
|
||||
|
||||
String owner = resultSet.getString(1);
|
||||
String groupName = resultSet.getString(2);
|
||||
String description = resultSet.getString(3);
|
||||
boolean isOpen = resultSet.getBoolean(4);
|
||||
String groupName = resultSet.getString(1);
|
||||
String description = resultSet.getString(2);
|
||||
boolean isOpen = resultSet.getBoolean(3);
|
||||
|
||||
ApprovalThreshold approvalThreshold = ApprovalThreshold.valueOf(resultSet.getInt(5));
|
||||
ApprovalThreshold approvalThreshold = ApprovalThreshold.valueOf(resultSet.getInt(4));
|
||||
|
||||
int minBlockDelay = resultSet.getInt(6);
|
||||
int maxBlockDelay = resultSet.getInt(7);
|
||||
int minBlockDelay = resultSet.getInt(5);
|
||||
int maxBlockDelay = resultSet.getInt(6);
|
||||
|
||||
Integer groupId = resultSet.getInt(8);
|
||||
Integer groupId = resultSet.getInt(7);
|
||||
if (groupId == 0 && resultSet.wasNull())
|
||||
groupId = null;
|
||||
|
||||
return new CreateGroupTransactionData(baseTransactionData, owner, groupName, description, isOpen, approvalThreshold,
|
||||
minBlockDelay, maxBlockDelay, groupId);
|
||||
String reducedGroupName = resultSet.getString(8);
|
||||
|
||||
return new CreateGroupTransactionData(baseTransactionData, groupName, description, isOpen, approvalThreshold,
|
||||
minBlockDelay, maxBlockDelay, groupId, reducedGroupName);
|
||||
} catch (SQLException 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");
|
||||
|
||||
saveHelper.bind("signature", createGroupTransactionData.getSignature()).bind("creator", createGroupTransactionData.getCreatorPublicKey())
|
||||
.bind("owner", createGroupTransactionData.getOwner()).bind("group_name", createGroupTransactionData.getGroupName())
|
||||
.bind("description", createGroupTransactionData.getDescription()).bind("is_open", createGroupTransactionData.getIsOpen())
|
||||
.bind("group_name", createGroupTransactionData.getGroupName()).bind("reduced_group_name", createGroupTransactionData.getReducedGroupName())
|
||||
.bind("description", createGroupTransactionData.getDescription()).bind("is_open", createGroupTransactionData.isOpen())
|
||||
.bind("approval_threshold", createGroupTransactionData.getApprovalThreshold().value)
|
||||
.bind("min_block_delay", createGroupTransactionData.getMinimumBlockDelay())
|
||||
.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.asset.Asset;
|
||||
import org.qortal.crypto.Crypto;
|
||||
import org.qortal.data.transaction.CreateGroupTransactionData;
|
||||
import org.qortal.data.transaction.TransactionData;
|
||||
import org.qortal.group.Group;
|
||||
import org.qortal.repository.DataException;
|
||||
import org.qortal.repository.Repository;
|
||||
import org.qortal.utils.Unicode;
|
||||
|
||||
import com.google.common.base.Utf8;
|
||||
|
||||
@ -31,7 +31,7 @@ public class CreateGroupTransaction extends Transaction {
|
||||
|
||||
@Override
|
||||
public List<String> getRecipientAddresses() throws DataException {
|
||||
return Collections.singletonList(this.createGroupTransactionData.getOwner());
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
// Navigation
|
||||
@ -40,14 +40,19 @@ public class CreateGroupTransaction extends Transaction {
|
||||
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
|
||||
|
||||
@Override
|
||||
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
|
||||
if (this.createGroupTransactionData.getApprovalThreshold() == null)
|
||||
return ValidationResult.INVALID_GROUP_APPROVAL_THRESHOLD;
|
||||
@ -62,9 +67,11 @@ public class CreateGroupTransaction extends Transaction {
|
||||
if (this.createGroupTransactionData.getMaximumBlockDelay() < this.createGroupTransactionData.getMinimumBlockDelay())
|
||||
return ValidationResult.INVALID_GROUP_BLOCK_DELAY;
|
||||
|
||||
String groupName = this.createGroupTransactionData.getGroupName();
|
||||
|
||||
// Check group name size bounds
|
||||
int groupNameLength = Utf8.encodedLength(this.createGroupTransactionData.getGroupName());
|
||||
if (groupNameLength < 1 || groupNameLength > Group.MAX_NAME_SIZE)
|
||||
int groupNameLength = Utf8.encodedLength(groupName);
|
||||
if (groupNameLength < Group.MIN_NAME_SIZE || groupNameLength > Group.MAX_NAME_SIZE)
|
||||
return ValidationResult.INVALID_NAME_LENGTH;
|
||||
|
||||
// Check description size bounds
|
||||
@ -72,8 +79,8 @@ public class CreateGroupTransaction extends Transaction {
|
||||
if (descriptionLength < 1 || descriptionLength > Group.MAX_DESCRIPTION_SIZE)
|
||||
return ValidationResult.INVALID_DESCRIPTION_LENGTH;
|
||||
|
||||
// Check group name is lowercase
|
||||
if (!this.createGroupTransactionData.getGroupName().equals(this.createGroupTransactionData.getGroupName().toLowerCase()))
|
||||
// Check name is in normalized form (no leading/trailing whitespace, etc.)
|
||||
if (!groupName.equals(Unicode.normalize(groupName)))
|
||||
return ValidationResult.NAME_NOT_LOWER_CASE;
|
||||
|
||||
Account creator = getCreator();
|
||||
@ -82,13 +89,16 @@ public class CreateGroupTransaction extends Transaction {
|
||||
if (creator.getConfirmedBalance(Asset.QORT) < this.createGroupTransactionData.getFee())
|
||||
return ValidationResult.NO_BALANCE;
|
||||
|
||||
// Fill in missing reduced name. Caller is likely to save this as next step.
|
||||
getReducedGroupName();
|
||||
|
||||
return ValidationResult.OK;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ValidationResult isProcessable() throws DataException {
|
||||
// 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.OK;
|
||||
|
@ -20,14 +20,13 @@ import com.google.common.primitives.Longs;
|
||||
public class CreateGroupTransactionTransformer extends TransactionTransformer {
|
||||
|
||||
// Property lengths
|
||||
private static final int OWNER_LENGTH = ADDRESS_LENGTH;
|
||||
private static final int NAME_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 APPROVAL_THRESHOLD_LENGTH = BYTE_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;
|
||||
|
||||
protected static final TransactionLayout layout;
|
||||
@ -39,7 +38,6 @@ public class CreateGroupTransactionTransformer extends TransactionTransformer {
|
||||
layout.add("transaction's groupID", TransformationType.INT);
|
||||
layout.add("reference", TransformationType.SIGNATURE);
|
||||
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", TransformationType.STRING);
|
||||
layout.add("group's description length", TransformationType.INT);
|
||||
@ -62,8 +60,6 @@ public class CreateGroupTransactionTransformer extends TransactionTransformer {
|
||||
|
||||
byte[] creatorPublicKey = Serialization.deserializePublicKey(byteBuffer);
|
||||
|
||||
String owner = Serialization.deserializeAddress(byteBuffer);
|
||||
|
||||
String groupName = Serialization.deserializeSizedString(byteBuffer, Group.MAX_NAME_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);
|
||||
|
||||
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 {
|
||||
@ -101,13 +97,11 @@ public class CreateGroupTransactionTransformer extends TransactionTransformer {
|
||||
|
||||
transformCommonBytes(transactionData, bytes);
|
||||
|
||||
Serialization.serializeAddress(bytes, createGroupTransactionData.getOwner());
|
||||
|
||||
Serialization.serializeSizedString(bytes, createGroupTransactionData.getGroupName());
|
||||
|
||||
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);
|
||||
|
||||
|
@ -27,7 +27,7 @@ public class GroupUtils {
|
||||
String groupDescription = groupName + " (test group)";
|
||||
|
||||
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);
|
||||
|
||||
|
@ -14,7 +14,6 @@ public class CreateGroupTestTransaction extends TestTransaction {
|
||||
public static TransactionData randomTransaction(Repository repository, PrivateKeyAccount account, boolean wantValid) throws DataException {
|
||||
Random random = new Random();
|
||||
|
||||
String owner = account.getAddress();
|
||||
String groupName = "test group " + random.nextInt(1_000_000);
|
||||
String description = "random test group";
|
||||
final boolean isOpen = false;
|
||||
@ -22,7 +21,7 @@ public class CreateGroupTestTransaction extends TestTransaction {
|
||||
final int minimumBlockDelay = 5;
|
||||
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 {
|
||||
String owner = account.getAddress();
|
||||
String groupName = "test group";
|
||||
String description = "random test group";
|
||||
final boolean isOpen = false;
|
||||
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);
|
||||
}
|
||||
|
||||
|
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": "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": "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": "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": "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": "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": "C6wuddsBV3HzRrXUtezE7P5MoRXp5m3mEDokRDGZB6ry", "assetName": "OTHER", "description": "other test asset", "data": "", "quantity": "1000000", "isDivisible": true, "fee": 0 },
|
||||
|
Loading…
Reference in New Issue
Block a user