forked from Qortal/qortal
Work on groups
Some dev/testing API calls are now turned off by default in production mode, see "restrictApi" settings entry, returning NON_PRODUCTION API error. Corrections to how account's defaultGroupId works, removing "effective groupID" which overly complicated matters. In relation to above, DEFAULT_GROUP (0) no longer exists and NO_GROUP(-1) now has the value 0 instead. So transactions can no longer have txGroupId of DEFAULT_GROUP, which in turn required all the erroneous "effective groupID" code. API call /addresses/{address} now supplies blockchain-wide defaultGroupId if account doesn't exist or if account's default not set and NO-GROUP not allowed. API /transactions/pending now offloaded to repository instead of Java-based processing and filtering. Transaction approval checks added to Block.isValid Groups now have min/max approval block delays. Checks added to incoming unconfirmed, block generator, block.isValid, etc. 'needing approval' and 'meets approval threshold' now split into separate calls. NB: settings.json no longer part of git repo
This commit is contained in:
parent
00656f6724
commit
86a35c3b71
@ -1,6 +0,0 @@
|
|||||||
{
|
|
||||||
"rpcallowed": [
|
|
||||||
"::/0",
|
|
||||||
"0.0.0.0/0"
|
|
||||||
]
|
|
||||||
}
|
|
@ -13,6 +13,7 @@ public enum ApiError {
|
|||||||
NOT_YET_RELEASED(3, 422),
|
NOT_YET_RELEASED(3, 422),
|
||||||
UNAUTHORIZED(4, 403),
|
UNAUTHORIZED(4, 403),
|
||||||
REPOSITORY_ISSUE(5, 500),
|
REPOSITORY_ISSUE(5, 500),
|
||||||
|
NON_PRODUCTION(6, 403),
|
||||||
|
|
||||||
// VALIDATION
|
// VALIDATION
|
||||||
INVALID_SIGNATURE(101, 400),
|
INVALID_SIGNATURE(101, 400),
|
||||||
|
@ -22,11 +22,14 @@ import org.qora.api.ApiErrors;
|
|||||||
import org.qora.api.ApiException;
|
import org.qora.api.ApiException;
|
||||||
import org.qora.api.ApiExceptionFactory;
|
import org.qora.api.ApiExceptionFactory;
|
||||||
import org.qora.asset.Asset;
|
import org.qora.asset.Asset;
|
||||||
|
import org.qora.block.BlockChain;
|
||||||
import org.qora.crypto.Crypto;
|
import org.qora.crypto.Crypto;
|
||||||
import org.qora.data.account.AccountData;
|
import org.qora.data.account.AccountData;
|
||||||
|
import org.qora.group.Group;
|
||||||
import org.qora.repository.DataException;
|
import org.qora.repository.DataException;
|
||||||
import org.qora.repository.Repository;
|
import org.qora.repository.Repository;
|
||||||
import org.qora.repository.RepositoryManager;
|
import org.qora.repository.RepositoryManager;
|
||||||
|
import org.qora.settings.Settings;
|
||||||
import org.qora.transform.Transformer;
|
import org.qora.transform.Transformer;
|
||||||
import org.qora.utils.Base58;
|
import org.qora.utils.Base58;
|
||||||
|
|
||||||
@ -55,7 +58,17 @@ public class AddressesResource {
|
|||||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_ADDRESS);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_ADDRESS);
|
||||||
|
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
return repository.getAccountRepository().getAccount(address);
|
AccountData accountData = repository.getAccountRepository().getAccount(address);
|
||||||
|
|
||||||
|
// Not found?
|
||||||
|
if (accountData == null)
|
||||||
|
accountData = new AccountData(address, null, null, BlockChain.getInstance().getDefaultGroupId());
|
||||||
|
|
||||||
|
// If Blockchain config doesn't allow NO_GROUP then change this to blockchain's default groupID
|
||||||
|
if (accountData.getDefaultGroupId() == Group.NO_GROUP && !BlockChain.getInstance().getGrouplessAllowed())
|
||||||
|
accountData.setDefaultGroupId(BlockChain.getInstance().getDefaultGroupId());
|
||||||
|
|
||||||
|
return accountData;
|
||||||
} catch (ApiException e) {
|
} catch (ApiException e) {
|
||||||
throw e;
|
throw e;
|
||||||
} catch (DataException e) {
|
} catch (DataException e) {
|
||||||
@ -227,8 +240,11 @@ public class AddressesResource {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ApiErrors({ApiError.INVALID_PUBLIC_KEY, ApiError.REPOSITORY_ISSUE})
|
@ApiErrors({ApiError.INVALID_PUBLIC_KEY, ApiError.NON_PRODUCTION, ApiError.REPOSITORY_ISSUE})
|
||||||
public String fromPublicKey(@PathParam("publickey") String publicKey58) {
|
public String fromPublicKey(@PathParam("publickey") String publicKey58) {
|
||||||
|
if (Settings.getInstance().isRestrictedApi())
|
||||||
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.NON_PRODUCTION);
|
||||||
|
|
||||||
// Decode public key
|
// Decode public key
|
||||||
byte[] publicKey;
|
byte[] publicKey;
|
||||||
try {
|
try {
|
||||||
|
@ -43,6 +43,7 @@ import org.qora.data.transaction.TransactionData;
|
|||||||
import org.qora.repository.DataException;
|
import org.qora.repository.DataException;
|
||||||
import org.qora.repository.Repository;
|
import org.qora.repository.Repository;
|
||||||
import org.qora.repository.RepositoryManager;
|
import org.qora.repository.RepositoryManager;
|
||||||
|
import org.qora.settings.Settings;
|
||||||
import org.qora.transaction.Transaction;
|
import org.qora.transaction.Transaction;
|
||||||
import org.qora.transaction.Transaction.ValidationResult;
|
import org.qora.transaction.Transaction.ValidationResult;
|
||||||
import org.qora.transform.TransformationException;
|
import org.qora.transform.TransformationException;
|
||||||
@ -554,9 +555,12 @@ public class AssetsResource {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ApiErrors({
|
@ApiErrors({
|
||||||
ApiError.TRANSFORMATION_ERROR, ApiError.REPOSITORY_ISSUE, ApiError.TRANSACTION_INVALID
|
ApiError.NON_PRODUCTION, ApiError.TRANSFORMATION_ERROR, ApiError.REPOSITORY_ISSUE, ApiError.TRANSACTION_INVALID
|
||||||
})
|
})
|
||||||
public String cancelOrder(CancelAssetOrderTransactionData transactionData) {
|
public String cancelOrder(CancelAssetOrderTransactionData transactionData) {
|
||||||
|
if (Settings.getInstance().isRestrictedApi())
|
||||||
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.NON_PRODUCTION);
|
||||||
|
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
Transaction transaction = Transaction.fromData(repository, transactionData);
|
Transaction transaction = Transaction.fromData(repository, transactionData);
|
||||||
|
|
||||||
@ -599,9 +603,12 @@ public class AssetsResource {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ApiErrors({
|
@ApiErrors({
|
||||||
ApiError.TRANSFORMATION_ERROR, ApiError.REPOSITORY_ISSUE, ApiError.TRANSACTION_INVALID
|
ApiError.NON_PRODUCTION, ApiError.TRANSFORMATION_ERROR, ApiError.REPOSITORY_ISSUE, ApiError.TRANSACTION_INVALID
|
||||||
})
|
})
|
||||||
public String issueAsset(IssueAssetTransactionData transactionData) {
|
public String issueAsset(IssueAssetTransactionData transactionData) {
|
||||||
|
if (Settings.getInstance().isRestrictedApi())
|
||||||
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.NON_PRODUCTION);
|
||||||
|
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
Transaction transaction = Transaction.fromData(repository, transactionData);
|
Transaction transaction = Transaction.fromData(repository, transactionData);
|
||||||
|
|
||||||
@ -644,9 +651,12 @@ public class AssetsResource {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ApiErrors({
|
@ApiErrors({
|
||||||
ApiError.TRANSFORMATION_ERROR, ApiError.REPOSITORY_ISSUE, ApiError.TRANSACTION_INVALID
|
ApiError.NON_PRODUCTION, ApiError.TRANSFORMATION_ERROR, ApiError.REPOSITORY_ISSUE, ApiError.TRANSACTION_INVALID
|
||||||
})
|
})
|
||||||
public String createOrder(CreateAssetOrderTransactionData transactionData) {
|
public String createOrder(CreateAssetOrderTransactionData transactionData) {
|
||||||
|
if (Settings.getInstance().isRestrictedApi())
|
||||||
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.NON_PRODUCTION);
|
||||||
|
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
Transaction transaction = Transaction.fromData(repository, transactionData);
|
Transaction transaction = Transaction.fromData(repository, transactionData);
|
||||||
|
|
||||||
|
@ -51,6 +51,7 @@ import org.qora.data.transaction.UpdateGroupTransactionData;
|
|||||||
import org.qora.repository.DataException;
|
import org.qora.repository.DataException;
|
||||||
import org.qora.repository.Repository;
|
import org.qora.repository.Repository;
|
||||||
import org.qora.repository.RepositoryManager;
|
import org.qora.repository.RepositoryManager;
|
||||||
|
import org.qora.settings.Settings;
|
||||||
import org.qora.transaction.Transaction;
|
import org.qora.transaction.Transaction;
|
||||||
import org.qora.transaction.Transaction.ValidationResult;
|
import org.qora.transaction.Transaction.ValidationResult;
|
||||||
import org.qora.transform.TransformationException;
|
import org.qora.transform.TransformationException;
|
||||||
@ -264,8 +265,11 @@ public class GroupsResource {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ApiErrors({ApiError.TRANSACTION_INVALID, ApiError.TRANSFORMATION_ERROR, ApiError.REPOSITORY_ISSUE})
|
@ApiErrors({ApiError.NON_PRODUCTION, ApiError.TRANSACTION_INVALID, ApiError.TRANSFORMATION_ERROR, ApiError.REPOSITORY_ISSUE})
|
||||||
public String createGroup(CreateGroupTransactionData transactionData) {
|
public String createGroup(CreateGroupTransactionData transactionData) {
|
||||||
|
if (Settings.getInstance().isRestrictedApi())
|
||||||
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.NON_PRODUCTION);
|
||||||
|
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
Transaction transaction = Transaction.fromData(repository, transactionData);
|
Transaction transaction = Transaction.fromData(repository, transactionData);
|
||||||
|
|
||||||
@ -307,8 +311,11 @@ public class GroupsResource {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ApiErrors({ApiError.TRANSACTION_INVALID, ApiError.TRANSFORMATION_ERROR, ApiError.REPOSITORY_ISSUE})
|
@ApiErrors({ApiError.NON_PRODUCTION, ApiError.TRANSACTION_INVALID, ApiError.TRANSFORMATION_ERROR, ApiError.REPOSITORY_ISSUE})
|
||||||
public String updateGroup(UpdateGroupTransactionData transactionData) {
|
public String updateGroup(UpdateGroupTransactionData transactionData) {
|
||||||
|
if (Settings.getInstance().isRestrictedApi())
|
||||||
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.NON_PRODUCTION);
|
||||||
|
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
Transaction transaction = Transaction.fromData(repository, transactionData);
|
Transaction transaction = Transaction.fromData(repository, transactionData);
|
||||||
|
|
||||||
@ -350,8 +357,11 @@ public class GroupsResource {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ApiErrors({ApiError.TRANSACTION_INVALID, ApiError.TRANSFORMATION_ERROR, ApiError.REPOSITORY_ISSUE})
|
@ApiErrors({ApiError.NON_PRODUCTION, ApiError.TRANSACTION_INVALID, ApiError.TRANSFORMATION_ERROR, ApiError.REPOSITORY_ISSUE})
|
||||||
public String addGroupAdmin(AddGroupAdminTransactionData transactionData) {
|
public String addGroupAdmin(AddGroupAdminTransactionData transactionData) {
|
||||||
|
if (Settings.getInstance().isRestrictedApi())
|
||||||
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.NON_PRODUCTION);
|
||||||
|
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
Transaction transaction = Transaction.fromData(repository, transactionData);
|
Transaction transaction = Transaction.fromData(repository, transactionData);
|
||||||
|
|
||||||
@ -393,8 +403,11 @@ public class GroupsResource {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ApiErrors({ApiError.TRANSACTION_INVALID, ApiError.TRANSFORMATION_ERROR, ApiError.REPOSITORY_ISSUE})
|
@ApiErrors({ApiError.NON_PRODUCTION, ApiError.TRANSACTION_INVALID, ApiError.TRANSFORMATION_ERROR, ApiError.REPOSITORY_ISSUE})
|
||||||
public String removeGroupAdmin(RemoveGroupAdminTransactionData transactionData) {
|
public String removeGroupAdmin(RemoveGroupAdminTransactionData transactionData) {
|
||||||
|
if (Settings.getInstance().isRestrictedApi())
|
||||||
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.NON_PRODUCTION);
|
||||||
|
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
Transaction transaction = Transaction.fromData(repository, transactionData);
|
Transaction transaction = Transaction.fromData(repository, transactionData);
|
||||||
|
|
||||||
@ -436,8 +449,11 @@ public class GroupsResource {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ApiErrors({ApiError.TRANSACTION_INVALID, ApiError.TRANSFORMATION_ERROR, ApiError.REPOSITORY_ISSUE})
|
@ApiErrors({ApiError.NON_PRODUCTION, ApiError.TRANSACTION_INVALID, ApiError.TRANSFORMATION_ERROR, ApiError.REPOSITORY_ISSUE})
|
||||||
public String groupBan(GroupBanTransactionData transactionData) {
|
public String groupBan(GroupBanTransactionData transactionData) {
|
||||||
|
if (Settings.getInstance().isRestrictedApi())
|
||||||
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.NON_PRODUCTION);
|
||||||
|
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
Transaction transaction = Transaction.fromData(repository, transactionData);
|
Transaction transaction = Transaction.fromData(repository, transactionData);
|
||||||
|
|
||||||
@ -479,8 +495,11 @@ public class GroupsResource {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ApiErrors({ApiError.TRANSACTION_INVALID, ApiError.TRANSFORMATION_ERROR, ApiError.REPOSITORY_ISSUE})
|
@ApiErrors({ApiError.NON_PRODUCTION, ApiError.TRANSACTION_INVALID, ApiError.TRANSFORMATION_ERROR, ApiError.REPOSITORY_ISSUE})
|
||||||
public String cancelGroupBan(CancelGroupBanTransactionData transactionData) {
|
public String cancelGroupBan(CancelGroupBanTransactionData transactionData) {
|
||||||
|
if (Settings.getInstance().isRestrictedApi())
|
||||||
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.NON_PRODUCTION);
|
||||||
|
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
Transaction transaction = Transaction.fromData(repository, transactionData);
|
Transaction transaction = Transaction.fromData(repository, transactionData);
|
||||||
|
|
||||||
@ -522,8 +541,11 @@ public class GroupsResource {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ApiErrors({ApiError.TRANSACTION_INVALID, ApiError.TRANSFORMATION_ERROR, ApiError.REPOSITORY_ISSUE})
|
@ApiErrors({ApiError.NON_PRODUCTION, ApiError.TRANSACTION_INVALID, ApiError.TRANSFORMATION_ERROR, ApiError.REPOSITORY_ISSUE})
|
||||||
public String groupKick(GroupKickTransactionData transactionData) {
|
public String groupKick(GroupKickTransactionData transactionData) {
|
||||||
|
if (Settings.getInstance().isRestrictedApi())
|
||||||
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.NON_PRODUCTION);
|
||||||
|
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
Transaction transaction = Transaction.fromData(repository, transactionData);
|
Transaction transaction = Transaction.fromData(repository, transactionData);
|
||||||
|
|
||||||
@ -565,8 +587,11 @@ public class GroupsResource {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ApiErrors({ApiError.TRANSACTION_INVALID, ApiError.TRANSFORMATION_ERROR, ApiError.REPOSITORY_ISSUE})
|
@ApiErrors({ApiError.NON_PRODUCTION, ApiError.TRANSACTION_INVALID, ApiError.TRANSFORMATION_ERROR, ApiError.REPOSITORY_ISSUE})
|
||||||
public String groupInvite(GroupInviteTransactionData transactionData) {
|
public String groupInvite(GroupInviteTransactionData transactionData) {
|
||||||
|
if (Settings.getInstance().isRestrictedApi())
|
||||||
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.NON_PRODUCTION);
|
||||||
|
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
Transaction transaction = Transaction.fromData(repository, transactionData);
|
Transaction transaction = Transaction.fromData(repository, transactionData);
|
||||||
|
|
||||||
@ -608,8 +633,11 @@ public class GroupsResource {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ApiErrors({ApiError.TRANSACTION_INVALID, ApiError.TRANSFORMATION_ERROR, ApiError.REPOSITORY_ISSUE})
|
@ApiErrors({ApiError.NON_PRODUCTION, ApiError.TRANSACTION_INVALID, ApiError.TRANSFORMATION_ERROR, ApiError.REPOSITORY_ISSUE})
|
||||||
public String cancelGroupInvite(CancelGroupInviteTransactionData transactionData) {
|
public String cancelGroupInvite(CancelGroupInviteTransactionData transactionData) {
|
||||||
|
if (Settings.getInstance().isRestrictedApi())
|
||||||
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.NON_PRODUCTION);
|
||||||
|
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
Transaction transaction = Transaction.fromData(repository, transactionData);
|
Transaction transaction = Transaction.fromData(repository, transactionData);
|
||||||
|
|
||||||
@ -651,8 +679,11 @@ public class GroupsResource {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ApiErrors({ApiError.TRANSACTION_INVALID, ApiError.TRANSFORMATION_ERROR, ApiError.REPOSITORY_ISSUE})
|
@ApiErrors({ApiError.NON_PRODUCTION, ApiError.TRANSACTION_INVALID, ApiError.TRANSFORMATION_ERROR, ApiError.REPOSITORY_ISSUE})
|
||||||
public String joinGroup(JoinGroupTransactionData transactionData) {
|
public String joinGroup(JoinGroupTransactionData transactionData) {
|
||||||
|
if (Settings.getInstance().isRestrictedApi())
|
||||||
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.NON_PRODUCTION);
|
||||||
|
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
Transaction transaction = Transaction.fromData(repository, transactionData);
|
Transaction transaction = Transaction.fromData(repository, transactionData);
|
||||||
|
|
||||||
@ -694,8 +725,11 @@ public class GroupsResource {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ApiErrors({ApiError.TRANSACTION_INVALID, ApiError.TRANSFORMATION_ERROR, ApiError.REPOSITORY_ISSUE})
|
@ApiErrors({ApiError.NON_PRODUCTION, ApiError.TRANSACTION_INVALID, ApiError.TRANSFORMATION_ERROR, ApiError.REPOSITORY_ISSUE})
|
||||||
public String leaveGroup(LeaveGroupTransactionData transactionData) {
|
public String leaveGroup(LeaveGroupTransactionData transactionData) {
|
||||||
|
if (Settings.getInstance().isRestrictedApi())
|
||||||
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.NON_PRODUCTION);
|
||||||
|
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
Transaction transaction = Transaction.fromData(repository, transactionData);
|
Transaction transaction = Transaction.fromData(repository, transactionData);
|
||||||
|
|
||||||
@ -829,8 +863,11 @@ public class GroupsResource {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ApiErrors({ApiError.TRANSACTION_INVALID, ApiError.TRANSFORMATION_ERROR, ApiError.REPOSITORY_ISSUE})
|
@ApiErrors({ApiError.NON_PRODUCTION, ApiError.TRANSACTION_INVALID, ApiError.TRANSFORMATION_ERROR, ApiError.REPOSITORY_ISSUE})
|
||||||
public String groupApproval(GroupApprovalTransactionData transactionData) {
|
public String groupApproval(GroupApprovalTransactionData transactionData) {
|
||||||
|
if (Settings.getInstance().isRestrictedApi())
|
||||||
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.NON_PRODUCTION);
|
||||||
|
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
Transaction transaction = Transaction.fromData(repository, transactionData);
|
Transaction transaction = Transaction.fromData(repository, transactionData);
|
||||||
|
|
||||||
@ -872,8 +909,11 @@ public class GroupsResource {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ApiErrors({ApiError.TRANSACTION_INVALID, ApiError.TRANSFORMATION_ERROR, ApiError.REPOSITORY_ISSUE})
|
@ApiErrors({ApiError.NON_PRODUCTION, ApiError.TRANSACTION_INVALID, ApiError.TRANSFORMATION_ERROR, ApiError.REPOSITORY_ISSUE})
|
||||||
public String setGroup(SetGroupTransactionData transactionData) {
|
public String setGroup(SetGroupTransactionData transactionData) {
|
||||||
|
if (Settings.getInstance().isRestrictedApi())
|
||||||
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.NON_PRODUCTION);
|
||||||
|
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
Transaction transaction = Transaction.fromData(repository, transactionData);
|
Transaction transaction = Transaction.fromData(repository, transactionData);
|
||||||
|
|
||||||
|
@ -36,6 +36,7 @@ import org.qora.data.transaction.UpdateNameTransactionData;
|
|||||||
import org.qora.repository.DataException;
|
import org.qora.repository.DataException;
|
||||||
import org.qora.repository.Repository;
|
import org.qora.repository.Repository;
|
||||||
import org.qora.repository.RepositoryManager;
|
import org.qora.repository.RepositoryManager;
|
||||||
|
import org.qora.settings.Settings;
|
||||||
import org.qora.transaction.Transaction;
|
import org.qora.transaction.Transaction;
|
||||||
import org.qora.transaction.Transaction.ValidationResult;
|
import org.qora.transaction.Transaction.ValidationResult;
|
||||||
import org.qora.transform.TransformationException;
|
import org.qora.transform.TransformationException;
|
||||||
@ -158,8 +159,11 @@ public class NamesResource {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ApiErrors({ApiError.TRANSACTION_INVALID, ApiError.TRANSFORMATION_ERROR, ApiError.REPOSITORY_ISSUE})
|
@ApiErrors({ApiError.NON_PRODUCTION, ApiError.TRANSACTION_INVALID, ApiError.TRANSFORMATION_ERROR, ApiError.REPOSITORY_ISSUE})
|
||||||
public String registerName(RegisterNameTransactionData transactionData) {
|
public String registerName(RegisterNameTransactionData transactionData) {
|
||||||
|
if (Settings.getInstance().isRestrictedApi())
|
||||||
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.NON_PRODUCTION);
|
||||||
|
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
Transaction transaction = Transaction.fromData(repository, transactionData);
|
Transaction transaction = Transaction.fromData(repository, transactionData);
|
||||||
|
|
||||||
@ -201,8 +205,11 @@ public class NamesResource {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ApiErrors({ApiError.TRANSACTION_INVALID, ApiError.TRANSFORMATION_ERROR, ApiError.REPOSITORY_ISSUE})
|
@ApiErrors({ApiError.NON_PRODUCTION, ApiError.TRANSACTION_INVALID, ApiError.TRANSFORMATION_ERROR, ApiError.REPOSITORY_ISSUE})
|
||||||
public String updateName(UpdateNameTransactionData transactionData) {
|
public String updateName(UpdateNameTransactionData transactionData) {
|
||||||
|
if (Settings.getInstance().isRestrictedApi())
|
||||||
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.NON_PRODUCTION);
|
||||||
|
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
Transaction transaction = Transaction.fromData(repository, transactionData);
|
Transaction transaction = Transaction.fromData(repository, transactionData);
|
||||||
|
|
||||||
@ -244,8 +251,11 @@ public class NamesResource {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ApiErrors({ApiError.TRANSACTION_INVALID, ApiError.TRANSFORMATION_ERROR, ApiError.REPOSITORY_ISSUE})
|
@ApiErrors({ApiError.NON_PRODUCTION, ApiError.TRANSACTION_INVALID, ApiError.TRANSFORMATION_ERROR, ApiError.REPOSITORY_ISSUE})
|
||||||
public String sellName(SellNameTransactionData transactionData) {
|
public String sellName(SellNameTransactionData transactionData) {
|
||||||
|
if (Settings.getInstance().isRestrictedApi())
|
||||||
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.NON_PRODUCTION);
|
||||||
|
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
Transaction transaction = Transaction.fromData(repository, transactionData);
|
Transaction transaction = Transaction.fromData(repository, transactionData);
|
||||||
|
|
||||||
@ -287,8 +297,11 @@ public class NamesResource {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ApiErrors({ApiError.TRANSACTION_INVALID, ApiError.TRANSFORMATION_ERROR, ApiError.REPOSITORY_ISSUE})
|
@ApiErrors({ApiError.NON_PRODUCTION, ApiError.TRANSACTION_INVALID, ApiError.TRANSFORMATION_ERROR, ApiError.REPOSITORY_ISSUE})
|
||||||
public String cancelSellName(CancelSellNameTransactionData transactionData) {
|
public String cancelSellName(CancelSellNameTransactionData transactionData) {
|
||||||
|
if (Settings.getInstance().isRestrictedApi())
|
||||||
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.NON_PRODUCTION);
|
||||||
|
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
Transaction transaction = Transaction.fromData(repository, transactionData);
|
Transaction transaction = Transaction.fromData(repository, transactionData);
|
||||||
|
|
||||||
@ -330,8 +343,11 @@ public class NamesResource {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ApiErrors({ApiError.TRANSACTION_INVALID, ApiError.TRANSFORMATION_ERROR, ApiError.REPOSITORY_ISSUE})
|
@ApiErrors({ApiError.NON_PRODUCTION, ApiError.TRANSACTION_INVALID, ApiError.TRANSFORMATION_ERROR, ApiError.REPOSITORY_ISSUE})
|
||||||
public String buyName(BuyNameTransactionData transactionData) {
|
public String buyName(BuyNameTransactionData transactionData) {
|
||||||
|
if (Settings.getInstance().isRestrictedApi())
|
||||||
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.NON_PRODUCTION);
|
||||||
|
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
Transaction transaction = Transaction.fromData(repository, transactionData);
|
Transaction transaction = Transaction.fromData(repository, transactionData);
|
||||||
|
|
||||||
|
@ -21,6 +21,7 @@ import org.qora.data.transaction.PaymentTransactionData;
|
|||||||
import org.qora.repository.DataException;
|
import org.qora.repository.DataException;
|
||||||
import org.qora.repository.Repository;
|
import org.qora.repository.Repository;
|
||||||
import org.qora.repository.RepositoryManager;
|
import org.qora.repository.RepositoryManager;
|
||||||
|
import org.qora.settings.Settings;
|
||||||
import org.qora.transaction.Transaction;
|
import org.qora.transaction.Transaction;
|
||||||
import org.qora.transaction.Transaction.ValidationResult;
|
import org.qora.transaction.Transaction.ValidationResult;
|
||||||
import org.qora.transform.TransformationException;
|
import org.qora.transform.TransformationException;
|
||||||
@ -60,8 +61,11 @@ public class PaymentsResource {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ApiErrors({ApiError.TRANSACTION_INVALID, ApiError.TRANSFORMATION_ERROR, ApiError.REPOSITORY_ISSUE})
|
@ApiErrors({ApiError.NON_PRODUCTION, ApiError.TRANSACTION_INVALID, ApiError.TRANSFORMATION_ERROR, ApiError.REPOSITORY_ISSUE})
|
||||||
public String buildTransaction(PaymentTransactionData transactionData) {
|
public String makePayment(PaymentTransactionData transactionData) {
|
||||||
|
if (Settings.getInstance().isRestrictedApi())
|
||||||
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.NON_PRODUCTION);
|
||||||
|
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
Transaction transaction = Transaction.fromData(repository, transactionData);
|
Transaction transaction = Transaction.fromData(repository, transactionData);
|
||||||
|
|
||||||
|
@ -34,6 +34,7 @@ import org.qora.globalization.Translator;
|
|||||||
import org.qora.repository.DataException;
|
import org.qora.repository.DataException;
|
||||||
import org.qora.repository.Repository;
|
import org.qora.repository.Repository;
|
||||||
import org.qora.repository.RepositoryManager;
|
import org.qora.repository.RepositoryManager;
|
||||||
|
import org.qora.settings.Settings;
|
||||||
import org.qora.transaction.Transaction;
|
import org.qora.transaction.Transaction;
|
||||||
import org.qora.transaction.Transaction.TransactionType;
|
import org.qora.transaction.Transaction.TransactionType;
|
||||||
import org.qora.transaction.Transaction.ValidationResult;
|
import org.qora.transaction.Transaction.ValidationResult;
|
||||||
@ -246,7 +247,7 @@ public class TransactionsResource {
|
|||||||
@ApiErrors({
|
@ApiErrors({
|
||||||
ApiError.REPOSITORY_ISSUE
|
ApiError.REPOSITORY_ISSUE
|
||||||
})
|
})
|
||||||
public List<TransactionData> getPendingTransactions(@QueryParam("groupId") Integer groupId, @Parameter(
|
public List<TransactionData> getPendingTransactions(@QueryParam("txGroupId") Integer txGroupId, @Parameter(
|
||||||
ref = "limit"
|
ref = "limit"
|
||||||
) @QueryParam("limit") Integer limit, @Parameter(
|
) @QueryParam("limit") Integer limit, @Parameter(
|
||||||
ref = "offset"
|
ref = "offset"
|
||||||
@ -254,22 +255,7 @@ public class TransactionsResource {
|
|||||||
ref = "reverse"
|
ref = "reverse"
|
||||||
) @QueryParam("reverse") Boolean reverse) {
|
) @QueryParam("reverse") Boolean reverse) {
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
List<TransactionData> transactions = repository.getTransactionRepository().getUnconfirmedTransactions(null, null, reverse);
|
return repository.getTransactionRepository().getPendingTransactions(txGroupId, limit, offset, reverse);
|
||||||
|
|
||||||
transactions.removeIf(transactionData -> {
|
|
||||||
if (groupId != null && groupId != transactionData.getTxGroupId())
|
|
||||||
return true;
|
|
||||||
|
|
||||||
try {
|
|
||||||
return !Transaction.fromData(repository, transactionData).needsGroupApproval();
|
|
||||||
} catch (DataException e) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Results slicing
|
|
||||||
|
|
||||||
return transactions;
|
|
||||||
} catch (ApiException e) {
|
} catch (ApiException e) {
|
||||||
throw e;
|
throw e;
|
||||||
} catch (DataException e) {
|
} catch (DataException e) {
|
||||||
@ -366,9 +352,12 @@ public class TransactionsResource {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ApiErrors({
|
@ApiErrors({
|
||||||
ApiError.INVALID_PRIVATE_KEY, ApiError.TRANSFORMATION_ERROR
|
ApiError.NON_PRODUCTION, ApiError.INVALID_PRIVATE_KEY, ApiError.TRANSFORMATION_ERROR
|
||||||
})
|
})
|
||||||
public String signTransaction(SimpleTransactionSignRequest signRequest) {
|
public String signTransaction(SimpleTransactionSignRequest signRequest) {
|
||||||
|
if (Settings.getInstance().isRestrictedApi())
|
||||||
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.NON_PRODUCTION);
|
||||||
|
|
||||||
if (signRequest.transactionBytes.length == 0)
|
if (signRequest.transactionBytes.length == 0)
|
||||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.JSON);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.JSON);
|
||||||
|
|
||||||
|
@ -29,6 +29,7 @@ import org.qora.api.ApiError;
|
|||||||
import org.qora.api.ApiErrors;
|
import org.qora.api.ApiErrors;
|
||||||
import org.qora.api.ApiExceptionFactory;
|
import org.qora.api.ApiExceptionFactory;
|
||||||
import org.qora.crypto.Crypto;
|
import org.qora.crypto.Crypto;
|
||||||
|
import org.qora.settings.Settings;
|
||||||
import org.qora.transaction.Transaction.TransactionType;
|
import org.qora.transaction.Transaction.TransactionType;
|
||||||
import org.qora.transform.transaction.TransactionTransformer;
|
import org.qora.transform.transaction.TransactionTransformer;
|
||||||
import org.qora.transform.transaction.TransactionTransformer.Transformation;
|
import org.qora.transform.transaction.TransactionTransformer.Transformation;
|
||||||
@ -76,8 +77,11 @@ public class UtilsResource {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ApiErrors({ApiError.INVALID_DATA})
|
@ApiErrors({ApiError.NON_PRODUCTION, ApiError.INVALID_DATA})
|
||||||
public String fromBase64(String base64) {
|
public String fromBase64(String base64) {
|
||||||
|
if (Settings.getInstance().isRestrictedApi())
|
||||||
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.NON_PRODUCTION);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return HashCode.fromBytes(Base64.getDecoder().decode(base64.trim())).toString();
|
return HashCode.fromBytes(Base64.getDecoder().decode(base64.trim())).toString();
|
||||||
} catch (IllegalArgumentException e) {
|
} catch (IllegalArgumentException e) {
|
||||||
@ -109,8 +113,11 @@ public class UtilsResource {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ApiErrors({ApiError.INVALID_DATA})
|
@ApiErrors({ApiError.NON_PRODUCTION, ApiError.INVALID_DATA})
|
||||||
public String base64from58(String base58) {
|
public String base64from58(String base58) {
|
||||||
|
if (Settings.getInstance().isRestrictedApi())
|
||||||
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.NON_PRODUCTION);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return HashCode.fromBytes(Base58.decode(base58.trim())).toString();
|
return HashCode.fromBytes(Base58.decode(base58.trim())).toString();
|
||||||
} catch (NumberFormatException e) {
|
} catch (NumberFormatException e) {
|
||||||
@ -133,7 +140,11 @@ public class UtilsResource {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ApiErrors({ApiError.NON_PRODUCTION})
|
||||||
public String toBase64(@PathParam("hex") String hex) {
|
public String toBase64(@PathParam("hex") String hex) {
|
||||||
|
if (Settings.getInstance().isRestrictedApi())
|
||||||
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.NON_PRODUCTION);
|
||||||
|
|
||||||
return Base64.getEncoder().encodeToString(HashCode.fromString(hex).asBytes());
|
return Base64.getEncoder().encodeToString(HashCode.fromString(hex).asBytes());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -152,7 +163,11 @@ public class UtilsResource {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ApiErrors({ApiError.NON_PRODUCTION})
|
||||||
public String toBase58(@PathParam("hex") String hex) {
|
public String toBase58(@PathParam("hex") String hex) {
|
||||||
|
if (Settings.getInstance().isRestrictedApi())
|
||||||
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.NON_PRODUCTION);
|
||||||
|
|
||||||
return Base58.encode(HashCode.fromString(hex).asBytes());
|
return Base58.encode(HashCode.fromString(hex).asBytes());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -173,7 +188,11 @@ public class UtilsResource {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ApiErrors({ApiError.NON_PRODUCTION})
|
||||||
public String random(@QueryParam("length") Integer length) {
|
public String random(@QueryParam("length") Integer length) {
|
||||||
|
if (Settings.getInstance().isRestrictedApi())
|
||||||
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.NON_PRODUCTION);
|
||||||
|
|
||||||
if (length == null)
|
if (length == null)
|
||||||
length = 32;
|
length = 32;
|
||||||
|
|
||||||
@ -200,8 +219,11 @@ public class UtilsResource {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ApiErrors({ApiError.INVALID_DATA})
|
@ApiErrors({ApiError.NON_PRODUCTION, ApiError.INVALID_DATA})
|
||||||
public String getMnemonic(@QueryParam("entropy") String suppliedEntropy) {
|
public String getMnemonic(@QueryParam("entropy") String suppliedEntropy) {
|
||||||
|
if (Settings.getInstance().isRestrictedApi())
|
||||||
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.NON_PRODUCTION);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* BIP39 word lists have 2048 entries so can be represented by 11 bits.
|
* BIP39 word lists have 2048 entries so can be represented by 11 bits.
|
||||||
* UUID (128bits) and another 4 bits gives 132 bits.
|
* UUID (128bits) and another 4 bits gives 132 bits.
|
||||||
@ -266,7 +288,11 @@ public class UtilsResource {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ApiErrors({ApiError.NON_PRODUCTION})
|
||||||
public String fromMnemonic(String mnemonic) {
|
public String fromMnemonic(String mnemonic) {
|
||||||
|
if (Settings.getInstance().isRestrictedApi())
|
||||||
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.NON_PRODUCTION);
|
||||||
|
|
||||||
if (mnemonic.isEmpty())
|
if (mnemonic.isEmpty())
|
||||||
return "false";
|
return "false";
|
||||||
|
|
||||||
@ -308,8 +334,11 @@ public class UtilsResource {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ApiErrors({ApiError.INVALID_DATA})
|
@ApiErrors({ApiError.NON_PRODUCTION, ApiError.INVALID_DATA})
|
||||||
public String privateKey(@PathParam("entropy") String entropy58) {
|
public String privateKey(@PathParam("entropy") String entropy58) {
|
||||||
|
if (Settings.getInstance().isRestrictedApi())
|
||||||
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.NON_PRODUCTION);
|
||||||
|
|
||||||
byte[] entropy;
|
byte[] entropy;
|
||||||
try {
|
try {
|
||||||
entropy = Base58.decode(entropy58);
|
entropy = Base58.decode(entropy58);
|
||||||
@ -341,8 +370,11 @@ public class UtilsResource {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ApiErrors({ApiError.INVALID_DATA})
|
@ApiErrors({ApiError.NON_PRODUCTION, ApiError.INVALID_DATA})
|
||||||
public String publicKey(@PathParam("privateKey") String privateKey58) {
|
public String publicKey(@PathParam("privateKey") String privateKey58) {
|
||||||
|
if (Settings.getInstance().isRestrictedApi())
|
||||||
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.NON_PRODUCTION);
|
||||||
|
|
||||||
byte[] privateKey;
|
byte[] privateKey;
|
||||||
try {
|
try {
|
||||||
privateKey = Base58.decode(privateKey58);
|
privateKey = Base58.decode(privateKey58);
|
||||||
|
@ -268,7 +268,7 @@ public class QoraATAPI extends API {
|
|||||||
byte[] reference = this.getLastReference();
|
byte[] reference = this.getLastReference();
|
||||||
BigDecimal amount = BigDecimal.valueOf(unscaledAmount, 8);
|
BigDecimal amount = BigDecimal.valueOf(unscaledAmount, 8);
|
||||||
|
|
||||||
ATTransactionData atTransactionData = new ATTransactionData(timestamp, Group.DEFAULT_GROUP, reference, this.atData.getATAddress(),
|
ATTransactionData atTransactionData = new ATTransactionData(timestamp, Group.NO_GROUP, reference, this.atData.getATAddress(),
|
||||||
recipient.getAddress(), amount, this.atData.getAssetId(), new byte[0], BigDecimal.ZERO.setScale(8));
|
recipient.getAddress(), amount, this.atData.getAssetId(), new byte[0], BigDecimal.ZERO.setScale(8));
|
||||||
AtTransaction atTransaction = new AtTransaction(this.repository, atTransactionData);
|
AtTransaction atTransaction = new AtTransaction(this.repository, atTransactionData);
|
||||||
|
|
||||||
@ -286,7 +286,7 @@ public class QoraATAPI extends API {
|
|||||||
long timestamp = this.getNextTransactionTimestamp();
|
long timestamp = this.getNextTransactionTimestamp();
|
||||||
byte[] reference = this.getLastReference();
|
byte[] reference = this.getLastReference();
|
||||||
|
|
||||||
ATTransactionData atTransactionData = new ATTransactionData(timestamp, Group.DEFAULT_GROUP, reference,
|
ATTransactionData atTransactionData = new ATTransactionData(timestamp, Group.NO_GROUP, reference,
|
||||||
this.atData.getATAddress(), recipient.getAddress(), BigDecimal.ZERO, this.atData.getAssetId(), message, BigDecimal.ZERO.setScale(8));
|
this.atData.getATAddress(), recipient.getAddress(), BigDecimal.ZERO, this.atData.getAssetId(), message, BigDecimal.ZERO.setScale(8));
|
||||||
AtTransaction atTransaction = new AtTransaction(this.repository, atTransactionData);
|
AtTransaction atTransaction = new AtTransaction(this.repository, atTransactionData);
|
||||||
|
|
||||||
@ -312,7 +312,7 @@ public class QoraATAPI extends API {
|
|||||||
byte[] reference = this.getLastReference();
|
byte[] reference = this.getLastReference();
|
||||||
BigDecimal amount = BigDecimal.valueOf(finalBalance, 8);
|
BigDecimal amount = BigDecimal.valueOf(finalBalance, 8);
|
||||||
|
|
||||||
ATTransactionData atTransactionData = new ATTransactionData(timestamp, Group.DEFAULT_GROUP, reference, this.atData.getATAddress(),
|
ATTransactionData atTransactionData = new ATTransactionData(timestamp, Group.NO_GROUP, reference, this.atData.getATAddress(),
|
||||||
creator.getAddress(), amount, this.atData.getAssetId(), new byte[0], BigDecimal.ZERO.setScale(8));
|
creator.getAddress(), amount, this.atData.getAssetId(), new byte[0], BigDecimal.ZERO.setScale(8));
|
||||||
AtTransaction atTransaction = new AtTransaction(this.repository, atTransactionData);
|
AtTransaction atTransaction = new AtTransaction(this.repository, atTransactionData);
|
||||||
|
|
||||||
|
@ -83,6 +83,7 @@ public class Block {
|
|||||||
TRANSACTION_INVALID(52),
|
TRANSACTION_INVALID(52),
|
||||||
TRANSACTION_PROCESSING_FAILED(53),
|
TRANSACTION_PROCESSING_FAILED(53),
|
||||||
TRANSACTION_ALREADY_PROCESSED(54),
|
TRANSACTION_ALREADY_PROCESSED(54),
|
||||||
|
TRANSACTION_NEEDS_APPROVAL(55),
|
||||||
AT_STATES_MISMATCH(61);
|
AT_STATES_MISMATCH(61);
|
||||||
|
|
||||||
public final int value;
|
public final int value;
|
||||||
@ -834,6 +835,10 @@ public class Block {
|
|||||||
if (this.repository.getTransactionRepository().isConfirmed(transaction.getTransactionData().getSignature()))
|
if (this.repository.getTransactionRepository().isConfirmed(transaction.getTransactionData().getSignature()))
|
||||||
return ValidationResult.TRANSACTION_ALREADY_PROCESSED;
|
return ValidationResult.TRANSACTION_ALREADY_PROCESSED;
|
||||||
|
|
||||||
|
// Check transaction doesn't still need approval
|
||||||
|
if (transaction.needsGroupApproval() && !transaction.meetsGroupApprovalThreshold())
|
||||||
|
return ValidationResult.TRANSACTION_NEEDS_APPROVAL;
|
||||||
|
|
||||||
// Check transaction is even valid
|
// Check transaction is even valid
|
||||||
// NOTE: in Gen1 there was an extra block height passed to DeployATTransaction.isValid
|
// NOTE: in Gen1 there was an extra block height passed to DeployATTransaction.isValid
|
||||||
Transaction.ValidationResult validationResult = transaction.isValid();
|
Transaction.ValidationResult validationResult = transaction.isValid();
|
||||||
|
@ -36,11 +36,12 @@ public class BlockChain {
|
|||||||
private static BlockChain instance = null;
|
private static BlockChain instance = null;
|
||||||
|
|
||||||
// Properties
|
// Properties
|
||||||
|
private boolean isTestNet;
|
||||||
private BigDecimal unitFee;
|
private BigDecimal unitFee;
|
||||||
private BigDecimal maxBytesPerUnitFee;
|
private BigDecimal maxBytesPerUnitFee;
|
||||||
private BigDecimal minFeePerByte;
|
private BigDecimal minFeePerByte;
|
||||||
/** Maximum coin supply. */
|
/** Maximum coin supply. */
|
||||||
private BigDecimal maxBalance;;
|
private BigDecimal maxBalance;
|
||||||
/** Number of blocks between recalculating block's generating balance. */
|
/** Number of blocks between recalculating block's generating balance. */
|
||||||
private int blockDifficultyInterval;
|
private int blockDifficultyInterval;
|
||||||
/** Minimum target time between blocks, in seconds. */
|
/** Minimum target time between blocks, in seconds. */
|
||||||
@ -51,7 +52,7 @@ public class BlockChain {
|
|||||||
private long blockTimestampMargin;
|
private long blockTimestampMargin;
|
||||||
/** Whether transactions with txGroupId of NO_GROUP are allowed */
|
/** Whether transactions with txGroupId of NO_GROUP are allowed */
|
||||||
private boolean grouplessAllowed;
|
private boolean grouplessAllowed;
|
||||||
/** Default groupID when txGroupID and account's default groupID are both zero */
|
/** Default groupID when account's default groupID isn't set */
|
||||||
private int defaultGroupId = Group.NO_GROUP;
|
private int defaultGroupId = Group.NO_GROUP;
|
||||||
/** Map of which blockchain features are enabled when (height/timestamp) */
|
/** Map of which blockchain features are enabled when (height/timestamp) */
|
||||||
private Map<String, Map<FeatureValueType, Long>> featureTriggers;
|
private Map<String, Map<FeatureValueType, Long>> featureTriggers;
|
||||||
@ -74,6 +75,10 @@ public class BlockChain {
|
|||||||
|
|
||||||
// Getters / setters
|
// Getters / setters
|
||||||
|
|
||||||
|
public boolean getIsTestNet() {
|
||||||
|
return this.isTestNet;
|
||||||
|
}
|
||||||
|
|
||||||
public BigDecimal getUnitFee() {
|
public BigDecimal getUnitFee() {
|
||||||
return this.unitFee;
|
return this.unitFee;
|
||||||
}
|
}
|
||||||
@ -190,11 +195,15 @@ public class BlockChain {
|
|||||||
|
|
||||||
// If groupless is not allowed the defaultGroupId needs to be set
|
// If groupless is not allowed the defaultGroupId needs to be set
|
||||||
// XXX we could also check groupID exists, or at least created in genesis block, or in blockchain config
|
// XXX we could also check groupID exists, or at least created in genesis block, or in blockchain config
|
||||||
if (!grouplessAllowed && (defaultGroupId == null || defaultGroupId == Group.DEFAULT_GROUP || defaultGroupId == Group.NO_GROUP)) {
|
if (!grouplessAllowed && (defaultGroupId == null || defaultGroupId == Group.NO_GROUP)) {
|
||||||
LOGGER.error("defaultGroupId must be set to valid groupID in blockchain config if groupless transactions are not allowed");
|
LOGGER.error("defaultGroupId must be set to valid groupID in blockchain config if groupless transactions are not allowed");
|
||||||
throw new RuntimeException("defaultGroupId must be set to valid groupID in blockchain config if groupless transactions are not allowed");
|
throw new RuntimeException("defaultGroupId must be set to valid groupID in blockchain config if groupless transactions are not allowed");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
boolean isTestNet = true;
|
||||||
|
if (json.containsKey("isTestNet"))
|
||||||
|
isTestNet = (Boolean) Settings.getTypedJson(json, "isTestNet", Boolean.class);
|
||||||
|
|
||||||
BigDecimal unitFee = Settings.getJsonBigDecimal(json, "unitFee");
|
BigDecimal unitFee = Settings.getJsonBigDecimal(json, "unitFee");
|
||||||
long maxBytesPerUnitFee = (Long) Settings.getTypedJson(json, "maxBytesPerUnitFee", Long.class);
|
long maxBytesPerUnitFee = (Long) Settings.getTypedJson(json, "maxBytesPerUnitFee", Long.class);
|
||||||
BigDecimal maxBalance = Settings.getJsonBigDecimal(json, "coinSupply");
|
BigDecimal maxBalance = Settings.getJsonBigDecimal(json, "coinSupply");
|
||||||
@ -229,6 +238,7 @@ public class BlockChain {
|
|||||||
}
|
}
|
||||||
|
|
||||||
instance = new BlockChain();
|
instance = new BlockChain();
|
||||||
|
instance.isTestNet = isTestNet;
|
||||||
instance.unitFee = unitFee;
|
instance.unitFee = unitFee;
|
||||||
instance.maxBytesPerUnitFee = BigDecimal.valueOf(maxBytesPerUnitFee).setScale(8);
|
instance.maxBytesPerUnitFee = BigDecimal.valueOf(maxBytesPerUnitFee).setScale(8);
|
||||||
instance.minFeePerByte = unitFee.divide(instance.maxBytesPerUnitFee, MathContext.DECIMAL32);
|
instance.minFeePerByte = unitFee.divide(instance.maxBytesPerUnitFee, MathContext.DECIMAL32);
|
||||||
|
@ -169,16 +169,13 @@ public class BlockGenerator extends Thread {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Ignore transactions that have not met group-admin approval threshold
|
// Ignore transactions that have not met group-admin approval threshold
|
||||||
if (transaction.needsGroupApproval()) {
|
if (transaction.needsGroupApproval() && !transaction.meetsGroupApprovalThreshold()) {
|
||||||
unconfirmedTransactions.remove(i);
|
unconfirmedTransactions.remove(i);
|
||||||
--i;
|
--i;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Discard any repository changes used to aid transaction validity checks
|
|
||||||
// repository.discardChanges(); // XXX possibly not needed any more thanks to savepoints?
|
|
||||||
|
|
||||||
// Attempt to add transactions until block is full, or we run out
|
// Attempt to add transactions until block is full, or we run out
|
||||||
// If a transaction makes the block invalid then skip it and it'll either expire or be in next block.
|
// If a transaction makes the block invalid then skip it and it'll either expire or be in next block.
|
||||||
for (TransactionData transactionData : unconfirmedTransactions) {
|
for (TransactionData transactionData : unconfirmedTransactions) {
|
||||||
|
@ -29,7 +29,7 @@ public class AccountData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public AccountData(String address) {
|
public AccountData(String address) {
|
||||||
this(address, null, null, Group.DEFAULT_GROUP);
|
this(address, null, null, Group.NO_GROUP);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Getters/Setters
|
// Getters/Setters
|
||||||
|
@ -21,6 +21,8 @@ public class GroupData {
|
|||||||
private Long updated;
|
private Long updated;
|
||||||
private boolean isOpen;
|
private boolean isOpen;
|
||||||
private ApprovalThreshold approvalThreshold;
|
private ApprovalThreshold approvalThreshold;
|
||||||
|
private int minimumBlockDelay;
|
||||||
|
private int maximumBlockDelay;
|
||||||
/** Reference to CREATE_GROUP or UPDATE_GROUP transaction, used to rebuild group during orphaning. */
|
/** Reference to CREATE_GROUP or UPDATE_GROUP transaction, used to rebuild group during orphaning. */
|
||||||
// No need to ever expose this via API
|
// No need to ever expose this via API
|
||||||
@XmlTransient
|
@XmlTransient
|
||||||
@ -34,7 +36,7 @@ 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, byte[] reference) {
|
public GroupData(Integer groupId, String owner, String name, String description, long created, Long updated, boolean isOpen, ApprovalThreshold approvalThreshold, int minBlockDelay, int maxBlockDelay, byte[] reference) {
|
||||||
this.groupId = groupId;
|
this.groupId = groupId;
|
||||||
this.owner = owner;
|
this.owner = owner;
|
||||||
this.groupName = name;
|
this.groupName = name;
|
||||||
@ -44,11 +46,13 @@ public class GroupData {
|
|||||||
this.isOpen = isOpen;
|
this.isOpen = isOpen;
|
||||||
this.approvalThreshold = approvalThreshold;
|
this.approvalThreshold = approvalThreshold;
|
||||||
this.reference = reference;
|
this.reference = reference;
|
||||||
|
this.minimumBlockDelay = minBlockDelay;
|
||||||
|
this.maximumBlockDelay = maxBlockDelay;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 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, byte[] reference) {
|
public GroupData(String owner, String name, String description, long created, boolean isOpen, ApprovalThreshold approvalThreshold, int minBlockDelay, int maxBlockDelay, byte[] reference) {
|
||||||
this(null, owner, name, description, created, null, isOpen, approvalThreshold, reference);
|
this(null, owner, name, description, created, null, isOpen, approvalThreshold, minBlockDelay, maxBlockDelay, reference);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Getters / setters
|
// Getters / setters
|
||||||
@ -117,4 +121,12 @@ public class GroupData {
|
|||||||
this.approvalThreshold = approvalThreshold;
|
this.approvalThreshold = approvalThreshold;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getMinimumBlockDelay() {
|
||||||
|
return this.minimumBlockDelay;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getMaximumBlockDelay() {
|
||||||
|
return this.maximumBlockDelay;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -52,6 +52,10 @@ public class CreateGroupTransactionData extends TransactionData {
|
|||||||
description = "how many group admins are required to approve group member transactions"
|
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")
|
||||||
|
private int minimumBlockDelay;
|
||||||
|
@Schema(description = "maximum block delay before which transaction approval must be reached")
|
||||||
|
private int maximumBlockDelay;
|
||||||
|
|
||||||
// Constructors
|
// Constructors
|
||||||
|
|
||||||
@ -61,7 +65,7 @@ public class CreateGroupTransactionData extends TransactionData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public CreateGroupTransactionData(long timestamp, int txGroupId, byte[] reference, byte[] creatorPublicKey, String owner, String groupName, String description,
|
public CreateGroupTransactionData(long timestamp, int txGroupId, byte[] reference, byte[] creatorPublicKey, String owner, String groupName, String description,
|
||||||
boolean isOpen, ApprovalThreshold approvalThreshold, Integer groupId, BigDecimal fee, byte[] signature) {
|
boolean isOpen, ApprovalThreshold approvalThreshold, int minimumBlockDelay, int maximumBlockDelay, Integer groupId, BigDecimal fee, byte[] signature) {
|
||||||
super(TransactionType.CREATE_GROUP, timestamp, txGroupId, reference, creatorPublicKey, fee, signature);
|
super(TransactionType.CREATE_GROUP, timestamp, txGroupId, reference, creatorPublicKey, fee, signature);
|
||||||
|
|
||||||
this.creatorPublicKey = creatorPublicKey;
|
this.creatorPublicKey = creatorPublicKey;
|
||||||
@ -70,6 +74,8 @@ public class CreateGroupTransactionData extends TransactionData {
|
|||||||
this.description = description;
|
this.description = description;
|
||||||
this.isOpen = isOpen;
|
this.isOpen = isOpen;
|
||||||
this.approvalThreshold = approvalThreshold;
|
this.approvalThreshold = approvalThreshold;
|
||||||
|
this.minimumBlockDelay = minimumBlockDelay;
|
||||||
|
this.maximumBlockDelay = maximumBlockDelay;
|
||||||
this.groupId = groupId;
|
this.groupId = groupId;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -95,6 +101,14 @@ public class CreateGroupTransactionData extends TransactionData {
|
|||||||
return this.approvalThreshold;
|
return this.approvalThreshold;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getMinimumBlockDelay() {
|
||||||
|
return this.minimumBlockDelay;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getMaximumBlockDelay() {
|
||||||
|
return this.maximumBlockDelay;
|
||||||
|
}
|
||||||
|
|
||||||
public Integer getGroupId() {
|
public Integer getGroupId() {
|
||||||
return this.groupId;
|
return this.groupId;
|
||||||
}
|
}
|
||||||
|
@ -51,6 +51,10 @@ public class UpdateGroupTransactionData extends TransactionData {
|
|||||||
description = "new group member transaction approval threshold"
|
description = "new group member transaction approval threshold"
|
||||||
)
|
)
|
||||||
private ApprovalThreshold newApprovalThreshold;
|
private ApprovalThreshold newApprovalThreshold;
|
||||||
|
@Schema(description = "new minimum block delay before approval takes effect")
|
||||||
|
private int newMinimumBlockDelay;
|
||||||
|
@Schema(description = "new maximum block delay before which transaction approval must be reached")
|
||||||
|
private int newMaximumBlockDelay;
|
||||||
/** Reference to CREATE_GROUP or UPDATE_GROUP transaction, used to rebuild group during orphaning. */
|
/** Reference to CREATE_GROUP or UPDATE_GROUP transaction, used to rebuild group during orphaning. */
|
||||||
// For internal use when orphaning
|
// For internal use when orphaning
|
||||||
@XmlTransient
|
@XmlTransient
|
||||||
@ -71,7 +75,7 @@ public class UpdateGroupTransactionData extends TransactionData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public UpdateGroupTransactionData(long timestamp, int txGroupId, byte[] reference, byte[] ownerPublicKey, int groupId,
|
public UpdateGroupTransactionData(long timestamp, int txGroupId, byte[] reference, byte[] ownerPublicKey, int groupId,
|
||||||
String newOwner, String newDescription, boolean newIsOpen, ApprovalThreshold newApprovalThreshold, byte[] groupReference, BigDecimal fee, byte[] signature) {
|
String newOwner, String newDescription, boolean newIsOpen, ApprovalThreshold newApprovalThreshold, int newMinimumBlockDelay, int newMaximumBlockDelay, byte[] groupReference, BigDecimal fee, byte[] signature) {
|
||||||
super(TransactionType.UPDATE_GROUP, timestamp, txGroupId, reference, ownerPublicKey, fee, signature);
|
super(TransactionType.UPDATE_GROUP, timestamp, txGroupId, reference, ownerPublicKey, fee, signature);
|
||||||
|
|
||||||
this.ownerPublicKey = ownerPublicKey;
|
this.ownerPublicKey = ownerPublicKey;
|
||||||
@ -80,13 +84,15 @@ public class UpdateGroupTransactionData extends TransactionData {
|
|||||||
this.newDescription = newDescription;
|
this.newDescription = newDescription;
|
||||||
this.newIsOpen = newIsOpen;
|
this.newIsOpen = newIsOpen;
|
||||||
this.newApprovalThreshold = newApprovalThreshold;
|
this.newApprovalThreshold = newApprovalThreshold;
|
||||||
|
this.newMinimumBlockDelay = newMinimumBlockDelay;
|
||||||
|
this.newMaximumBlockDelay = newMaximumBlockDelay;
|
||||||
this.groupReference = groupReference;
|
this.groupReference = groupReference;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Constructor typically used after deserialization */
|
/** Constructor typically used after deserialization */
|
||||||
public UpdateGroupTransactionData(long timestamp, int txGroupId, byte[] reference, byte[] ownerPublicKey, int groupId,
|
public UpdateGroupTransactionData(long timestamp, int txGroupId, byte[] reference, byte[] ownerPublicKey, int groupId,
|
||||||
String newOwner, String newDescription, boolean newIsOpen, ApprovalThreshold newApprovalThreshold, BigDecimal fee, byte[] signature) {
|
String newOwner, String newDescription, boolean newIsOpen, ApprovalThreshold newApprovalThreshold, int newMinimumBlockDelay, int newMaximumBlockDelay, BigDecimal fee, byte[] signature) {
|
||||||
this(timestamp, txGroupId, reference, ownerPublicKey, groupId, newOwner, newDescription, newIsOpen, newApprovalThreshold, null, fee, signature);
|
this(timestamp, txGroupId, reference, ownerPublicKey, groupId, newOwner, newDescription, newIsOpen, newApprovalThreshold, newMinimumBlockDelay, newMaximumBlockDelay, null, fee, signature);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Getters / setters
|
// Getters / setters
|
||||||
@ -115,6 +121,14 @@ public class UpdateGroupTransactionData extends TransactionData {
|
|||||||
return this.newApprovalThreshold;
|
return this.newApprovalThreshold;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getNewMinimumBlockDelay() {
|
||||||
|
return this.newMinimumBlockDelay;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getNewMaximumBlockDelay() {
|
||||||
|
return this.newMaximumBlockDelay;
|
||||||
|
}
|
||||||
|
|
||||||
public byte[] getGroupReference() {
|
public byte[] getGroupReference() {
|
||||||
return this.groupReference;
|
return this.groupReference;
|
||||||
}
|
}
|
||||||
|
@ -46,7 +46,8 @@ public class Group {
|
|||||||
public final int value;
|
public final int value;
|
||||||
public final boolean isPercentage;
|
public final boolean isPercentage;
|
||||||
|
|
||||||
private final static Map<Integer, ApprovalThreshold> map = stream(ApprovalThreshold.values()).collect(toMap(threshold -> threshold.value, threshold -> threshold));
|
private final static Map<Integer, ApprovalThreshold> map = stream(ApprovalThreshold.values())
|
||||||
|
.collect(toMap(threshold -> threshold.value, threshold -> threshold));
|
||||||
|
|
||||||
ApprovalThreshold(int value, boolean isPercentage) {
|
ApprovalThreshold(int value, boolean isPercentage) {
|
||||||
this.value = value;
|
this.value = value;
|
||||||
@ -65,23 +66,25 @@ public class Group {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns whether transaction need approval.
|
* Returns whether transaction meets approval threshold.
|
||||||
*
|
*
|
||||||
* @param repository
|
* @param repository
|
||||||
* @param txGroupId transaction's groupID
|
* @param txGroupId
|
||||||
* @param signature transaction's signature
|
* transaction's groupID
|
||||||
|
* @param signature
|
||||||
|
* transaction's signature
|
||||||
* @return true if approval still needed, false if transaction can be included in block
|
* @return true if approval still needed, false if transaction can be included in block
|
||||||
* @throws DataException
|
* @throws DataException
|
||||||
*/
|
*/
|
||||||
public boolean needsApproval(Repository repository, int txGroupId, byte[] signature) throws DataException {
|
public boolean meetsApprovalThreshold(Repository repository, int txGroupId, byte[] signature) throws DataException {
|
||||||
// Fetch total number of admins in group
|
// Fetch total number of admins in group
|
||||||
final int totalAdmins = repository.getGroupRepository().countGroupAdmins(txGroupId);
|
final int totalAdmins = repository.getGroupRepository().countGroupAdmins(txGroupId);
|
||||||
|
|
||||||
// Fetch total number of approvals for signature
|
// Fetch total number of approvals for signature
|
||||||
// NOT simply number of GROUP_APPROVE transactions as some may be rejecting transaction, or changed opinions
|
// NOT simply number of GROUP_APPROVE transactions as some may be rejecting transaction, or changed opinions
|
||||||
final int currentApprovals = repository.getTransactionRepository().countTransactionApprovals(txGroupId, signature);
|
final int currentApprovals = repository.getTransactionRepository().countTransactionApprovals(txGroupId, signature);
|
||||||
|
|
||||||
return !meetsTheshold(currentApprovals, totalAdmins);
|
return meetsTheshold(currentApprovals, totalAdmins);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -91,8 +94,7 @@ public class Group {
|
|||||||
private GroupData groupData;
|
private GroupData groupData;
|
||||||
|
|
||||||
// Useful constants
|
// Useful constants
|
||||||
public static final int NO_GROUP = -1;
|
public static final int NO_GROUP = 0;
|
||||||
public static final int DEFAULT_GROUP = 0;
|
|
||||||
|
|
||||||
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;
|
||||||
@ -113,7 +115,8 @@ public class Group {
|
|||||||
|
|
||||||
this.groupData = new GroupData(createGroupTransactionData.getOwner(), createGroupTransactionData.getGroupName(),
|
this.groupData = new GroupData(createGroupTransactionData.getOwner(), createGroupTransactionData.getGroupName(),
|
||||||
createGroupTransactionData.getDescription(), createGroupTransactionData.getTimestamp(), createGroupTransactionData.getIsOpen(),
|
createGroupTransactionData.getDescription(), createGroupTransactionData.getTimestamp(), createGroupTransactionData.getIsOpen(),
|
||||||
createGroupTransactionData.getApprovalThreshold(), createGroupTransactionData.getSignature());
|
createGroupTransactionData.getApprovalThreshold(), createGroupTransactionData.getMinimumBlockDelay(),
|
||||||
|
createGroupTransactionData.getMaximumBlockDelay(), createGroupTransactionData.getSignature());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -44,6 +44,15 @@ public interface BlockRepository {
|
|||||||
*/
|
*/
|
||||||
public int getHeightFromSignature(byte[] signature) throws DataException;
|
public int getHeightFromSignature(byte[] signature) throws DataException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return height of block with timestamp just before passed timestamp.
|
||||||
|
*
|
||||||
|
* @param timestamp
|
||||||
|
* @return height, or 0 if not found in blockchain.
|
||||||
|
* @throws DataException
|
||||||
|
*/
|
||||||
|
public int getHeightFromTimestamp(long timestamp) throws DataException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return highest block height from repository.
|
* Return highest block height from repository.
|
||||||
*
|
*
|
||||||
|
@ -47,6 +47,20 @@ public interface TransactionRepository {
|
|||||||
public List<TransactionData> getAssetTransactions(int assetId, ConfirmationStatus confirmationStatus, Integer limit, Integer offset, Boolean reverse)
|
public List<TransactionData> getAssetTransactions(int assetId, ConfirmationStatus confirmationStatus, Integer limit, Integer offset, Boolean reverse)
|
||||||
throws DataException;
|
throws DataException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns list of transactions pending approval, with optional txGgroupId filtering.
|
||||||
|
* <p>
|
||||||
|
* This is typically called by the API.
|
||||||
|
*
|
||||||
|
* @param txGroupId
|
||||||
|
* @param limit
|
||||||
|
* @param offset
|
||||||
|
* @param reverse
|
||||||
|
* @return list of transactions, or empty if none.
|
||||||
|
* @throws DataException
|
||||||
|
*/
|
||||||
|
public List<TransactionData> getPendingTransactions(Integer txGroupId, Integer limit, Integer offset, Boolean reverse) throws DataException;
|
||||||
|
|
||||||
/** Returns number of approvals for transaction with given signature. */
|
/** Returns number of approvals for transaction with given signature. */
|
||||||
public int countTransactionApprovals(int txGroupId, byte[] signature) throws DataException;
|
public int countTransactionApprovals(int txGroupId, byte[] signature) throws DataException;
|
||||||
|
|
||||||
|
@ -90,6 +90,18 @@ public class HSQLDBBlockRepository implements BlockRepository {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getHeightFromTimestamp(long timestamp) throws DataException {
|
||||||
|
try (ResultSet resultSet = this.repository.checkedExecute("SELECT MAX(height) FROM Blocks WHERE generation <= ?", new Timestamp(timestamp))) {
|
||||||
|
if (resultSet == null)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
return resultSet.getInt(1);
|
||||||
|
} catch (SQLException e) {
|
||||||
|
throw new DataException("Error obtaining block height from repository", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getBlockchainHeight() throws DataException {
|
public int getBlockchainHeight() throws DataException {
|
||||||
try (ResultSet resultSet = this.repository.checkedExecute("SELECT MAX(height) FROM Blocks LIMIT 1")) {
|
try (ResultSet resultSet = this.repository.checkedExecute("SELECT MAX(height) FROM Blocks LIMIT 1")) {
|
||||||
|
@ -558,6 +558,16 @@ public class HSQLDBDatabaseUpdates {
|
|||||||
+ "PRIMARY KEY (signature), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
|
+ "PRIMARY KEY (signature), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case 35:
|
||||||
|
// Group-based transaction approval min/max block delay
|
||||||
|
stmt.execute("ALTER TABLE Groups ADD COLUMN min_block_delay INT NOT NULL DEFAULT 0 BEFORE reference");
|
||||||
|
stmt.execute("ALTER TABLE Groups ADD COLUMN max_block_delay INT NOT NULL DEFAULT 1440 BEFORE reference");
|
||||||
|
stmt.execute("ALTER TABLE CreateGroupTransactions ADD COLUMN min_block_delay INT NOT NULL DEFAULT 0 BEFORE group_id");
|
||||||
|
stmt.execute("ALTER TABLE CreateGroupTransactions ADD COLUMN max_block_delay INT NOT NULL DEFAULT 1440 BEFORE group_id");
|
||||||
|
stmt.execute("ALTER TABLE UpdateGroupTransactions ADD COLUMN new_min_block_delay INT NOT NULL DEFAULT 0 BEFORE group_reference");
|
||||||
|
stmt.execute("ALTER TABLE UpdateGroupTransactions ADD COLUMN new_max_block_delay INT NOT NULL DEFAULT 1440 BEFORE group_reference");
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
// nothing to do
|
// nothing to do
|
||||||
return false;
|
return false;
|
||||||
|
@ -30,7 +30,7 @@ public class HSQLDBGroupRepository implements GroupRepository {
|
|||||||
@Override
|
@Override
|
||||||
public GroupData fromGroupId(int groupId) throws DataException {
|
public GroupData fromGroupId(int groupId) throws DataException {
|
||||||
try (ResultSet resultSet = this.repository
|
try (ResultSet resultSet = this.repository
|
||||||
.checkedExecute("SELECT group_name, owner, description, created, updated, reference, is_open, approval_threshold FROM Groups WHERE group_id = ?", groupId)) {
|
.checkedExecute("SELECT group_name, owner, description, created, updated, reference, is_open, approval_threshold, min_block_delay, max_block_delay FROM Groups WHERE group_id = ?", groupId)) {
|
||||||
if (resultSet == null)
|
if (resultSet == null)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
@ -48,7 +48,10 @@ public class HSQLDBGroupRepository implements GroupRepository {
|
|||||||
|
|
||||||
ApprovalThreshold approvalThreshold = ApprovalThreshold.valueOf(resultSet.getInt(8));
|
ApprovalThreshold approvalThreshold = ApprovalThreshold.valueOf(resultSet.getInt(8));
|
||||||
|
|
||||||
return new GroupData(groupId, owner, groupName, description, created, updated, isOpen, approvalThreshold, reference);
|
int minBlockDelay = resultSet.getInt(9);
|
||||||
|
int maxBlockDelay = resultSet.getInt(10);
|
||||||
|
|
||||||
|
return new GroupData(groupId, owner, groupName, description, created, updated, isOpen, approvalThreshold, minBlockDelay, maxBlockDelay, reference);
|
||||||
} 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);
|
||||||
}
|
}
|
||||||
@ -57,7 +60,7 @@ public class HSQLDBGroupRepository implements GroupRepository {
|
|||||||
@Override
|
@Override
|
||||||
public GroupData fromGroupName(String groupName) throws DataException {
|
public GroupData fromGroupName(String groupName) throws DataException {
|
||||||
try (ResultSet resultSet = this.repository
|
try (ResultSet resultSet = this.repository
|
||||||
.checkedExecute("SELECT group_id, owner, description, created, updated, reference, is_open, approval_threshold FROM Groups WHERE group_name = ?", groupName)) {
|
.checkedExecute("SELECT group_id, owner, description, created, updated, reference, is_open, approval_threshold, min_block_delay, max_block_delay FROM Groups WHERE group_name = ?", groupName)) {
|
||||||
if (resultSet == null)
|
if (resultSet == null)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
@ -75,7 +78,10 @@ public class HSQLDBGroupRepository implements GroupRepository {
|
|||||||
|
|
||||||
ApprovalThreshold approvalThreshold = ApprovalThreshold.valueOf(resultSet.getInt(8));
|
ApprovalThreshold approvalThreshold = ApprovalThreshold.valueOf(resultSet.getInt(8));
|
||||||
|
|
||||||
return new GroupData(groupId, owner, groupName, description, created, updated, isOpen, approvalThreshold, reference);
|
int minBlockDelay = resultSet.getInt(9);
|
||||||
|
int maxBlockDelay = resultSet.getInt(10);
|
||||||
|
|
||||||
|
return new GroupData(groupId, owner, groupName, description, created, updated, isOpen, approvalThreshold, minBlockDelay, maxBlockDelay, reference);
|
||||||
} 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);
|
||||||
}
|
}
|
||||||
@ -101,7 +107,7 @@ public class HSQLDBGroupRepository implements GroupRepository {
|
|||||||
|
|
||||||
@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 {
|
||||||
String sql = "SELECT group_id, owner, group_name, description, created, updated, reference, is_open, approval_threshold FROM Groups ORDER BY group_name";
|
String sql = "SELECT group_id, owner, group_name, description, created, updated, reference, is_open, approval_threshold, min_block_delay, max_block_delay FROM Groups ORDER BY group_name";
|
||||||
if (reverse != null && reverse)
|
if (reverse != null && reverse)
|
||||||
sql += " DESC";
|
sql += " DESC";
|
||||||
sql += HSQLDBRepository.limitOffsetSql(limit, offset);
|
sql += HSQLDBRepository.limitOffsetSql(limit, offset);
|
||||||
@ -128,7 +134,10 @@ public class HSQLDBGroupRepository implements GroupRepository {
|
|||||||
|
|
||||||
ApprovalThreshold approvalThreshold = ApprovalThreshold.valueOf(resultSet.getInt(9));
|
ApprovalThreshold approvalThreshold = ApprovalThreshold.valueOf(resultSet.getInt(9));
|
||||||
|
|
||||||
groups.add(new GroupData(groupId, owner, groupName, description, created, updated, isOpen, approvalThreshold, reference));
|
int minBlockDelay = resultSet.getInt(10);
|
||||||
|
int maxBlockDelay = resultSet.getInt(11);
|
||||||
|
|
||||||
|
groups.add(new GroupData(groupId, owner, groupName, description, created, updated, isOpen, approvalThreshold, minBlockDelay, maxBlockDelay, reference));
|
||||||
} while (resultSet.next());
|
} while (resultSet.next());
|
||||||
|
|
||||||
return groups;
|
return groups;
|
||||||
@ -139,7 +148,7 @@ public class HSQLDBGroupRepository implements GroupRepository {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<GroupData> getGroupsByOwner(String owner, Integer limit, Integer offset, Boolean reverse) throws DataException {
|
public List<GroupData> getGroupsByOwner(String owner, Integer limit, Integer offset, Boolean reverse) throws DataException {
|
||||||
String sql = "SELECT group_id, group_name, description, created, updated, reference, is_open, approval_threshold FROM Groups WHERE owner = ? ORDER BY group_name";
|
String sql = "SELECT group_id, group_name, description, created, updated, reference, is_open, approval_threshold, min_block_delay, max_block_delay FROM Groups WHERE owner = ? ORDER BY group_name";
|
||||||
if (reverse != null && reverse)
|
if (reverse != null && reverse)
|
||||||
sql += " DESC";
|
sql += " DESC";
|
||||||
sql += HSQLDBRepository.limitOffsetSql(limit, offset);
|
sql += HSQLDBRepository.limitOffsetSql(limit, offset);
|
||||||
@ -165,7 +174,10 @@ public class HSQLDBGroupRepository implements GroupRepository {
|
|||||||
|
|
||||||
ApprovalThreshold approvalThreshold = ApprovalThreshold.valueOf(resultSet.getInt(8));
|
ApprovalThreshold approvalThreshold = ApprovalThreshold.valueOf(resultSet.getInt(8));
|
||||||
|
|
||||||
groups.add(new GroupData(groupId, owner, groupName, description, created, updated, isOpen, approvalThreshold, reference));
|
int minBlockDelay = resultSet.getInt(9);
|
||||||
|
int maxBlockDelay = resultSet.getInt(10);
|
||||||
|
|
||||||
|
groups.add(new GroupData(groupId, owner, groupName, description, created, updated, isOpen, approvalThreshold, minBlockDelay, maxBlockDelay, reference));
|
||||||
} while (resultSet.next());
|
} while (resultSet.next());
|
||||||
|
|
||||||
return groups;
|
return groups;
|
||||||
@ -176,7 +188,8 @@ public class HSQLDBGroupRepository implements GroupRepository {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<GroupData> getGroupsWithMember(String member, Integer limit, Integer offset, Boolean reverse) throws DataException {
|
public List<GroupData> getGroupsWithMember(String member, Integer limit, Integer offset, Boolean reverse) throws DataException {
|
||||||
String sql = "SELECT group_id, owner, group_name, description, created, updated, reference, is_open, approval_threshold FROM Groups JOIN GroupMembers USING (group_id) WHERE address = ? ORDER BY group_name";
|
String sql = "SELECT group_id, owner, group_name, description, created, updated, reference, is_open, approval_threshold min_block_delay, max_block_delay FROM Groups "
|
||||||
|
+ "JOIN GroupMembers USING (group_id) WHERE address = ? ORDER BY group_name";
|
||||||
if (reverse != null && reverse)
|
if (reverse != null && reverse)
|
||||||
sql += " DESC";
|
sql += " DESC";
|
||||||
sql += HSQLDBRepository.limitOffsetSql(limit, offset);
|
sql += HSQLDBRepository.limitOffsetSql(limit, offset);
|
||||||
@ -203,7 +216,10 @@ public class HSQLDBGroupRepository implements GroupRepository {
|
|||||||
|
|
||||||
ApprovalThreshold approvalThreshold = ApprovalThreshold.valueOf(resultSet.getInt(9));
|
ApprovalThreshold approvalThreshold = ApprovalThreshold.valueOf(resultSet.getInt(9));
|
||||||
|
|
||||||
groups.add(new GroupData(groupId, owner, groupName, description, created, updated, isOpen, approvalThreshold, reference));
|
int minBlockDelay = resultSet.getInt(10);
|
||||||
|
int maxBlockDelay = resultSet.getInt(11);
|
||||||
|
|
||||||
|
groups.add(new GroupData(groupId, owner, groupName, description, created, updated, isOpen, approvalThreshold, minBlockDelay, maxBlockDelay, reference));
|
||||||
} while (resultSet.next());
|
} while (resultSet.next());
|
||||||
|
|
||||||
return groups;
|
return groups;
|
||||||
@ -222,7 +238,8 @@ 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", new Timestamp(groupData.getCreated())).bind("updated", updatedTimestamp)
|
.bind("description", groupData.getDescription()).bind("created", new Timestamp(groupData.getCreated())).bind("updated", updatedTimestamp)
|
||||||
.bind("reference", groupData.getReference()).bind("is_open", groupData.getIsOpen()).bind("approval_threshold", groupData.getApprovalThreshold().value);
|
.bind("reference", groupData.getReference()).bind("is_open", groupData.getIsOpen()).bind("approval_threshold", groupData.getApprovalThreshold().value)
|
||||||
|
.bind("min_block_delay", groupData.getMinimumBlockDelay()).bind("max_block_delay", groupData.getMaximumBlockDelay());
|
||||||
|
|
||||||
try {
|
try {
|
||||||
saveHelper.execute(this.repository);
|
saveHelper.execute(this.repository);
|
||||||
|
@ -18,8 +18,9 @@ public class HSQLDBCreateGroupTransactionRepository extends HSQLDBTransactionRep
|
|||||||
}
|
}
|
||||||
|
|
||||||
TransactionData fromBase(long timestamp, int txGroupId, byte[] reference, byte[] creatorPublicKey, BigDecimal fee, byte[] signature) throws DataException {
|
TransactionData fromBase(long timestamp, int txGroupId, byte[] reference, byte[] creatorPublicKey, BigDecimal fee, byte[] signature) throws DataException {
|
||||||
try (ResultSet resultSet = this.repository
|
try (ResultSet resultSet = this.repository.checkedExecute(
|
||||||
.checkedExecute("SELECT owner, group_name, description, is_open, approval_threshold, group_id FROM CreateGroupTransactions WHERE signature = ?", signature)) {
|
"SELECT owner, group_name, description, is_open, approval_threshold, min_block_delay, max_block_delay, group_id FROM CreateGroupTransactions WHERE signature = ?",
|
||||||
|
signature)) {
|
||||||
if (resultSet == null)
|
if (resultSet == null)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
@ -30,12 +31,15 @@ public class HSQLDBCreateGroupTransactionRepository extends HSQLDBTransactionRep
|
|||||||
|
|
||||||
ApprovalThreshold approvalThreshold = ApprovalThreshold.valueOf(resultSet.getInt(5));
|
ApprovalThreshold approvalThreshold = ApprovalThreshold.valueOf(resultSet.getInt(5));
|
||||||
|
|
||||||
Integer groupId = resultSet.getInt(6);
|
int minBlockDelay = resultSet.getInt(6);
|
||||||
|
int maxBlockDelay = resultSet.getInt(7);
|
||||||
|
|
||||||
|
Integer groupId = resultSet.getInt(8);
|
||||||
if (resultSet.wasNull())
|
if (resultSet.wasNull())
|
||||||
groupId = null;
|
groupId = null;
|
||||||
|
|
||||||
return new CreateGroupTransactionData(timestamp, txGroupId, reference, creatorPublicKey, owner, groupName, description, isOpen, approvalThreshold,
|
return new CreateGroupTransactionData(timestamp, txGroupId, reference, creatorPublicKey, owner, groupName, description, isOpen, approvalThreshold,
|
||||||
groupId, fee, signature);
|
minBlockDelay, maxBlockDelay, groupId, fee, signature);
|
||||||
} 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);
|
||||||
}
|
}
|
||||||
@ -51,7 +55,8 @@ public class HSQLDBCreateGroupTransactionRepository extends HSQLDBTransactionRep
|
|||||||
.bind("owner", createGroupTransactionData.getOwner()).bind("group_name", createGroupTransactionData.getGroupName())
|
.bind("owner", createGroupTransactionData.getOwner()).bind("group_name", createGroupTransactionData.getGroupName())
|
||||||
.bind("description", createGroupTransactionData.getDescription()).bind("is_open", createGroupTransactionData.getIsOpen())
|
.bind("description", createGroupTransactionData.getDescription()).bind("is_open", createGroupTransactionData.getIsOpen())
|
||||||
.bind("approval_threshold", createGroupTransactionData.getApprovalThreshold().value)
|
.bind("approval_threshold", createGroupTransactionData.getApprovalThreshold().value)
|
||||||
.bind("group_id", createGroupTransactionData.getGroupId());
|
.bind("min_block_delay", createGroupTransactionData.getMinimumBlockDelay())
|
||||||
|
.bind("max_block_delay", createGroupTransactionData.getMaximumBlockDelay()).bind("group_id", createGroupTransactionData.getGroupId());
|
||||||
|
|
||||||
try {
|
try {
|
||||||
saveHelper.execute(this.repository);
|
saveHelper.execute(this.repository);
|
||||||
|
@ -9,6 +9,7 @@ import java.sql.SQLException;
|
|||||||
import java.sql.Timestamp;
|
import java.sql.Timestamp;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import static java.util.Arrays.stream;
|
||||||
import java.util.Calendar;
|
import java.util.Calendar;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
@ -19,10 +20,12 @@ import org.qora.api.resource.TransactionsResource.ConfirmationStatus;
|
|||||||
import org.qora.data.PaymentData;
|
import org.qora.data.PaymentData;
|
||||||
import org.qora.data.transaction.GroupApprovalTransactionData;
|
import org.qora.data.transaction.GroupApprovalTransactionData;
|
||||||
import org.qora.data.transaction.TransactionData;
|
import org.qora.data.transaction.TransactionData;
|
||||||
|
import org.qora.group.Group;
|
||||||
import org.qora.repository.DataException;
|
import org.qora.repository.DataException;
|
||||||
import org.qora.repository.TransactionRepository;
|
import org.qora.repository.TransactionRepository;
|
||||||
import org.qora.repository.hsqldb.HSQLDBRepository;
|
import org.qora.repository.hsqldb.HSQLDBRepository;
|
||||||
import org.qora.repository.hsqldb.HSQLDBSaver;
|
import org.qora.repository.hsqldb.HSQLDBSaver;
|
||||||
|
import org.qora.transaction.Transaction;
|
||||||
import org.qora.transaction.Transaction.TransactionType;
|
import org.qora.transaction.Transaction.TransactionType;
|
||||||
|
|
||||||
import static org.qora.transaction.Transaction.TransactionType.*;
|
import static org.qora.transaction.Transaction.TransactionType.*;
|
||||||
@ -384,7 +387,7 @@ public class HSQLDBTransactionRepository implements TransactionRepository {
|
|||||||
|
|
||||||
sql += HSQLDBRepository.limitOffsetSql(limit, offset);
|
sql += HSQLDBRepository.limitOffsetSql(limit, offset);
|
||||||
|
|
||||||
LOGGER.trace(sql);
|
LOGGER.trace(String.format("Transaction search SQL: %s", sql));
|
||||||
|
|
||||||
try (ResultSet resultSet = this.repository.checkedExecute(sql, bindParams.toArray())) {
|
try (ResultSet resultSet = this.repository.checkedExecute(sql, bindParams.toArray())) {
|
||||||
if (resultSet == null)
|
if (resultSet == null)
|
||||||
@ -488,6 +491,59 @@ public class HSQLDBTransactionRepository implements TransactionRepository {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<TransactionData> getPendingTransactions(Integer txGroupId, Integer limit, Integer offset, Boolean reverse) throws DataException {
|
||||||
|
String[] txTypesNeedingApproval = stream(Transaction.TransactionType.values())
|
||||||
|
.filter(txType -> txType.needsApproval)
|
||||||
|
.map(txType -> String.valueOf(txType.value))
|
||||||
|
.toArray(String[]::new);
|
||||||
|
|
||||||
|
String txTypes = String.join(", ", txTypesNeedingApproval);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* We only want transactions matching certain types needing approval,
|
||||||
|
* with txGroupId not set to NO_GROUP and where auto-approval won't
|
||||||
|
* happen due to the transaction creator being an admin of that group.
|
||||||
|
*/
|
||||||
|
String sql = "SELECT signature FROM UnconfirmedTransactions "
|
||||||
|
+ "NATURAL JOIN Transactions "
|
||||||
|
+ "LEFT OUTER JOIN Accounts ON Accounts.public_key = Transactions.creator "
|
||||||
|
+ "LEFT OUTER JOIN GroupAdmins ON GroupAdmins.admin = Accounts.account "
|
||||||
|
+ "WHERE Transactions.tx_group_id != ? AND GroupAdmins.admin IS NULL "
|
||||||
|
+ "AND Transactions.type IN (" + txTypes + ") "
|
||||||
|
+ "ORDER BY creation";
|
||||||
|
if (reverse != null && reverse)
|
||||||
|
sql += " DESC";
|
||||||
|
sql += ", signature";
|
||||||
|
if (reverse != null && reverse)
|
||||||
|
sql += " DESC";
|
||||||
|
sql += HSQLDBRepository.limitOffsetSql(limit, offset);
|
||||||
|
|
||||||
|
List<TransactionData> transactions = new ArrayList<TransactionData>();
|
||||||
|
|
||||||
|
// Find transactions with no corresponding row in BlockTransactions
|
||||||
|
try (ResultSet resultSet = this.repository.checkedExecute(sql, Group.NO_GROUP)) {
|
||||||
|
if (resultSet == null)
|
||||||
|
return transactions;
|
||||||
|
|
||||||
|
do {
|
||||||
|
byte[] signature = resultSet.getBytes(1);
|
||||||
|
|
||||||
|
TransactionData transactionData = this.fromSignature(signature);
|
||||||
|
|
||||||
|
if (transactionData == null)
|
||||||
|
// Something inconsistent with the repository
|
||||||
|
throw new DataException("Unable to fetch unconfirmed transaction from repository?");
|
||||||
|
|
||||||
|
transactions.add(transactionData);
|
||||||
|
} while (resultSet.next());
|
||||||
|
|
||||||
|
return transactions;
|
||||||
|
} catch (SQLException | DataException e) {
|
||||||
|
throw new DataException("Unable to fetch unconfirmed transactions from repository", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int countTransactionApprovals(int txGroupId, byte[] signature) throws DataException {
|
public int countTransactionApprovals(int txGroupId, byte[] signature) throws DataException {
|
||||||
// Fetch total number of approvals for signature
|
// Fetch total number of approvals for signature
|
||||||
|
@ -19,7 +19,7 @@ public class HSQLDBUpdateGroupTransactionRepository extends HSQLDBTransactionRep
|
|||||||
|
|
||||||
TransactionData fromBase(long timestamp, int txGroupId, byte[] reference, byte[] creatorPublicKey, BigDecimal fee, byte[] signature) throws DataException {
|
TransactionData fromBase(long timestamp, int txGroupId, byte[] reference, byte[] creatorPublicKey, BigDecimal fee, byte[] signature) throws DataException {
|
||||||
try (ResultSet resultSet = this.repository.checkedExecute(
|
try (ResultSet resultSet = this.repository.checkedExecute(
|
||||||
"SELECT group_id, new_owner, new_description, new_is_open, new_approval_threshold, group_reference FROM UpdateGroupTransactions WHERE signature = ?",
|
"SELECT group_id, new_owner, new_description, new_is_open, new_approval_threshold, new_min_block_delay, new_max_block_delay, group_reference FROM UpdateGroupTransactions WHERE signature = ?",
|
||||||
signature)) {
|
signature)) {
|
||||||
if (resultSet == null)
|
if (resultSet == null)
|
||||||
return null;
|
return null;
|
||||||
@ -30,9 +30,11 @@ public class HSQLDBUpdateGroupTransactionRepository extends HSQLDBTransactionRep
|
|||||||
boolean newIsOpen = resultSet.getBoolean(4);
|
boolean newIsOpen = resultSet.getBoolean(4);
|
||||||
ApprovalThreshold newApprovalThreshold = ApprovalThreshold.valueOf(resultSet.getInt(5));
|
ApprovalThreshold newApprovalThreshold = ApprovalThreshold.valueOf(resultSet.getInt(5));
|
||||||
byte[] groupReference = resultSet.getBytes(6);
|
byte[] groupReference = resultSet.getBytes(6);
|
||||||
|
int newMinBlockDelay = resultSet.getInt(7);
|
||||||
|
int newMaxBlockDelay = resultSet.getInt(8);
|
||||||
|
|
||||||
return new UpdateGroupTransactionData(timestamp, txGroupId, reference, creatorPublicKey, groupId, newOwner, newDescription, newIsOpen,
|
return new UpdateGroupTransactionData(timestamp, txGroupId, reference, creatorPublicKey, groupId, newOwner, newDescription, newIsOpen,
|
||||||
newApprovalThreshold, groupReference, fee, signature);
|
newApprovalThreshold, newMinBlockDelay, newMaxBlockDelay, groupReference, fee, signature);
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
throw new DataException("Unable to fetch update group transaction from repository", e);
|
throw new DataException("Unable to fetch update group transaction from repository", e);
|
||||||
}
|
}
|
||||||
@ -48,6 +50,8 @@ public class HSQLDBUpdateGroupTransactionRepository extends HSQLDBTransactionRep
|
|||||||
.bind("group_id", updateGroupTransactionData.getGroupId()).bind("new_owner", updateGroupTransactionData.getNewOwner())
|
.bind("group_id", updateGroupTransactionData.getGroupId()).bind("new_owner", updateGroupTransactionData.getNewOwner())
|
||||||
.bind("new_description", updateGroupTransactionData.getNewDescription()).bind("new_is_open", updateGroupTransactionData.getNewIsOpen())
|
.bind("new_description", updateGroupTransactionData.getNewDescription()).bind("new_is_open", updateGroupTransactionData.getNewIsOpen())
|
||||||
.bind("new_approval_threshold", updateGroupTransactionData.getNewApprovalThreshold().value)
|
.bind("new_approval_threshold", updateGroupTransactionData.getNewApprovalThreshold().value)
|
||||||
|
.bind("new_min_block_delay", updateGroupTransactionData.getNewMinimumBlockDelay())
|
||||||
|
.bind("new_max_block_delay", updateGroupTransactionData.getNewMaximumBlockDelay())
|
||||||
.bind("group_reference", updateGroupTransactionData.getGroupReference());
|
.bind("group_reference", updateGroupTransactionData.getGroupReference());
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -27,6 +27,7 @@ public class Settings {
|
|||||||
private String userpath = "";
|
private String userpath = "";
|
||||||
private boolean useBitcoinTestNet = false;
|
private boolean useBitcoinTestNet = false;
|
||||||
private boolean wipeUnconfirmedOnStart = false;
|
private boolean wipeUnconfirmedOnStart = false;
|
||||||
|
private Boolean restrictedApi;
|
||||||
private String blockchainConfigPath = "blockchain.json";
|
private String blockchainConfigPath = "blockchain.json";
|
||||||
/** Maximum number of unconfirmed transactions allowed per account */
|
/** Maximum number of unconfirmed transactions allowed per account */
|
||||||
private int maxUnconfirmedPerAccount = 100;
|
private int maxUnconfirmedPerAccount = 100;
|
||||||
@ -143,6 +144,9 @@ public class Settings {
|
|||||||
if (json.containsKey("apiEnabled"))
|
if (json.containsKey("apiEnabled"))
|
||||||
this.apiEnabled = ((Boolean) json.get("apiEnabled")).booleanValue();
|
this.apiEnabled = ((Boolean) json.get("apiEnabled")).booleanValue();
|
||||||
|
|
||||||
|
if (json.containsKey("restrictedApi"))
|
||||||
|
this.restrictedApi = ((Boolean) json.get("restrictedApi")).booleanValue();
|
||||||
|
|
||||||
// Peer-to-peer networking
|
// Peer-to-peer networking
|
||||||
|
|
||||||
if (json.containsKey("listenPort"))
|
if (json.containsKey("listenPort"))
|
||||||
@ -208,6 +212,14 @@ public class Settings {
|
|||||||
return this.apiEnabled;
|
return this.apiEnabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isRestrictedApi() {
|
||||||
|
if (this.restrictedApi != null)
|
||||||
|
return this.restrictedApi;
|
||||||
|
|
||||||
|
// Not set in config file, so restrict if not testnet
|
||||||
|
return !BlockChain.getInstance().getIsTestNet();
|
||||||
|
}
|
||||||
|
|
||||||
public int getListenPort() {
|
public int getListenPort() {
|
||||||
return this.listenPort;
|
return this.listenPort;
|
||||||
}
|
}
|
||||||
|
@ -69,15 +69,14 @@ public class GroupApprovalTransaction extends Transaction {
|
|||||||
if (pendingTransactionData == null)
|
if (pendingTransactionData == null)
|
||||||
return ValidationResult.TRANSACTION_UNKNOWN;
|
return ValidationResult.TRANSACTION_UNKNOWN;
|
||||||
|
|
||||||
|
// Check pending transaction's groupID matches our transaction's groupID
|
||||||
|
if (groupApprovalTransactionData.getTxGroupId() != pendingTransactionData.getTxGroupId())
|
||||||
|
return ValidationResult.GROUP_ID_MISMATCH;
|
||||||
|
|
||||||
// Check pending transaction is not already in a block
|
// Check pending transaction is not already in a block
|
||||||
if (this.repository.getTransactionRepository().getHeightFromSignature(groupApprovalTransactionData.getPendingSignature()) != 0)
|
if (this.repository.getTransactionRepository().getHeightFromSignature(groupApprovalTransactionData.getPendingSignature()) != 0)
|
||||||
return ValidationResult.TRANSACTION_ALREADY_CONFIRMED;
|
return ValidationResult.TRANSACTION_ALREADY_CONFIRMED;
|
||||||
|
|
||||||
// Check pending transaction's groupID matches our transaction's groupID
|
|
||||||
int effectiveTxGroupId = this.getEffectiveGroupId();
|
|
||||||
if (effectiveTxGroupId != pendingTransactionData.getTxGroupId())
|
|
||||||
return ValidationResult.GROUP_ID_MISMATCH;
|
|
||||||
|
|
||||||
Account admin = getAdmin();
|
Account admin = getAdmin();
|
||||||
|
|
||||||
// Can't cast approval decision if not an admin
|
// Can't cast approval decision if not an admin
|
||||||
|
@ -10,7 +10,6 @@ import org.qora.account.PublicKeyAccount;
|
|||||||
import org.qora.asset.Asset;
|
import org.qora.asset.Asset;
|
||||||
import org.qora.crypto.Crypto;
|
import org.qora.crypto.Crypto;
|
||||||
import org.qora.data.transaction.GroupInviteTransactionData;
|
import org.qora.data.transaction.GroupInviteTransactionData;
|
||||||
import org.qora.data.group.GroupData;
|
|
||||||
import org.qora.data.transaction.TransactionData;
|
import org.qora.data.transaction.TransactionData;
|
||||||
import org.qora.group.Group;
|
import org.qora.group.Group;
|
||||||
import org.qora.repository.DataException;
|
import org.qora.repository.DataException;
|
||||||
@ -74,6 +73,12 @@ public class GroupInviteTransaction extends Transaction {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ValidationResult isValid() throws DataException {
|
public ValidationResult isValid() throws DataException {
|
||||||
|
int groupId = groupInviteTransactionData.getGroupId();
|
||||||
|
|
||||||
|
// Check transaction's groupID matches group's ID
|
||||||
|
if (groupInviteTransactionData.getTxGroupId() != groupId)
|
||||||
|
return ValidationResult.GROUP_ID_MISMATCH;
|
||||||
|
|
||||||
// Check time to live zero (infinite) or positive
|
// Check time to live zero (infinite) or positive
|
||||||
if (groupInviteTransactionData.getTimeToLive() < 0)
|
if (groupInviteTransactionData.getTimeToLive() < 0)
|
||||||
return ValidationResult.INVALID_LIFETIME;
|
return ValidationResult.INVALID_LIFETIME;
|
||||||
@ -82,31 +87,24 @@ public class GroupInviteTransaction extends Transaction {
|
|||||||
if (!Crypto.isValidAddress(groupInviteTransactionData.getInvitee()))
|
if (!Crypto.isValidAddress(groupInviteTransactionData.getInvitee()))
|
||||||
return ValidationResult.INVALID_ADDRESS;
|
return ValidationResult.INVALID_ADDRESS;
|
||||||
|
|
||||||
GroupData groupData = this.repository.getGroupRepository().fromGroupId(groupInviteTransactionData.getGroupId());
|
|
||||||
|
|
||||||
// Check group exists
|
// Check group exists
|
||||||
if (groupData == null)
|
if (!this.repository.getGroupRepository().groupExists(groupId))
|
||||||
return ValidationResult.GROUP_DOES_NOT_EXIST;
|
return ValidationResult.GROUP_DOES_NOT_EXIST;
|
||||||
|
|
||||||
// Check transaction's groupID matches group's ID
|
|
||||||
int effectiveTxGroupId = this.getEffectiveGroupId();
|
|
||||||
if (effectiveTxGroupId != groupInviteTransactionData.getTxGroupId())
|
|
||||||
return ValidationResult.GROUP_ID_MISMATCH;
|
|
||||||
|
|
||||||
Account admin = getAdmin();
|
Account admin = getAdmin();
|
||||||
|
|
||||||
// Can't invite if not an admin
|
// Can't invite if not an admin
|
||||||
if (!this.repository.getGroupRepository().adminExists(groupInviteTransactionData.getGroupId(), admin.getAddress()))
|
if (!this.repository.getGroupRepository().adminExists(groupId, admin.getAddress()))
|
||||||
return ValidationResult.NOT_GROUP_ADMIN;
|
return ValidationResult.NOT_GROUP_ADMIN;
|
||||||
|
|
||||||
Account invitee = getInvitee();
|
Account invitee = getInvitee();
|
||||||
|
|
||||||
// Check invitee not already in group
|
// Check invitee not already in group
|
||||||
if (this.repository.getGroupRepository().memberExists(groupInviteTransactionData.getGroupId(), invitee.getAddress()))
|
if (this.repository.getGroupRepository().memberExists(groupId, invitee.getAddress()))
|
||||||
return ValidationResult.ALREADY_GROUP_MEMBER;
|
return ValidationResult.ALREADY_GROUP_MEMBER;
|
||||||
|
|
||||||
// Check invitee is not banned
|
// Check invitee is not banned
|
||||||
if (this.repository.getGroupRepository().banExists(groupInviteTransactionData.getGroupId(), invitee.getAddress()))
|
if (this.repository.getGroupRepository().banExists(groupId, invitee.getAddress()))
|
||||||
return ValidationResult.BANNED_FROM_GROUP;
|
return ValidationResult.BANNED_FROM_GROUP;
|
||||||
|
|
||||||
// Check fee is positive
|
// Check fee is positive
|
||||||
|
@ -9,7 +9,6 @@ import org.qora.account.Account;
|
|||||||
import org.qora.account.PublicKeyAccount;
|
import org.qora.account.PublicKeyAccount;
|
||||||
import org.qora.asset.Asset;
|
import org.qora.asset.Asset;
|
||||||
import org.qora.data.transaction.JoinGroupTransactionData;
|
import org.qora.data.transaction.JoinGroupTransactionData;
|
||||||
import org.qora.data.group.GroupData;
|
|
||||||
import org.qora.data.transaction.TransactionData;
|
import org.qora.data.transaction.TransactionData;
|
||||||
import org.qora.group.Group;
|
import org.qora.group.Group;
|
||||||
import org.qora.repository.DataException;
|
import org.qora.repository.DataException;
|
||||||
@ -66,23 +65,28 @@ public class JoinGroupTransaction extends Transaction {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ValidationResult isValid() throws DataException {
|
public ValidationResult isValid() throws DataException {
|
||||||
GroupData groupData = this.repository.getGroupRepository().fromGroupId(joinGroupTransactionData.getGroupId());
|
int groupId = joinGroupTransactionData.getGroupId();
|
||||||
|
|
||||||
|
// Check txGroupId
|
||||||
|
int txGroupId = joinGroupTransactionData.getTxGroupId();
|
||||||
|
if (txGroupId != Group.NO_GROUP && txGroupId != groupId)
|
||||||
|
return ValidationResult.GROUP_ID_MISMATCH;
|
||||||
|
|
||||||
// Check group exists
|
// Check group exists
|
||||||
if (groupData == null)
|
if (!this.repository.getGroupRepository().groupExists(groupId))
|
||||||
return ValidationResult.GROUP_DOES_NOT_EXIST;
|
return ValidationResult.GROUP_DOES_NOT_EXIST;
|
||||||
|
|
||||||
Account joiner = getJoiner();
|
Account joiner = getJoiner();
|
||||||
|
|
||||||
if (this.repository.getGroupRepository().memberExists(joinGroupTransactionData.getGroupId(), joiner.getAddress()))
|
if (this.repository.getGroupRepository().memberExists(groupId, joiner.getAddress()))
|
||||||
return ValidationResult.ALREADY_GROUP_MEMBER;
|
return ValidationResult.ALREADY_GROUP_MEMBER;
|
||||||
|
|
||||||
// Check member is not banned
|
// Check member is not banned
|
||||||
if (this.repository.getGroupRepository().banExists(joinGroupTransactionData.getGroupId(), joiner.getAddress()))
|
if (this.repository.getGroupRepository().banExists(groupId, joiner.getAddress()))
|
||||||
return ValidationResult.BANNED_FROM_GROUP;
|
return ValidationResult.BANNED_FROM_GROUP;
|
||||||
|
|
||||||
// Check join request doesn't already exist
|
// Check join request doesn't already exist
|
||||||
if (this.repository.getGroupRepository().joinRequestExists(joinGroupTransactionData.getGroupId(), joiner.getAddress()))
|
if (this.repository.getGroupRepository().joinRequestExists(groupId, joiner.getAddress()))
|
||||||
return ValidationResult.JOIN_REQUEST_EXISTS;
|
return ValidationResult.JOIN_REQUEST_EXISTS;
|
||||||
|
|
||||||
// Check fee is positive
|
// Check fee is positive
|
||||||
|
@ -8,7 +8,6 @@ import java.util.List;
|
|||||||
import org.qora.account.Account;
|
import org.qora.account.Account;
|
||||||
import org.qora.asset.Asset;
|
import org.qora.asset.Asset;
|
||||||
import org.qora.data.transaction.SetGroupTransactionData;
|
import org.qora.data.transaction.SetGroupTransactionData;
|
||||||
import org.qora.data.group.GroupData;
|
|
||||||
import org.qora.data.transaction.TransactionData;
|
import org.qora.data.transaction.TransactionData;
|
||||||
import org.qora.group.Group;
|
import org.qora.group.Group;
|
||||||
import org.qora.repository.DataException;
|
import org.qora.repository.DataException;
|
||||||
@ -61,10 +60,8 @@ public class SetGroupTransaction extends Transaction {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ValidationResult isValid() throws DataException {
|
public ValidationResult isValid() throws DataException {
|
||||||
GroupData groupData = this.repository.getGroupRepository().fromGroupId(setGroupTransactionData.getDefaultGroupId());
|
|
||||||
|
|
||||||
// Check group exists
|
// Check group exists
|
||||||
if (groupData == null)
|
if (!this.repository.getGroupRepository().groupExists(setGroupTransactionData.getDefaultGroupId()))
|
||||||
return ValidationResult.GROUP_DOES_NOT_EXIST;
|
return ValidationResult.GROUP_DOES_NOT_EXIST;
|
||||||
|
|
||||||
Account creator = getCreator();
|
Account creator = getCreator();
|
||||||
@ -94,7 +91,7 @@ public class SetGroupTransaction extends Transaction {
|
|||||||
|
|
||||||
Integer previousDefaultGroupId = this.repository.getAccountRepository().getDefaultGroupId(creator.getAddress());
|
Integer previousDefaultGroupId = this.repository.getAccountRepository().getDefaultGroupId(creator.getAddress());
|
||||||
if (previousDefaultGroupId == null)
|
if (previousDefaultGroupId == null)
|
||||||
previousDefaultGroupId = Group.DEFAULT_GROUP;
|
previousDefaultGroupId = Group.NO_GROUP;
|
||||||
|
|
||||||
setGroupTransactionData.setPreviousDefaultGroupId(previousDefaultGroupId);
|
setGroupTransactionData.setPreviousDefaultGroupId(previousDefaultGroupId);
|
||||||
|
|
||||||
@ -118,7 +115,7 @@ public class SetGroupTransaction extends Transaction {
|
|||||||
|
|
||||||
Integer previousDefaultGroupId = setGroupTransactionData.getPreviousDefaultGroupId();
|
Integer previousDefaultGroupId = setGroupTransactionData.getPreviousDefaultGroupId();
|
||||||
if (previousDefaultGroupId == null)
|
if (previousDefaultGroupId == null)
|
||||||
previousDefaultGroupId = Group.DEFAULT_GROUP;
|
previousDefaultGroupId = Group.NO_GROUP;
|
||||||
|
|
||||||
creator.setDefaultGroupId(previousDefaultGroupId);
|
creator.setDefaultGroupId(previousDefaultGroupId);
|
||||||
|
|
||||||
|
@ -16,6 +16,7 @@ import org.qora.account.PrivateKeyAccount;
|
|||||||
import org.qora.account.PublicKeyAccount;
|
import org.qora.account.PublicKeyAccount;
|
||||||
import org.qora.block.BlockChain;
|
import org.qora.block.BlockChain;
|
||||||
import org.qora.data.block.BlockData;
|
import org.qora.data.block.BlockData;
|
||||||
|
import org.qora.data.group.GroupData;
|
||||||
import org.qora.data.transaction.TransactionData;
|
import org.qora.data.transaction.TransactionData;
|
||||||
import org.qora.group.Group;
|
import org.qora.group.Group;
|
||||||
import org.qora.repository.DataException;
|
import org.qora.repository.DataException;
|
||||||
@ -37,43 +38,43 @@ public abstract class Transaction {
|
|||||||
// Transaction types
|
// Transaction types
|
||||||
public enum TransactionType {
|
public enum TransactionType {
|
||||||
// NOTE: must be contiguous or reflection fails
|
// NOTE: must be contiguous or reflection fails
|
||||||
GENESIS(1, true),
|
GENESIS(1, false),
|
||||||
PAYMENT(2, false),
|
PAYMENT(2, false),
|
||||||
REGISTER_NAME(3, false),
|
REGISTER_NAME(3, true),
|
||||||
UPDATE_NAME(4, false),
|
UPDATE_NAME(4, true),
|
||||||
SELL_NAME(5, false),
|
SELL_NAME(5, false),
|
||||||
CANCEL_SELL_NAME(6, false),
|
CANCEL_SELL_NAME(6, false),
|
||||||
BUY_NAME(7, false),
|
BUY_NAME(7, false),
|
||||||
CREATE_POLL(8, false),
|
CREATE_POLL(8, true),
|
||||||
VOTE_ON_POLL(9, false),
|
VOTE_ON_POLL(9, false),
|
||||||
ARBITRARY(10, false),
|
ARBITRARY(10, true),
|
||||||
ISSUE_ASSET(11, false),
|
ISSUE_ASSET(11, true),
|
||||||
TRANSFER_ASSET(12, false),
|
TRANSFER_ASSET(12, false),
|
||||||
CREATE_ASSET_ORDER(13, false),
|
CREATE_ASSET_ORDER(13, false),
|
||||||
CANCEL_ASSET_ORDER(14, false),
|
CANCEL_ASSET_ORDER(14, false),
|
||||||
MULTI_PAYMENT(15, false),
|
MULTI_PAYMENT(15, false),
|
||||||
DEPLOY_AT(16, false),
|
DEPLOY_AT(16, true),
|
||||||
MESSAGE(17, false),
|
MESSAGE(17, true),
|
||||||
DELEGATION(18, false),
|
DELEGATION(18, false),
|
||||||
SUPERNODE(19, false),
|
SUPERNODE(19, false),
|
||||||
AIRDROP(20, true),
|
AIRDROP(20, false),
|
||||||
AT(21, true),
|
AT(21, false),
|
||||||
CREATE_GROUP(22, false),
|
CREATE_GROUP(22, true),
|
||||||
UPDATE_GROUP(23, true),
|
UPDATE_GROUP(23, true),
|
||||||
ADD_GROUP_ADMIN(24, true),
|
ADD_GROUP_ADMIN(24, false),
|
||||||
REMOVE_GROUP_ADMIN(25, true),
|
REMOVE_GROUP_ADMIN(25, false),
|
||||||
GROUP_BAN(26, true),
|
GROUP_BAN(26, false),
|
||||||
CANCEL_GROUP_BAN(27, true),
|
CANCEL_GROUP_BAN(27, false),
|
||||||
GROUP_KICK(28, true),
|
GROUP_KICK(28, false),
|
||||||
GROUP_INVITE(29, true),
|
GROUP_INVITE(29, false),
|
||||||
CANCEL_GROUP_INVITE(30, true),
|
CANCEL_GROUP_INVITE(30, false),
|
||||||
JOIN_GROUP(31, true),
|
JOIN_GROUP(31, false),
|
||||||
LEAVE_GROUP(32, true),
|
LEAVE_GROUP(32, false),
|
||||||
GROUP_APPROVAL(33, true),
|
GROUP_APPROVAL(33, false),
|
||||||
SET_GROUP(34, true);
|
SET_GROUP(34, false);
|
||||||
|
|
||||||
public final int value;
|
public final int value;
|
||||||
public final boolean skipsApproval;
|
public final boolean needsApproval;
|
||||||
public final String valueString;
|
public final String valueString;
|
||||||
public final String className;
|
public final String className;
|
||||||
public final Class<?> clazz;
|
public final Class<?> clazz;
|
||||||
@ -81,9 +82,9 @@ public abstract class Transaction {
|
|||||||
|
|
||||||
private final static Map<Integer, TransactionType> map = stream(TransactionType.values()).collect(toMap(type -> type.value, type -> type));
|
private final static Map<Integer, TransactionType> map = stream(TransactionType.values()).collect(toMap(type -> type.value, type -> type));
|
||||||
|
|
||||||
TransactionType(int value, boolean skipsApproval) {
|
TransactionType(int value, boolean needsApproval) {
|
||||||
this.value = value;
|
this.value = value;
|
||||||
this.skipsApproval = skipsApproval;
|
this.needsApproval = needsApproval;
|
||||||
this.valueString = String.valueOf(value);
|
this.valueString = String.valueOf(value);
|
||||||
|
|
||||||
String[] classNameParts = this.name().toLowerCase().split("_");
|
String[] classNameParts = this.name().toLowerCase().split("_");
|
||||||
@ -495,19 +496,20 @@ public abstract class Transaction {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private boolean isValidTxGroupId() throws DataException {
|
private boolean isValidTxGroupId() throws DataException {
|
||||||
// Does this transaction type bypass approval?
|
int txGroupId = this.transactionData.getTxGroupId();
|
||||||
if (this.transactionData.getType().skipsApproval)
|
|
||||||
return true;
|
|
||||||
|
|
||||||
int txGroupId = this.getEffectiveGroupId();
|
// Handling NO_GROUP
|
||||||
if (txGroupId == Group.NO_GROUP)
|
if (txGroupId == Group.NO_GROUP)
|
||||||
return true;
|
// true if NO_GROUP allowed, false otherwise
|
||||||
|
return BlockChain.getInstance().getGrouplessAllowed();
|
||||||
|
|
||||||
Group group = new Group(repository, txGroupId);
|
// Group even exist?
|
||||||
if (group.getGroupData() == null) {
|
if (!this.repository.getGroupRepository().groupExists(txGroupId))
|
||||||
// Group no longer exists? Possibly due to blockchain orphaning undoing group creation?
|
|
||||||
return false;
|
return false;
|
||||||
}
|
|
||||||
|
// Does this transaction type bypass approval?
|
||||||
|
if (!this.transactionData.getType().needsApproval)
|
||||||
|
return true;
|
||||||
|
|
||||||
GroupRepository groupRepository = this.repository.getGroupRepository();
|
GroupRepository groupRepository = this.repository.getGroupRepository();
|
||||||
|
|
||||||
@ -615,6 +617,17 @@ public abstract class Transaction {
|
|||||||
if (transaction.getDeadline() <= blockTimestamp || transaction.getDeadline() < NTP.getTime())
|
if (transaction.getDeadline() <= blockTimestamp || transaction.getDeadline() < NTP.getTime())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
// Is transaction is past max approval period?
|
||||||
|
if (transaction.needsGroupApproval()) {
|
||||||
|
int txGroupId = transactionData.getTxGroupId();
|
||||||
|
GroupData groupData = repository.getGroupRepository().fromGroupId(txGroupId);
|
||||||
|
|
||||||
|
int creationBlockHeight = repository.getBlockRepository().getHeightFromTimestamp(transactionData.getTimestamp());
|
||||||
|
int currentBlockHeight = repository.getBlockRepository().getBlockchainHeight();
|
||||||
|
if (currentBlockHeight > creationBlockHeight + groupData.getMaximumBlockDelay())
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// Check transaction is currently valid
|
// Check transaction is currently valid
|
||||||
if (transaction.isValid() != Transaction.ValidationResult.OK)
|
if (transaction.isValid() != Transaction.ValidationResult.OK)
|
||||||
return false;
|
return false;
|
||||||
@ -628,72 +641,47 @@ public abstract class Transaction {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns transaction's effective groupID, using default values where necessary.
|
* Returns whether transaction needs to go through group-admin approval.
|
||||||
*/
|
|
||||||
public int getEffectiveGroupId() throws DataException {
|
|
||||||
int txGroupId = this.transactionData.getTxGroupId();
|
|
||||||
|
|
||||||
// If transaction's groupID is NO_GROUP then group-ness doesn't apply
|
|
||||||
if (txGroupId == Group.NO_GROUP) {
|
|
||||||
if (BlockChain.getInstance().getGrouplessAllowed())
|
|
||||||
return txGroupId;
|
|
||||||
else
|
|
||||||
txGroupId = Group.DEFAULT_GROUP;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If transaction's groupID is not DEFAULT_GROUP then no further processing required
|
|
||||||
if (txGroupId != Group.DEFAULT_GROUP)
|
|
||||||
return txGroupId;
|
|
||||||
|
|
||||||
// Try using account's default groupID
|
|
||||||
PublicKeyAccount creator = this.getCreator();
|
|
||||||
txGroupId = creator.getDefaultGroupId();
|
|
||||||
|
|
||||||
// If transaction's groupID is NO_GROUP then group-ness doesn't apply
|
|
||||||
if (txGroupId == Group.NO_GROUP) {
|
|
||||||
if (BlockChain.getInstance().getGrouplessAllowed())
|
|
||||||
return txGroupId;
|
|
||||||
else
|
|
||||||
txGroupId = Group.DEFAULT_GROUP;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If txGroupId now not DEFAULT_GROUP then no further processing required
|
|
||||||
if (txGroupId != Group.DEFAULT_GROUP)
|
|
||||||
return txGroupId;
|
|
||||||
|
|
||||||
// Still zero? Use blockchain default
|
|
||||||
return BlockChain.getInstance().getDefaultGroupId();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns whether transaction still requires group-admin approval.
|
|
||||||
*
|
*
|
||||||
* @throws DataException
|
* @throws DataException
|
||||||
*/
|
*/
|
||||||
public boolean needsGroupApproval() throws DataException {
|
public boolean needsGroupApproval() throws DataException {
|
||||||
// Does this transaction type bypass approval?
|
// Does this transaction type bypass approval?
|
||||||
if (this.transactionData.getType().skipsApproval)
|
if (!this.transactionData.getType().needsApproval)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
int txGroupId = this.getEffectiveGroupId();
|
int txGroupId = this.transactionData.getTxGroupId();
|
||||||
|
|
||||||
if (txGroupId == Group.NO_GROUP)
|
if (txGroupId == Group.NO_GROUP)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
Group group = new Group(repository, txGroupId);
|
GroupRepository groupRepository = this.repository.getGroupRepository();
|
||||||
if (group.getGroupData() == null) {
|
|
||||||
|
if (!groupRepository.groupExists(txGroupId))
|
||||||
// Group no longer exists? Possibly due to blockchain orphaning undoing group creation?
|
// Group no longer exists? Possibly due to blockchain orphaning undoing group creation?
|
||||||
return true;
|
return true;
|
||||||
}
|
|
||||||
|
|
||||||
GroupRepository groupRepository = this.repository.getGroupRepository();
|
|
||||||
|
|
||||||
// If transaction's creator is group admin then auto-approve
|
// If transaction's creator is group admin then auto-approve
|
||||||
PublicKeyAccount creator = this.getCreator();
|
PublicKeyAccount creator = this.getCreator();
|
||||||
if (groupRepository.adminExists(txGroupId, creator.getAddress()))
|
if (groupRepository.adminExists(txGroupId, creator.getAddress()))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
return group.getGroupData().getApprovalThreshold().needsApproval(repository, txGroupId, this.transactionData.getSignature());
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean meetsGroupApprovalThreshold() throws DataException {
|
||||||
|
int txGroupId = this.transactionData.getTxGroupId();
|
||||||
|
|
||||||
|
Group group = new Group(repository, txGroupId);
|
||||||
|
GroupData groupData = group.getGroupData();
|
||||||
|
|
||||||
|
// Is transaction is outside of min/max approval period?
|
||||||
|
int creationBlockHeight = this.repository.getBlockRepository().getHeightFromTimestamp(this.transactionData.getTimestamp());
|
||||||
|
int currentBlockHeight = this.repository.getBlockRepository().getBlockchainHeight();
|
||||||
|
if (currentBlockHeight < creationBlockHeight + groupData.getMinimumBlockDelay() || currentBlockHeight > creationBlockHeight + groupData.getMaximumBlockDelay())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return group.getGroupData().getApprovalThreshold().meetsApprovalThreshold(repository, txGroupId, this.transactionData.getSignature());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -15,6 +15,7 @@ import org.qora.transform.TransformationException;
|
|||||||
import org.qora.utils.Serialization;
|
import org.qora.utils.Serialization;
|
||||||
|
|
||||||
import com.google.common.base.Utf8;
|
import com.google.common.base.Utf8;
|
||||||
|
import com.google.common.primitives.Ints;
|
||||||
|
|
||||||
public class CreateGroupTransactionTransformer extends TransactionTransformer {
|
public class CreateGroupTransactionTransformer extends TransactionTransformer {
|
||||||
|
|
||||||
@ -42,6 +43,8 @@ public class CreateGroupTransactionTransformer extends TransactionTransformer {
|
|||||||
layout.add("group's description", TransformationType.STRING);
|
layout.add("group's description", TransformationType.STRING);
|
||||||
layout.add("is group \"open\"?", TransformationType.BOOLEAN);
|
layout.add("is group \"open\"?", TransformationType.BOOLEAN);
|
||||||
layout.add("group transaction approval threshold", TransformationType.BYTE);
|
layout.add("group transaction approval threshold", TransformationType.BYTE);
|
||||||
|
layout.add("minimum block delay for transaction approvals", TransformationType.INT);
|
||||||
|
layout.add("maximum block delay for transaction approvals", TransformationType.INT);
|
||||||
layout.add("fee", TransformationType.AMOUNT);
|
layout.add("fee", TransformationType.AMOUNT);
|
||||||
layout.add("signature", TransformationType.SIGNATURE);
|
layout.add("signature", TransformationType.SIGNATURE);
|
||||||
}
|
}
|
||||||
@ -68,13 +71,17 @@ public class CreateGroupTransactionTransformer extends TransactionTransformer {
|
|||||||
|
|
||||||
ApprovalThreshold approvalThreshold = ApprovalThreshold.valueOf(byteBuffer.get());
|
ApprovalThreshold approvalThreshold = ApprovalThreshold.valueOf(byteBuffer.get());
|
||||||
|
|
||||||
|
int minBlockDelay = byteBuffer.getInt();
|
||||||
|
|
||||||
|
int maxBlockDelay = byteBuffer.getInt();
|
||||||
|
|
||||||
BigDecimal fee = Serialization.deserializeBigDecimal(byteBuffer);
|
BigDecimal fee = Serialization.deserializeBigDecimal(byteBuffer);
|
||||||
|
|
||||||
byte[] signature = new byte[SIGNATURE_LENGTH];
|
byte[] signature = new byte[SIGNATURE_LENGTH];
|
||||||
byteBuffer.get(signature);
|
byteBuffer.get(signature);
|
||||||
|
|
||||||
return new CreateGroupTransactionData(timestamp, txGroupId, reference, creatorPublicKey, owner, groupName, description, isOpen, approvalThreshold, null,
|
return new CreateGroupTransactionData(timestamp, txGroupId, reference, creatorPublicKey, owner, groupName, description, isOpen, approvalThreshold,
|
||||||
fee, signature);
|
minBlockDelay, maxBlockDelay, null, fee, signature);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static int getDataLength(TransactionData transactionData) throws TransformationException {
|
public static int getDataLength(TransactionData transactionData) throws TransformationException {
|
||||||
@ -102,6 +109,10 @@ public class CreateGroupTransactionTransformer extends TransactionTransformer {
|
|||||||
|
|
||||||
bytes.write((byte) createGroupTransactionData.getApprovalThreshold().value);
|
bytes.write((byte) createGroupTransactionData.getApprovalThreshold().value);
|
||||||
|
|
||||||
|
bytes.write(Ints.toByteArray(createGroupTransactionData.getMinimumBlockDelay()));
|
||||||
|
|
||||||
|
bytes.write(Ints.toByteArray(createGroupTransactionData.getMaximumBlockDelay()));
|
||||||
|
|
||||||
Serialization.serializeBigDecimal(bytes, createGroupTransactionData.getFee());
|
Serialization.serializeBigDecimal(bytes, createGroupTransactionData.getFee());
|
||||||
|
|
||||||
if (createGroupTransactionData.getSignature() != null)
|
if (createGroupTransactionData.getSignature() != null)
|
||||||
|
@ -70,13 +70,17 @@ public class UpdateGroupTransactionTransformer extends TransactionTransformer {
|
|||||||
|
|
||||||
ApprovalThreshold newApprovalThreshold = ApprovalThreshold.valueOf(byteBuffer.get());
|
ApprovalThreshold newApprovalThreshold = ApprovalThreshold.valueOf(byteBuffer.get());
|
||||||
|
|
||||||
|
int newMinBlockDelay = byteBuffer.getInt();
|
||||||
|
|
||||||
|
int newMaxBlockDelay = byteBuffer.getInt();
|
||||||
|
|
||||||
BigDecimal fee = Serialization.deserializeBigDecimal(byteBuffer);
|
BigDecimal fee = Serialization.deserializeBigDecimal(byteBuffer);
|
||||||
|
|
||||||
byte[] signature = new byte[SIGNATURE_LENGTH];
|
byte[] signature = new byte[SIGNATURE_LENGTH];
|
||||||
byteBuffer.get(signature);
|
byteBuffer.get(signature);
|
||||||
|
|
||||||
return new UpdateGroupTransactionData(timestamp, txGroupId, reference, ownerPublicKey, groupId, newOwner, newDescription, newIsOpen,
|
return new UpdateGroupTransactionData(timestamp, txGroupId, reference, ownerPublicKey, groupId, newOwner, newDescription, newIsOpen,
|
||||||
newApprovalThreshold, fee, signature);
|
newApprovalThreshold, newMinBlockDelay, newMaxBlockDelay, fee, signature);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static int getDataLength(TransactionData transactionData) throws TransformationException {
|
public static int getDataLength(TransactionData transactionData) throws TransformationException {
|
||||||
@ -103,6 +107,10 @@ public class UpdateGroupTransactionTransformer extends TransactionTransformer {
|
|||||||
|
|
||||||
bytes.write((byte) updateGroupTransactionData.getNewApprovalThreshold().value);
|
bytes.write((byte) updateGroupTransactionData.getNewApprovalThreshold().value);
|
||||||
|
|
||||||
|
bytes.write(Ints.toByteArray(updateGroupTransactionData.getNewMinimumBlockDelay()));
|
||||||
|
|
||||||
|
bytes.write(Ints.toByteArray(updateGroupTransactionData.getNewMaximumBlockDelay()));
|
||||||
|
|
||||||
Serialization.serializeBigDecimal(bytes, updateGroupTransactionData.getFee());
|
Serialization.serializeBigDecimal(bytes, updateGroupTransactionData.getFee());
|
||||||
|
|
||||||
if (updateGroupTransactionData.getSignature() != null)
|
if (updateGroupTransactionData.getSignature() != null)
|
||||||
|
@ -496,13 +496,13 @@ public class v1feeder extends Thread {
|
|||||||
|
|
||||||
BigDecimal fee = BigDecimal.ZERO.setScale(8);
|
BigDecimal fee = BigDecimal.ZERO.setScale(8);
|
||||||
|
|
||||||
TransactionData transactionData = new ATTransactionData(timestamp, Group.DEFAULT_GROUP, reference, sender, recipient, amount, Asset.QORA, message, fee);
|
TransactionData transactionData = new ATTransactionData(timestamp, Group.NO_GROUP, reference, sender, recipient, amount, Asset.QORA, message, fee);
|
||||||
byte[] digest;
|
byte[] digest;
|
||||||
try {
|
try {
|
||||||
digest = Crypto.digest(AtTransactionTransformer.toBytes(transactionData));
|
digest = Crypto.digest(AtTransactionTransformer.toBytes(transactionData));
|
||||||
byte[] signature = Bytes.concat(digest, digest);
|
byte[] signature = Bytes.concat(digest, digest);
|
||||||
|
|
||||||
transactionData = new ATTransactionData(timestamp, Group.DEFAULT_GROUP, reference, sender, recipient, amount, Asset.QORA, message, fee, signature);
|
transactionData = new ATTransactionData(timestamp, Group.NO_GROUP, reference, sender, recipient, amount, Asset.QORA, message, fee, signature);
|
||||||
} catch (TransformationException e) {
|
} catch (TransformationException e) {
|
||||||
throw new RuntimeException("Couldn't transform AT Transaction into bytes", e);
|
throw new RuntimeException("Couldn't transform AT Transaction into bytes", e);
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@ NO_BALANCE=not enough balance
|
|||||||
NOT_YET_RELEASED=that feature is not yet released
|
NOT_YET_RELEASED=that feature is not yet released
|
||||||
UNAUTHORIZED=api call unauthorized
|
UNAUTHORIZED=api call unauthorized
|
||||||
REPOSITORY_ISSUE=repository error
|
REPOSITORY_ISSUE=repository error
|
||||||
|
NON_PRODUCTION=This API call is not permitted for production systems
|
||||||
|
|
||||||
# Validation
|
# Validation
|
||||||
INVALID_SIGNATURE=invalid signature
|
INVALID_SIGNATURE=invalid signature
|
||||||
|
@ -47,7 +47,7 @@ public class ATTests extends Common {
|
|||||||
byte[] reference = Base58.decode("2D3jX1pEgu6irsQ7QzJb85QP1D9M45dNyP5M9a3WFHndU5ZywF4F5pnUurcbzMnGMcTwpAY6H7DuLw8cUBU66ao1");
|
byte[] reference = Base58.decode("2D3jX1pEgu6irsQ7QzJb85QP1D9M45dNyP5M9a3WFHndU5ZywF4F5pnUurcbzMnGMcTwpAY6H7DuLw8cUBU66ao1");
|
||||||
byte[] signature = Base58.decode("2dZ4megUyNoYYY7qWmuSd4xw1yUKgPPF97yBbeddh8aKuC8PLpz7Xvf3r6Zjv1zwGrR8fEAHuaztCPD4KQp76KdL");
|
byte[] signature = Base58.decode("2dZ4megUyNoYYY7qWmuSd4xw1yUKgPPF97yBbeddh8aKuC8PLpz7Xvf3r6Zjv1zwGrR8fEAHuaztCPD4KQp76KdL");
|
||||||
|
|
||||||
DeployAtTransactionData transactionData = new DeployAtTransactionData(timestamp, Group.DEFAULT_GROUP, reference, creatorPublicKey, name, description, ATType,
|
DeployAtTransactionData transactionData = new DeployAtTransactionData(timestamp, Group.NO_GROUP, reference, creatorPublicKey, name, description, ATType,
|
||||||
tags, creationBytes, amount, Asset.QORA, fee, signature);
|
tags, creationBytes, amount, Asset.QORA, fee, signature);
|
||||||
|
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
|
@ -23,7 +23,7 @@ public class SaveTests extends Common {
|
|||||||
byte[] signature = Base58.decode(signature58);
|
byte[] signature = Base58.decode(signature58);
|
||||||
PublicKeyAccount sender = new PublicKeyAccount(repository, "Qsender".getBytes());
|
PublicKeyAccount sender = new PublicKeyAccount(repository, "Qsender".getBytes());
|
||||||
|
|
||||||
PaymentTransactionData paymentTransactionData = new PaymentTransactionData(Instant.now().getEpochSecond(), Group.DEFAULT_GROUP, reference,
|
PaymentTransactionData paymentTransactionData = new PaymentTransactionData(Instant.now().getEpochSecond(), Group.NO_GROUP, reference,
|
||||||
sender.getPublicKey(), "Qrecipient", BigDecimal.valueOf(12345L), BigDecimal.ONE, signature);
|
sender.getPublicKey(), "Qrecipient", BigDecimal.valueOf(12345L), BigDecimal.ONE, signature);
|
||||||
|
|
||||||
repository.getTransactionRepository().save(paymentTransactionData);
|
repository.getTransactionRepository().save(paymentTransactionData);
|
||||||
|
@ -121,7 +121,7 @@ public class TransactionTests {
|
|||||||
|
|
||||||
// Create test generator account
|
// Create test generator account
|
||||||
generator = new PrivateKeyAccount(repository, generatorSeed);
|
generator = new PrivateKeyAccount(repository, generatorSeed);
|
||||||
accountRepository.setLastReference(new AccountData(generator.getAddress(), generatorSeed, generator.getPublicKey(), Group.DEFAULT_GROUP));
|
accountRepository.setLastReference(new AccountData(generator.getAddress(), generatorSeed, generator.getPublicKey(), Group.NO_GROUP));
|
||||||
accountRepository.save(new AccountBalanceData(generator.getAddress(), Asset.QORA, initialGeneratorBalance));
|
accountRepository.save(new AccountBalanceData(generator.getAddress(), Asset.QORA, initialGeneratorBalance));
|
||||||
|
|
||||||
// Create test sender account
|
// Create test sender account
|
||||||
@ -129,7 +129,7 @@ public class TransactionTests {
|
|||||||
|
|
||||||
// Mock account
|
// Mock account
|
||||||
reference = senderSeed;
|
reference = senderSeed;
|
||||||
accountRepository.setLastReference(new AccountData(sender.getAddress(), reference, sender.getPublicKey(), Group.DEFAULT_GROUP));
|
accountRepository.setLastReference(new AccountData(sender.getAddress(), reference, sender.getPublicKey(), Group.NO_GROUP));
|
||||||
|
|
||||||
// Mock balance
|
// Mock balance
|
||||||
accountRepository.save(new AccountBalanceData(sender.getAddress(), Asset.QORA, initialSenderBalance));
|
accountRepository.save(new AccountBalanceData(sender.getAddress(), Asset.QORA, initialSenderBalance));
|
||||||
@ -147,7 +147,7 @@ public class TransactionTests {
|
|||||||
BigDecimal amount = genericPaymentAmount;
|
BigDecimal amount = genericPaymentAmount;
|
||||||
BigDecimal fee = BigDecimal.ONE;
|
BigDecimal fee = BigDecimal.ONE;
|
||||||
long timestamp = parentBlockData.getTimestamp() + 1_000;
|
long timestamp = parentBlockData.getTimestamp() + 1_000;
|
||||||
PaymentTransactionData paymentTransactionData = new PaymentTransactionData(timestamp, Group.DEFAULT_GROUP, reference, sender.getPublicKey(), recipient, amount, fee);
|
PaymentTransactionData paymentTransactionData = new PaymentTransactionData(timestamp, Group.NO_GROUP, reference, sender.getPublicKey(), recipient, amount, fee);
|
||||||
|
|
||||||
Transaction paymentTransaction = new PaymentTransaction(repository, paymentTransactionData);
|
Transaction paymentTransaction = new PaymentTransaction(repository, paymentTransactionData);
|
||||||
paymentTransaction.sign(sender);
|
paymentTransaction.sign(sender);
|
||||||
@ -164,7 +164,7 @@ public class TransactionTests {
|
|||||||
BigDecimal amount = BigDecimal.valueOf(1_000L);
|
BigDecimal amount = BigDecimal.valueOf(1_000L);
|
||||||
BigDecimal fee = BigDecimal.ONE;
|
BigDecimal fee = BigDecimal.ONE;
|
||||||
long timestamp = parentBlockData.getTimestamp() + 1_000;
|
long timestamp = parentBlockData.getTimestamp() + 1_000;
|
||||||
PaymentTransactionData paymentTransactionData = new PaymentTransactionData(timestamp, Group.DEFAULT_GROUP, reference, sender.getPublicKey(), recipient.getAddress(),
|
PaymentTransactionData paymentTransactionData = new PaymentTransactionData(timestamp, Group.NO_GROUP, reference, sender.getPublicKey(), recipient.getAddress(),
|
||||||
amount, fee);
|
amount, fee);
|
||||||
|
|
||||||
Transaction paymentTransaction = new PaymentTransaction(repository, paymentTransactionData);
|
Transaction paymentTransaction = new PaymentTransaction(repository, paymentTransactionData);
|
||||||
@ -225,7 +225,7 @@ public class TransactionTests {
|
|||||||
|
|
||||||
BigDecimal fee = BigDecimal.ONE;
|
BigDecimal fee = BigDecimal.ONE;
|
||||||
long timestamp = parentBlockData.getTimestamp() + 1_000;
|
long timestamp = parentBlockData.getTimestamp() + 1_000;
|
||||||
RegisterNameTransactionData registerNameTransactionData = new RegisterNameTransactionData(timestamp, Group.DEFAULT_GROUP, reference, sender.getPublicKey(), sender.getAddress(),
|
RegisterNameTransactionData registerNameTransactionData = new RegisterNameTransactionData(timestamp, Group.NO_GROUP, reference, sender.getPublicKey(), sender.getAddress(),
|
||||||
name, data, fee);
|
name, data, fee);
|
||||||
|
|
||||||
Transaction registerNameTransaction = new RegisterNameTransaction(repository, registerNameTransactionData);
|
Transaction registerNameTransaction = new RegisterNameTransaction(repository, registerNameTransactionData);
|
||||||
@ -281,7 +281,7 @@ public class TransactionTests {
|
|||||||
|
|
||||||
BigDecimal fee = BigDecimal.ONE;
|
BigDecimal fee = BigDecimal.ONE;
|
||||||
long timestamp = parentBlockData.getTimestamp() + 1_000;
|
long timestamp = parentBlockData.getTimestamp() + 1_000;
|
||||||
UpdateNameTransactionData updateNameTransactionData = new UpdateNameTransactionData(timestamp, Group.DEFAULT_GROUP, reference, sender.getPublicKey(),
|
UpdateNameTransactionData updateNameTransactionData = new UpdateNameTransactionData(timestamp, Group.NO_GROUP, reference, sender.getPublicKey(),
|
||||||
newOwner.getAddress(), name, newData, nameReference, fee);
|
newOwner.getAddress(), name, newData, nameReference, fee);
|
||||||
|
|
||||||
Transaction updateNameTransaction = new UpdateNameTransaction(repository, updateNameTransactionData);
|
Transaction updateNameTransaction = new UpdateNameTransaction(repository, updateNameTransactionData);
|
||||||
@ -327,7 +327,7 @@ public class TransactionTests {
|
|||||||
|
|
||||||
BigDecimal fee = BigDecimal.ONE;
|
BigDecimal fee = BigDecimal.ONE;
|
||||||
long timestamp = parentBlockData.getTimestamp() + 1_000;
|
long timestamp = parentBlockData.getTimestamp() + 1_000;
|
||||||
SellNameTransactionData sellNameTransactionData = new SellNameTransactionData(timestamp, Group.DEFAULT_GROUP, reference, sender.getPublicKey(), name, amount, fee);
|
SellNameTransactionData sellNameTransactionData = new SellNameTransactionData(timestamp, Group.NO_GROUP, reference, sender.getPublicKey(), name, amount, fee);
|
||||||
|
|
||||||
Transaction sellNameTransaction = new SellNameTransaction(repository, sellNameTransactionData);
|
Transaction sellNameTransaction = new SellNameTransaction(repository, sellNameTransactionData);
|
||||||
sellNameTransaction.sign(sender);
|
sellNameTransaction.sign(sender);
|
||||||
@ -378,7 +378,7 @@ public class TransactionTests {
|
|||||||
|
|
||||||
BigDecimal fee = BigDecimal.ONE;
|
BigDecimal fee = BigDecimal.ONE;
|
||||||
long timestamp = parentBlockData.getTimestamp() + 1_000;
|
long timestamp = parentBlockData.getTimestamp() + 1_000;
|
||||||
CancelSellNameTransactionData cancelSellNameTransactionData = new CancelSellNameTransactionData(timestamp, Group.DEFAULT_GROUP, reference, sender.getPublicKey(), name, fee);
|
CancelSellNameTransactionData cancelSellNameTransactionData = new CancelSellNameTransactionData(timestamp, Group.NO_GROUP, reference, sender.getPublicKey(), name, fee);
|
||||||
|
|
||||||
Transaction cancelSellNameTransaction = new CancelSellNameTransaction(repository, cancelSellNameTransactionData);
|
Transaction cancelSellNameTransaction = new CancelSellNameTransaction(repository, cancelSellNameTransactionData);
|
||||||
cancelSellNameTransaction.sign(sender);
|
cancelSellNameTransaction.sign(sender);
|
||||||
@ -443,7 +443,7 @@ public class TransactionTests {
|
|||||||
|
|
||||||
BigDecimal fee = BigDecimal.ONE;
|
BigDecimal fee = BigDecimal.ONE;
|
||||||
long timestamp = parentBlockData.getTimestamp() + 1_000;
|
long timestamp = parentBlockData.getTimestamp() + 1_000;
|
||||||
BuyNameTransactionData buyNameTransactionData = new BuyNameTransactionData(timestamp, Group.DEFAULT_GROUP, buyersReference, buyer.getPublicKey(),
|
BuyNameTransactionData buyNameTransactionData = new BuyNameTransactionData(timestamp, Group.NO_GROUP, buyersReference, buyer.getPublicKey(),
|
||||||
name, originalNameData.getSalePrice(), seller, nameReference, fee);
|
name, originalNameData.getSalePrice(), seller, nameReference, fee);
|
||||||
|
|
||||||
Transaction buyNameTransaction = new BuyNameTransaction(repository, buyNameTransactionData);
|
Transaction buyNameTransaction = new BuyNameTransaction(repository, buyNameTransactionData);
|
||||||
@ -496,7 +496,7 @@ public class TransactionTests {
|
|||||||
Account recipient = new PublicKeyAccount(repository, recipientSeed);
|
Account recipient = new PublicKeyAccount(repository, recipientSeed);
|
||||||
BigDecimal fee = BigDecimal.ONE;
|
BigDecimal fee = BigDecimal.ONE;
|
||||||
long timestamp = parentBlockData.getTimestamp() + 1_000;
|
long timestamp = parentBlockData.getTimestamp() + 1_000;
|
||||||
CreatePollTransactionData createPollTransactionData = new CreatePollTransactionData(timestamp, Group.DEFAULT_GROUP, reference,
|
CreatePollTransactionData createPollTransactionData = new CreatePollTransactionData(timestamp, Group.NO_GROUP, reference,
|
||||||
sender.getPublicKey(), recipient.getAddress(), pollName, description, pollOptions, fee);
|
sender.getPublicKey(), recipient.getAddress(), pollName, description, pollOptions, fee);
|
||||||
|
|
||||||
Transaction createPollTransaction = new CreatePollTransaction(repository, createPollTransactionData);
|
Transaction createPollTransaction = new CreatePollTransaction(repository, createPollTransactionData);
|
||||||
@ -550,7 +550,7 @@ public class TransactionTests {
|
|||||||
|
|
||||||
for (int optionIndex = 0; optionIndex <= pollOptionsSize; ++optionIndex) {
|
for (int optionIndex = 0; optionIndex <= pollOptionsSize; ++optionIndex) {
|
||||||
// Make a vote-on-poll transaction
|
// Make a vote-on-poll transaction
|
||||||
VoteOnPollTransactionData voteOnPollTransactionData = new VoteOnPollTransactionData(timestamp, Group.DEFAULT_GROUP, reference, sender.getPublicKey(), pollName,
|
VoteOnPollTransactionData voteOnPollTransactionData = new VoteOnPollTransactionData(timestamp, Group.NO_GROUP, reference, sender.getPublicKey(), pollName,
|
||||||
optionIndex, fee);
|
optionIndex, fee);
|
||||||
|
|
||||||
Transaction voteOnPollTransaction = new VoteOnPollTransaction(repository, voteOnPollTransactionData);
|
Transaction voteOnPollTransaction = new VoteOnPollTransaction(repository, voteOnPollTransactionData);
|
||||||
@ -622,7 +622,7 @@ public class TransactionTests {
|
|||||||
BigDecimal fee = BigDecimal.ONE;
|
BigDecimal fee = BigDecimal.ONE;
|
||||||
long timestamp = parentBlockData.getTimestamp() + 1_000;
|
long timestamp = parentBlockData.getTimestamp() + 1_000;
|
||||||
|
|
||||||
IssueAssetTransactionData issueAssetTransactionData = new IssueAssetTransactionData(timestamp, Group.DEFAULT_GROUP, reference, sender.getPublicKey(),
|
IssueAssetTransactionData issueAssetTransactionData = new IssueAssetTransactionData(timestamp, Group.NO_GROUP, reference, sender.getPublicKey(),
|
||||||
sender.getAddress(), assetName, description, quantity, isDivisible, fee);
|
sender.getAddress(), assetName, description, quantity, isDivisible, fee);
|
||||||
|
|
||||||
Transaction issueAssetTransaction = new IssueAssetTransaction(repository, issueAssetTransactionData);
|
Transaction issueAssetTransaction = new IssueAssetTransaction(repository, issueAssetTransactionData);
|
||||||
@ -712,7 +712,7 @@ public class TransactionTests {
|
|||||||
BigDecimal fee = BigDecimal.ONE;
|
BigDecimal fee = BigDecimal.ONE;
|
||||||
long timestamp = parentBlockData.getTimestamp() + 1_000;
|
long timestamp = parentBlockData.getTimestamp() + 1_000;
|
||||||
|
|
||||||
TransferAssetTransactionData transferAssetTransactionData = new TransferAssetTransactionData(timestamp, Group.DEFAULT_GROUP, reference,
|
TransferAssetTransactionData transferAssetTransactionData = new TransferAssetTransactionData(timestamp, Group.NO_GROUP, reference,
|
||||||
sender.getPublicKey(), recipient.getAddress(), amount, assetId, fee);
|
sender.getPublicKey(), recipient.getAddress(), amount, assetId, fee);
|
||||||
|
|
||||||
Transaction transferAssetTransaction = new TransferAssetTransaction(repository, transferAssetTransactionData);
|
Transaction transferAssetTransaction = new TransferAssetTransaction(repository, transferAssetTransactionData);
|
||||||
@ -817,7 +817,7 @@ public class TransactionTests {
|
|||||||
BigDecimal fee = BigDecimal.ONE;
|
BigDecimal fee = BigDecimal.ONE;
|
||||||
long timestamp = parentBlockData.getTimestamp() + 1_000;
|
long timestamp = parentBlockData.getTimestamp() + 1_000;
|
||||||
|
|
||||||
CreateAssetOrderTransactionData createOrderTransactionData = new CreateAssetOrderTransactionData(timestamp, Group.DEFAULT_GROUP, buyersReference, buyer.getPublicKey(), haveAssetId,
|
CreateAssetOrderTransactionData createOrderTransactionData = new CreateAssetOrderTransactionData(timestamp, Group.NO_GROUP, buyersReference, buyer.getPublicKey(), haveAssetId,
|
||||||
wantAssetId, amount, price, fee);
|
wantAssetId, amount, price, fee);
|
||||||
Transaction createOrderTransaction = new CreateAssetOrderTransaction(this.repository, createOrderTransactionData);
|
Transaction createOrderTransaction = new CreateAssetOrderTransaction(this.repository, createOrderTransactionData);
|
||||||
createOrderTransaction.sign(buyer);
|
createOrderTransaction.sign(buyer);
|
||||||
@ -898,7 +898,7 @@ public class TransactionTests {
|
|||||||
BigDecimal fee = BigDecimal.ONE;
|
BigDecimal fee = BigDecimal.ONE;
|
||||||
long timestamp = parentBlockData.getTimestamp() + 1_000;
|
long timestamp = parentBlockData.getTimestamp() + 1_000;
|
||||||
byte[] buyersReference = buyer.getLastReference();
|
byte[] buyersReference = buyer.getLastReference();
|
||||||
CancelAssetOrderTransactionData cancelOrderTransactionData = new CancelAssetOrderTransactionData(timestamp, Group.DEFAULT_GROUP, buyersReference, buyer.getPublicKey(), orderId, fee);
|
CancelAssetOrderTransactionData cancelOrderTransactionData = new CancelAssetOrderTransactionData(timestamp, Group.NO_GROUP, buyersReference, buyer.getPublicKey(), orderId, fee);
|
||||||
|
|
||||||
Transaction cancelOrderTransaction = new CancelAssetOrderTransaction(this.repository, cancelOrderTransactionData);
|
Transaction cancelOrderTransaction = new CancelAssetOrderTransaction(this.repository, cancelOrderTransactionData);
|
||||||
cancelOrderTransaction.sign(buyer);
|
cancelOrderTransaction.sign(buyer);
|
||||||
@ -973,7 +973,7 @@ public class TransactionTests {
|
|||||||
long timestamp = parentBlockData.getTimestamp() + 1_000;
|
long timestamp = parentBlockData.getTimestamp() + 1_000;
|
||||||
BigDecimal senderPreTradeWantBalance = sender.getConfirmedBalance(wantAssetId);
|
BigDecimal senderPreTradeWantBalance = sender.getConfirmedBalance(wantAssetId);
|
||||||
|
|
||||||
CreateAssetOrderTransactionData createOrderTransactionData = new CreateAssetOrderTransactionData(timestamp, Group.DEFAULT_GROUP, reference, sender.getPublicKey(), haveAssetId,
|
CreateAssetOrderTransactionData createOrderTransactionData = new CreateAssetOrderTransactionData(timestamp, Group.NO_GROUP, reference, sender.getPublicKey(), haveAssetId,
|
||||||
wantAssetId, amount, price, fee);
|
wantAssetId, amount, price, fee);
|
||||||
Transaction createOrderTransaction = new CreateAssetOrderTransaction(this.repository, createOrderTransactionData);
|
Transaction createOrderTransaction = new CreateAssetOrderTransaction(this.repository, createOrderTransactionData);
|
||||||
createOrderTransaction.sign(sender);
|
createOrderTransaction.sign(sender);
|
||||||
@ -1082,7 +1082,7 @@ public class TransactionTests {
|
|||||||
payments.add(paymentData);
|
payments.add(paymentData);
|
||||||
}
|
}
|
||||||
|
|
||||||
MultiPaymentTransactionData multiPaymentTransactionData = new MultiPaymentTransactionData(timestamp, Group.DEFAULT_GROUP, reference, sender.getPublicKey(), payments, fee);
|
MultiPaymentTransactionData multiPaymentTransactionData = new MultiPaymentTransactionData(timestamp, Group.NO_GROUP, reference, sender.getPublicKey(), payments, fee);
|
||||||
|
|
||||||
Transaction multiPaymentTransaction = new MultiPaymentTransaction(repository, multiPaymentTransactionData);
|
Transaction multiPaymentTransaction = new MultiPaymentTransaction(repository, multiPaymentTransactionData);
|
||||||
multiPaymentTransaction.sign(sender);
|
multiPaymentTransaction.sign(sender);
|
||||||
@ -1151,7 +1151,7 @@ public class TransactionTests {
|
|||||||
boolean isText = true;
|
boolean isText = true;
|
||||||
boolean isEncrypted = false;
|
boolean isEncrypted = false;
|
||||||
|
|
||||||
MessageTransactionData messageTransactionData = new MessageTransactionData(timestamp, Group.DEFAULT_GROUP, reference, sender.getPublicKey(), version,
|
MessageTransactionData messageTransactionData = new MessageTransactionData(timestamp, Group.NO_GROUP, reference, sender.getPublicKey(), version,
|
||||||
recipient.getAddress(), Asset.QORA, amount, data, isText, isEncrypted, fee);
|
recipient.getAddress(), Asset.QORA, amount, data, isText, isEncrypted, fee);
|
||||||
|
|
||||||
Transaction messageTransaction = new MessageTransaction(repository, messageTransactionData);
|
Transaction messageTransaction = new MessageTransaction(repository, messageTransactionData);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user