diff --git a/settings.json b/settings.json deleted file mode 100644 index e60b3697..00000000 --- a/settings.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "rpcallowed": [ - "::/0", - "0.0.0.0/0" - ] -} diff --git a/src/main/java/org/qora/api/ApiError.java b/src/main/java/org/qora/api/ApiError.java index 9634363e..49635754 100644 --- a/src/main/java/org/qora/api/ApiError.java +++ b/src/main/java/org/qora/api/ApiError.java @@ -13,6 +13,7 @@ public enum ApiError { NOT_YET_RELEASED(3, 422), UNAUTHORIZED(4, 403), REPOSITORY_ISSUE(5, 500), + NON_PRODUCTION(6, 403), // VALIDATION INVALID_SIGNATURE(101, 400), diff --git a/src/main/java/org/qora/api/resource/AddressesResource.java b/src/main/java/org/qora/api/resource/AddressesResource.java index f3bacb6d..ea68ae17 100644 --- a/src/main/java/org/qora/api/resource/AddressesResource.java +++ b/src/main/java/org/qora/api/resource/AddressesResource.java @@ -22,11 +22,14 @@ import org.qora.api.ApiErrors; import org.qora.api.ApiException; import org.qora.api.ApiExceptionFactory; import org.qora.asset.Asset; +import org.qora.block.BlockChain; import org.qora.crypto.Crypto; import org.qora.data.account.AccountData; +import org.qora.group.Group; import org.qora.repository.DataException; import org.qora.repository.Repository; import org.qora.repository.RepositoryManager; +import org.qora.settings.Settings; import org.qora.transform.Transformer; import org.qora.utils.Base58; @@ -55,7 +58,17 @@ public class AddressesResource { throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_ADDRESS); 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) { throw 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) { + if (Settings.getInstance().isRestrictedApi()) + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.NON_PRODUCTION); + // Decode public key byte[] publicKey; try { diff --git a/src/main/java/org/qora/api/resource/AssetsResource.java b/src/main/java/org/qora/api/resource/AssetsResource.java index d721cd84..a0e8b61c 100644 --- a/src/main/java/org/qora/api/resource/AssetsResource.java +++ b/src/main/java/org/qora/api/resource/AssetsResource.java @@ -43,6 +43,7 @@ import org.qora.data.transaction.TransactionData; import org.qora.repository.DataException; import org.qora.repository.Repository; import org.qora.repository.RepositoryManager; +import org.qora.settings.Settings; import org.qora.transaction.Transaction; import org.qora.transaction.Transaction.ValidationResult; import org.qora.transform.TransformationException; @@ -554,9 +555,12 @@ public class AssetsResource { } ) @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) { + if (Settings.getInstance().isRestrictedApi()) + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.NON_PRODUCTION); + try (final Repository repository = RepositoryManager.getRepository()) { Transaction transaction = Transaction.fromData(repository, transactionData); @@ -599,9 +603,12 @@ public class AssetsResource { } ) @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) { + if (Settings.getInstance().isRestrictedApi()) + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.NON_PRODUCTION); + try (final Repository repository = RepositoryManager.getRepository()) { Transaction transaction = Transaction.fromData(repository, transactionData); @@ -644,9 +651,12 @@ public class AssetsResource { } ) @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) { + if (Settings.getInstance().isRestrictedApi()) + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.NON_PRODUCTION); + try (final Repository repository = RepositoryManager.getRepository()) { Transaction transaction = Transaction.fromData(repository, transactionData); diff --git a/src/main/java/org/qora/api/resource/GroupsResource.java b/src/main/java/org/qora/api/resource/GroupsResource.java index da68de54..76723917 100644 --- a/src/main/java/org/qora/api/resource/GroupsResource.java +++ b/src/main/java/org/qora/api/resource/GroupsResource.java @@ -51,6 +51,7 @@ import org.qora.data.transaction.UpdateGroupTransactionData; import org.qora.repository.DataException; import org.qora.repository.Repository; import org.qora.repository.RepositoryManager; +import org.qora.settings.Settings; import org.qora.transaction.Transaction; import org.qora.transaction.Transaction.ValidationResult; 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) { + if (Settings.getInstance().isRestrictedApi()) + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.NON_PRODUCTION); + try (final Repository repository = RepositoryManager.getRepository()) { 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) { + if (Settings.getInstance().isRestrictedApi()) + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.NON_PRODUCTION); + try (final Repository repository = RepositoryManager.getRepository()) { 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) { + if (Settings.getInstance().isRestrictedApi()) + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.NON_PRODUCTION); + try (final Repository repository = RepositoryManager.getRepository()) { 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) { + if (Settings.getInstance().isRestrictedApi()) + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.NON_PRODUCTION); + try (final Repository repository = RepositoryManager.getRepository()) { 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) { + if (Settings.getInstance().isRestrictedApi()) + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.NON_PRODUCTION); + try (final Repository repository = RepositoryManager.getRepository()) { 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) { + if (Settings.getInstance().isRestrictedApi()) + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.NON_PRODUCTION); + try (final Repository repository = RepositoryManager.getRepository()) { 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) { + if (Settings.getInstance().isRestrictedApi()) + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.NON_PRODUCTION); + try (final Repository repository = RepositoryManager.getRepository()) { 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) { + if (Settings.getInstance().isRestrictedApi()) + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.NON_PRODUCTION); + try (final Repository repository = RepositoryManager.getRepository()) { 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) { + if (Settings.getInstance().isRestrictedApi()) + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.NON_PRODUCTION); + try (final Repository repository = RepositoryManager.getRepository()) { 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) { + if (Settings.getInstance().isRestrictedApi()) + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.NON_PRODUCTION); + try (final Repository repository = RepositoryManager.getRepository()) { 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) { + if (Settings.getInstance().isRestrictedApi()) + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.NON_PRODUCTION); + try (final Repository repository = RepositoryManager.getRepository()) { 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) { + if (Settings.getInstance().isRestrictedApi()) + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.NON_PRODUCTION); + try (final Repository repository = RepositoryManager.getRepository()) { 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) { + if (Settings.getInstance().isRestrictedApi()) + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.NON_PRODUCTION); + try (final Repository repository = RepositoryManager.getRepository()) { Transaction transaction = Transaction.fromData(repository, transactionData); diff --git a/src/main/java/org/qora/api/resource/NamesResource.java b/src/main/java/org/qora/api/resource/NamesResource.java index 95d19396..318d9ccb 100644 --- a/src/main/java/org/qora/api/resource/NamesResource.java +++ b/src/main/java/org/qora/api/resource/NamesResource.java @@ -36,6 +36,7 @@ import org.qora.data.transaction.UpdateNameTransactionData; import org.qora.repository.DataException; import org.qora.repository.Repository; import org.qora.repository.RepositoryManager; +import org.qora.settings.Settings; import org.qora.transaction.Transaction; import org.qora.transaction.Transaction.ValidationResult; 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) { + if (Settings.getInstance().isRestrictedApi()) + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.NON_PRODUCTION); + try (final Repository repository = RepositoryManager.getRepository()) { 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) { + if (Settings.getInstance().isRestrictedApi()) + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.NON_PRODUCTION); + try (final Repository repository = RepositoryManager.getRepository()) { 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) { + if (Settings.getInstance().isRestrictedApi()) + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.NON_PRODUCTION); + try (final Repository repository = RepositoryManager.getRepository()) { 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) { + if (Settings.getInstance().isRestrictedApi()) + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.NON_PRODUCTION); + try (final Repository repository = RepositoryManager.getRepository()) { 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) { + if (Settings.getInstance().isRestrictedApi()) + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.NON_PRODUCTION); + try (final Repository repository = RepositoryManager.getRepository()) { Transaction transaction = Transaction.fromData(repository, transactionData); diff --git a/src/main/java/org/qora/api/resource/PaymentsResource.java b/src/main/java/org/qora/api/resource/PaymentsResource.java index ddce2712..c2bc15e4 100644 --- a/src/main/java/org/qora/api/resource/PaymentsResource.java +++ b/src/main/java/org/qora/api/resource/PaymentsResource.java @@ -21,6 +21,7 @@ import org.qora.data.transaction.PaymentTransactionData; import org.qora.repository.DataException; import org.qora.repository.Repository; import org.qora.repository.RepositoryManager; +import org.qora.settings.Settings; import org.qora.transaction.Transaction; import org.qora.transaction.Transaction.ValidationResult; import org.qora.transform.TransformationException; @@ -60,8 +61,11 @@ public class PaymentsResource { ) } ) - @ApiErrors({ApiError.TRANSACTION_INVALID, ApiError.TRANSFORMATION_ERROR, ApiError.REPOSITORY_ISSUE}) - public String buildTransaction(PaymentTransactionData transactionData) { + @ApiErrors({ApiError.NON_PRODUCTION, ApiError.TRANSACTION_INVALID, ApiError.TRANSFORMATION_ERROR, ApiError.REPOSITORY_ISSUE}) + public String makePayment(PaymentTransactionData transactionData) { + if (Settings.getInstance().isRestrictedApi()) + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.NON_PRODUCTION); + try (final Repository repository = RepositoryManager.getRepository()) { Transaction transaction = Transaction.fromData(repository, transactionData); diff --git a/src/main/java/org/qora/api/resource/TransactionsResource.java b/src/main/java/org/qora/api/resource/TransactionsResource.java index d81217e1..8b4ef832 100644 --- a/src/main/java/org/qora/api/resource/TransactionsResource.java +++ b/src/main/java/org/qora/api/resource/TransactionsResource.java @@ -34,6 +34,7 @@ import org.qora.globalization.Translator; import org.qora.repository.DataException; import org.qora.repository.Repository; import org.qora.repository.RepositoryManager; +import org.qora.settings.Settings; import org.qora.transaction.Transaction; import org.qora.transaction.Transaction.TransactionType; import org.qora.transaction.Transaction.ValidationResult; @@ -246,7 +247,7 @@ public class TransactionsResource { @ApiErrors({ ApiError.REPOSITORY_ISSUE }) - public List getPendingTransactions(@QueryParam("groupId") Integer groupId, @Parameter( + public List getPendingTransactions(@QueryParam("txGroupId") Integer txGroupId, @Parameter( ref = "limit" ) @QueryParam("limit") Integer limit, @Parameter( ref = "offset" @@ -254,22 +255,7 @@ public class TransactionsResource { ref = "reverse" ) @QueryParam("reverse") Boolean reverse) { try (final Repository repository = RepositoryManager.getRepository()) { - List transactions = repository.getTransactionRepository().getUnconfirmedTransactions(null, null, 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; + return repository.getTransactionRepository().getPendingTransactions(txGroupId, limit, offset, reverse); } catch (ApiException e) { throw e; } catch (DataException e) { @@ -366,9 +352,12 @@ public class TransactionsResource { } ) @ApiErrors({ - ApiError.INVALID_PRIVATE_KEY, ApiError.TRANSFORMATION_ERROR + ApiError.NON_PRODUCTION, ApiError.INVALID_PRIVATE_KEY, ApiError.TRANSFORMATION_ERROR }) public String signTransaction(SimpleTransactionSignRequest signRequest) { + if (Settings.getInstance().isRestrictedApi()) + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.NON_PRODUCTION); + if (signRequest.transactionBytes.length == 0) throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.JSON); diff --git a/src/main/java/org/qora/api/resource/UtilsResource.java b/src/main/java/org/qora/api/resource/UtilsResource.java index 367a03fa..a7a02f15 100644 --- a/src/main/java/org/qora/api/resource/UtilsResource.java +++ b/src/main/java/org/qora/api/resource/UtilsResource.java @@ -29,6 +29,7 @@ import org.qora.api.ApiError; import org.qora.api.ApiErrors; import org.qora.api.ApiExceptionFactory; import org.qora.crypto.Crypto; +import org.qora.settings.Settings; import org.qora.transaction.Transaction.TransactionType; import org.qora.transform.transaction.TransactionTransformer; 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) { + if (Settings.getInstance().isRestrictedApi()) + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.NON_PRODUCTION); + try { return HashCode.fromBytes(Base64.getDecoder().decode(base64.trim())).toString(); } 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) { + if (Settings.getInstance().isRestrictedApi()) + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.NON_PRODUCTION); + try { return HashCode.fromBytes(Base58.decode(base58.trim())).toString(); } catch (NumberFormatException e) { @@ -133,7 +140,11 @@ public class UtilsResource { ) } ) + @ApiErrors({ApiError.NON_PRODUCTION}) 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()); } @@ -152,7 +163,11 @@ public class UtilsResource { ) } ) + @ApiErrors({ApiError.NON_PRODUCTION}) 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()); } @@ -173,7 +188,11 @@ public class UtilsResource { ) } ) + @ApiErrors({ApiError.NON_PRODUCTION}) public String random(@QueryParam("length") Integer length) { + if (Settings.getInstance().isRestrictedApi()) + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.NON_PRODUCTION); + if (length == null) 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) { + 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. * 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) { + if (Settings.getInstance().isRestrictedApi()) + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.NON_PRODUCTION); + if (mnemonic.isEmpty()) 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) { + if (Settings.getInstance().isRestrictedApi()) + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.NON_PRODUCTION); + byte[] entropy; try { 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) { + if (Settings.getInstance().isRestrictedApi()) + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.NON_PRODUCTION); + byte[] privateKey; try { privateKey = Base58.decode(privateKey58); diff --git a/src/main/java/org/qora/at/QoraATAPI.java b/src/main/java/org/qora/at/QoraATAPI.java index d650e5dd..7a32a64c 100644 --- a/src/main/java/org/qora/at/QoraATAPI.java +++ b/src/main/java/org/qora/at/QoraATAPI.java @@ -268,7 +268,7 @@ public class QoraATAPI extends API { byte[] reference = this.getLastReference(); 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)); AtTransaction atTransaction = new AtTransaction(this.repository, atTransactionData); @@ -286,7 +286,7 @@ public class QoraATAPI extends API { long timestamp = this.getNextTransactionTimestamp(); 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)); AtTransaction atTransaction = new AtTransaction(this.repository, atTransactionData); @@ -312,7 +312,7 @@ public class QoraATAPI extends API { byte[] reference = this.getLastReference(); 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)); AtTransaction atTransaction = new AtTransaction(this.repository, atTransactionData); diff --git a/src/main/java/org/qora/block/Block.java b/src/main/java/org/qora/block/Block.java index 9fcb18fa..012e932b 100644 --- a/src/main/java/org/qora/block/Block.java +++ b/src/main/java/org/qora/block/Block.java @@ -83,6 +83,7 @@ public class Block { TRANSACTION_INVALID(52), TRANSACTION_PROCESSING_FAILED(53), TRANSACTION_ALREADY_PROCESSED(54), + TRANSACTION_NEEDS_APPROVAL(55), AT_STATES_MISMATCH(61); public final int value; @@ -834,6 +835,10 @@ public class Block { if (this.repository.getTransactionRepository().isConfirmed(transaction.getTransactionData().getSignature())) 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 // NOTE: in Gen1 there was an extra block height passed to DeployATTransaction.isValid Transaction.ValidationResult validationResult = transaction.isValid(); diff --git a/src/main/java/org/qora/block/BlockChain.java b/src/main/java/org/qora/block/BlockChain.java index 4fee23fe..33e924f3 100644 --- a/src/main/java/org/qora/block/BlockChain.java +++ b/src/main/java/org/qora/block/BlockChain.java @@ -36,11 +36,12 @@ public class BlockChain { private static BlockChain instance = null; // Properties + private boolean isTestNet; private BigDecimal unitFee; private BigDecimal maxBytesPerUnitFee; private BigDecimal minFeePerByte; /** Maximum coin supply. */ - private BigDecimal maxBalance;; + private BigDecimal maxBalance; /** Number of blocks between recalculating block's generating balance. */ private int blockDifficultyInterval; /** Minimum target time between blocks, in seconds. */ @@ -51,7 +52,7 @@ public class BlockChain { private long blockTimestampMargin; /** Whether transactions with txGroupId of NO_GROUP are allowed */ 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; /** Map of which blockchain features are enabled when (height/timestamp) */ private Map> featureTriggers; @@ -74,6 +75,10 @@ public class BlockChain { // Getters / setters + public boolean getIsTestNet() { + return this.isTestNet; + } + public BigDecimal getUnitFee() { return this.unitFee; } @@ -190,11 +195,15 @@ public class BlockChain { // 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 - 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"); 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"); long maxBytesPerUnitFee = (Long) Settings.getTypedJson(json, "maxBytesPerUnitFee", Long.class); BigDecimal maxBalance = Settings.getJsonBigDecimal(json, "coinSupply"); @@ -229,6 +238,7 @@ public class BlockChain { } instance = new BlockChain(); + instance.isTestNet = isTestNet; instance.unitFee = unitFee; instance.maxBytesPerUnitFee = BigDecimal.valueOf(maxBytesPerUnitFee).setScale(8); instance.minFeePerByte = unitFee.divide(instance.maxBytesPerUnitFee, MathContext.DECIMAL32); diff --git a/src/main/java/org/qora/block/BlockGenerator.java b/src/main/java/org/qora/block/BlockGenerator.java index d210a3ad..a8c04a1b 100644 --- a/src/main/java/org/qora/block/BlockGenerator.java +++ b/src/main/java/org/qora/block/BlockGenerator.java @@ -169,16 +169,13 @@ public class BlockGenerator extends Thread { } // Ignore transactions that have not met group-admin approval threshold - if (transaction.needsGroupApproval()) { + if (transaction.needsGroupApproval() && !transaction.meetsGroupApprovalThreshold()) { unconfirmedTransactions.remove(i); --i; 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 // If a transaction makes the block invalid then skip it and it'll either expire or be in next block. for (TransactionData transactionData : unconfirmedTransactions) { diff --git a/src/main/java/org/qora/data/account/AccountData.java b/src/main/java/org/qora/data/account/AccountData.java index 15b5fbb3..0ea79259 100644 --- a/src/main/java/org/qora/data/account/AccountData.java +++ b/src/main/java/org/qora/data/account/AccountData.java @@ -29,7 +29,7 @@ public class AccountData { } public AccountData(String address) { - this(address, null, null, Group.DEFAULT_GROUP); + this(address, null, null, Group.NO_GROUP); } // Getters/Setters diff --git a/src/main/java/org/qora/data/group/GroupData.java b/src/main/java/org/qora/data/group/GroupData.java index 09a487c0..75e50463 100644 --- a/src/main/java/org/qora/data/group/GroupData.java +++ b/src/main/java/org/qora/data/group/GroupData.java @@ -21,6 +21,8 @@ public class GroupData { private Long updated; private boolean isOpen; private ApprovalThreshold approvalThreshold; + private int minimumBlockDelay; + private int maximumBlockDelay; /** Reference to CREATE_GROUP or UPDATE_GROUP transaction, used to rebuild group during orphaning. */ // No need to ever expose this via API @XmlTransient @@ -34,7 +36,7 @@ public class GroupData { } /** Constructs new GroupData with nullable groupId and nullable updated [timestamp] */ - public GroupData(Integer groupId, String owner, String name, String description, long created, Long updated, boolean isOpen, ApprovalThreshold approvalThreshold, 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.owner = owner; this.groupName = name; @@ -44,11 +46,13 @@ public class GroupData { this.isOpen = isOpen; this.approvalThreshold = approvalThreshold; this.reference = reference; + this.minimumBlockDelay = minBlockDelay; + this.maximumBlockDelay = maxBlockDelay; } /** Constructs new GroupData with unassigned groupId */ - public GroupData(String owner, String name, String description, long created, boolean isOpen, ApprovalThreshold approvalThreshold, byte[] reference) { - this(null, owner, name, description, created, null, isOpen, approvalThreshold, 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, minBlockDelay, maxBlockDelay, reference); } // Getters / setters @@ -117,4 +121,12 @@ public class GroupData { this.approvalThreshold = approvalThreshold; } + public int getMinimumBlockDelay() { + return this.minimumBlockDelay; + } + + public int getMaximumBlockDelay() { + return this.maximumBlockDelay; + } + } diff --git a/src/main/java/org/qora/data/transaction/CreateGroupTransactionData.java b/src/main/java/org/qora/data/transaction/CreateGroupTransactionData.java index a07fc0ee..06fd4e27 100644 --- a/src/main/java/org/qora/data/transaction/CreateGroupTransactionData.java +++ b/src/main/java/org/qora/data/transaction/CreateGroupTransactionData.java @@ -52,6 +52,10 @@ public class CreateGroupTransactionData extends TransactionData { description = "how many group admins are required to approve group member transactions" ) private ApprovalThreshold approvalThreshold; + @Schema(description = "minimum block delay before approval takes effect") + private int minimumBlockDelay; + @Schema(description = "maximum block delay before which transaction approval must be reached") + private int maximumBlockDelay; // 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, - 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); this.creatorPublicKey = creatorPublicKey; @@ -70,6 +74,8 @@ public class CreateGroupTransactionData extends TransactionData { this.description = description; this.isOpen = isOpen; this.approvalThreshold = approvalThreshold; + this.minimumBlockDelay = minimumBlockDelay; + this.maximumBlockDelay = maximumBlockDelay; this.groupId = groupId; } @@ -95,6 +101,14 @@ public class CreateGroupTransactionData extends TransactionData { return this.approvalThreshold; } + public int getMinimumBlockDelay() { + return this.minimumBlockDelay; + } + + public int getMaximumBlockDelay() { + return this.maximumBlockDelay; + } + public Integer getGroupId() { return this.groupId; } diff --git a/src/main/java/org/qora/data/transaction/UpdateGroupTransactionData.java b/src/main/java/org/qora/data/transaction/UpdateGroupTransactionData.java index d101702e..3d9c4dbc 100644 --- a/src/main/java/org/qora/data/transaction/UpdateGroupTransactionData.java +++ b/src/main/java/org/qora/data/transaction/UpdateGroupTransactionData.java @@ -51,6 +51,10 @@ public class UpdateGroupTransactionData extends TransactionData { description = "new group member transaction approval threshold" ) 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. */ // For internal use when orphaning @XmlTransient @@ -71,7 +75,7 @@ public class UpdateGroupTransactionData extends TransactionData { } 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); this.ownerPublicKey = ownerPublicKey; @@ -80,13 +84,15 @@ public class UpdateGroupTransactionData extends TransactionData { this.newDescription = newDescription; this.newIsOpen = newIsOpen; this.newApprovalThreshold = newApprovalThreshold; + this.newMinimumBlockDelay = newMinimumBlockDelay; + this.newMaximumBlockDelay = newMaximumBlockDelay; this.groupReference = groupReference; } /** Constructor typically used after deserialization */ public UpdateGroupTransactionData(long timestamp, int txGroupId, byte[] reference, byte[] ownerPublicKey, int groupId, - String newOwner, String newDescription, boolean newIsOpen, ApprovalThreshold newApprovalThreshold, BigDecimal fee, byte[] signature) { - this(timestamp, txGroupId, reference, ownerPublicKey, groupId, newOwner, newDescription, newIsOpen, newApprovalThreshold, null, fee, 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, newMinimumBlockDelay, newMaximumBlockDelay, null, fee, signature); } // Getters / setters @@ -115,6 +121,14 @@ public class UpdateGroupTransactionData extends TransactionData { return this.newApprovalThreshold; } + public int getNewMinimumBlockDelay() { + return this.newMinimumBlockDelay; + } + + public int getNewMaximumBlockDelay() { + return this.newMaximumBlockDelay; + } + public byte[] getGroupReference() { return this.groupReference; } diff --git a/src/main/java/org/qora/group/Group.java b/src/main/java/org/qora/group/Group.java index 869b9daa..7a509726 100644 --- a/src/main/java/org/qora/group/Group.java +++ b/src/main/java/org/qora/group/Group.java @@ -46,7 +46,8 @@ public class Group { public final int value; public final boolean isPercentage; - private final static Map map = stream(ApprovalThreshold.values()).collect(toMap(threshold -> threshold.value, threshold -> threshold)); + private final static Map map = stream(ApprovalThreshold.values()) + .collect(toMap(threshold -> threshold.value, threshold -> threshold)); ApprovalThreshold(int value, boolean isPercentage) { this.value = value; @@ -65,23 +66,25 @@ public class Group { } /** - * Returns whether transaction need approval. + * Returns whether transaction meets approval threshold. * * @param repository - * @param txGroupId transaction's groupID - * @param signature transaction's signature + * @param txGroupId + * transaction's groupID + * @param signature + * transaction's signature * @return true if approval still needed, false if transaction can be included in block * @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 final int totalAdmins = repository.getGroupRepository().countGroupAdmins(txGroupId); - + // Fetch total number of approvals for signature // NOT simply number of GROUP_APPROVE transactions as some may be rejecting transaction, or changed opinions 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; // Useful constants - public static final int NO_GROUP = -1; - public static final int DEFAULT_GROUP = 0; + public static final int NO_GROUP = 0; public static final int MAX_NAME_SIZE = 32; public static final int MAX_DESCRIPTION_SIZE = 128; @@ -113,7 +115,8 @@ public class Group { this.groupData = new GroupData(createGroupTransactionData.getOwner(), createGroupTransactionData.getGroupName(), createGroupTransactionData.getDescription(), createGroupTransactionData.getTimestamp(), createGroupTransactionData.getIsOpen(), - createGroupTransactionData.getApprovalThreshold(), createGroupTransactionData.getSignature()); + createGroupTransactionData.getApprovalThreshold(), createGroupTransactionData.getMinimumBlockDelay(), + createGroupTransactionData.getMaximumBlockDelay(), createGroupTransactionData.getSignature()); } /** diff --git a/src/main/java/org/qora/repository/BlockRepository.java b/src/main/java/org/qora/repository/BlockRepository.java index 65b85512..be1e683f 100644 --- a/src/main/java/org/qora/repository/BlockRepository.java +++ b/src/main/java/org/qora/repository/BlockRepository.java @@ -44,6 +44,15 @@ public interface BlockRepository { */ 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. * diff --git a/src/main/java/org/qora/repository/TransactionRepository.java b/src/main/java/org/qora/repository/TransactionRepository.java index 74dbc383..7a5794ee 100644 --- a/src/main/java/org/qora/repository/TransactionRepository.java +++ b/src/main/java/org/qora/repository/TransactionRepository.java @@ -47,6 +47,20 @@ public interface TransactionRepository { public List getAssetTransactions(int assetId, ConfirmationStatus confirmationStatus, Integer limit, Integer offset, Boolean reverse) throws DataException; + /** + * Returns list of transactions pending approval, with optional txGgroupId filtering. + *

+ * 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 getPendingTransactions(Integer txGroupId, Integer limit, Integer offset, Boolean reverse) throws DataException; + /** Returns number of approvals for transaction with given signature. */ public int countTransactionApprovals(int txGroupId, byte[] signature) throws DataException; diff --git a/src/main/java/org/qora/repository/hsqldb/HSQLDBBlockRepository.java b/src/main/java/org/qora/repository/hsqldb/HSQLDBBlockRepository.java index 392731fa..4da188d9 100644 --- a/src/main/java/org/qora/repository/hsqldb/HSQLDBBlockRepository.java +++ b/src/main/java/org/qora/repository/hsqldb/HSQLDBBlockRepository.java @@ -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 public int getBlockchainHeight() throws DataException { try (ResultSet resultSet = this.repository.checkedExecute("SELECT MAX(height) FROM Blocks LIMIT 1")) { diff --git a/src/main/java/org/qora/repository/hsqldb/HSQLDBDatabaseUpdates.java b/src/main/java/org/qora/repository/hsqldb/HSQLDBDatabaseUpdates.java index 8bd51b07..50bf8813 100644 --- a/src/main/java/org/qora/repository/hsqldb/HSQLDBDatabaseUpdates.java +++ b/src/main/java/org/qora/repository/hsqldb/HSQLDBDatabaseUpdates.java @@ -558,6 +558,16 @@ public class HSQLDBDatabaseUpdates { + "PRIMARY KEY (signature), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)"); 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: // nothing to do return false; diff --git a/src/main/java/org/qora/repository/hsqldb/HSQLDBGroupRepository.java b/src/main/java/org/qora/repository/hsqldb/HSQLDBGroupRepository.java index 39ff765b..792c71aa 100644 --- a/src/main/java/org/qora/repository/hsqldb/HSQLDBGroupRepository.java +++ b/src/main/java/org/qora/repository/hsqldb/HSQLDBGroupRepository.java @@ -30,7 +30,7 @@ public class HSQLDBGroupRepository implements GroupRepository { @Override public GroupData fromGroupId(int groupId) throws DataException { 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) return null; @@ -48,7 +48,10 @@ public class HSQLDBGroupRepository implements GroupRepository { 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) { throw new DataException("Unable to fetch group info from repository", e); } @@ -57,7 +60,7 @@ public class HSQLDBGroupRepository implements GroupRepository { @Override public GroupData fromGroupName(String groupName) throws DataException { 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) return null; @@ -75,7 +78,10 @@ public class HSQLDBGroupRepository implements GroupRepository { 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) { throw new DataException("Unable to fetch group info from repository", e); } @@ -101,7 +107,7 @@ public class HSQLDBGroupRepository implements GroupRepository { @Override public List 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) sql += " DESC"; sql += HSQLDBRepository.limitOffsetSql(limit, offset); @@ -128,7 +134,10 @@ public class HSQLDBGroupRepository implements GroupRepository { 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()); return groups; @@ -139,7 +148,7 @@ public class HSQLDBGroupRepository implements GroupRepository { @Override public List 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) sql += " DESC"; sql += HSQLDBRepository.limitOffsetSql(limit, offset); @@ -165,7 +174,10 @@ public class HSQLDBGroupRepository implements GroupRepository { 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()); return groups; @@ -176,7 +188,8 @@ public class HSQLDBGroupRepository implements GroupRepository { @Override public List 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) sql += " DESC"; sql += HSQLDBRepository.limitOffsetSql(limit, offset); @@ -203,7 +216,10 @@ public class HSQLDBGroupRepository implements GroupRepository { 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()); 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()) .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 { saveHelper.execute(this.repository); diff --git a/src/main/java/org/qora/repository/hsqldb/transaction/HSQLDBCreateGroupTransactionRepository.java b/src/main/java/org/qora/repository/hsqldb/transaction/HSQLDBCreateGroupTransactionRepository.java index 098d4dd1..003dcdd7 100644 --- a/src/main/java/org/qora/repository/hsqldb/transaction/HSQLDBCreateGroupTransactionRepository.java +++ b/src/main/java/org/qora/repository/hsqldb/transaction/HSQLDBCreateGroupTransactionRepository.java @@ -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 { - try (ResultSet resultSet = this.repository - .checkedExecute("SELECT owner, group_name, description, is_open, approval_threshold, group_id FROM CreateGroupTransactions WHERE signature = ?", signature)) { + try (ResultSet resultSet = this.repository.checkedExecute( + "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) return null; @@ -30,12 +31,15 @@ public class HSQLDBCreateGroupTransactionRepository extends HSQLDBTransactionRep 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()) groupId = null; return new CreateGroupTransactionData(timestamp, txGroupId, reference, creatorPublicKey, owner, groupName, description, isOpen, approvalThreshold, - groupId, fee, signature); + minBlockDelay, maxBlockDelay, groupId, fee, signature); } catch (SQLException 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("description", createGroupTransactionData.getDescription()).bind("is_open", createGroupTransactionData.getIsOpen()) .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 { saveHelper.execute(this.repository); diff --git a/src/main/java/org/qora/repository/hsqldb/transaction/HSQLDBTransactionRepository.java b/src/main/java/org/qora/repository/hsqldb/transaction/HSQLDBTransactionRepository.java index 84a1af77..7b59ec46 100644 --- a/src/main/java/org/qora/repository/hsqldb/transaction/HSQLDBTransactionRepository.java +++ b/src/main/java/org/qora/repository/hsqldb/transaction/HSQLDBTransactionRepository.java @@ -9,6 +9,7 @@ import java.sql.SQLException; import java.sql.Timestamp; import java.util.ArrayList; import java.util.Arrays; +import static java.util.Arrays.stream; import java.util.Calendar; import java.util.List; 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.transaction.GroupApprovalTransactionData; import org.qora.data.transaction.TransactionData; +import org.qora.group.Group; import org.qora.repository.DataException; import org.qora.repository.TransactionRepository; import org.qora.repository.hsqldb.HSQLDBRepository; import org.qora.repository.hsqldb.HSQLDBSaver; +import org.qora.transaction.Transaction; import 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); - LOGGER.trace(sql); + LOGGER.trace(String.format("Transaction search SQL: %s", sql)); try (ResultSet resultSet = this.repository.checkedExecute(sql, bindParams.toArray())) { if (resultSet == null) @@ -488,6 +491,59 @@ public class HSQLDBTransactionRepository implements TransactionRepository { } } + @Override + public List 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 transactions = new ArrayList(); + + // 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 public int countTransactionApprovals(int txGroupId, byte[] signature) throws DataException { // Fetch total number of approvals for signature diff --git a/src/main/java/org/qora/repository/hsqldb/transaction/HSQLDBUpdateGroupTransactionRepository.java b/src/main/java/org/qora/repository/hsqldb/transaction/HSQLDBUpdateGroupTransactionRepository.java index 47e25373..ff479857 100644 --- a/src/main/java/org/qora/repository/hsqldb/transaction/HSQLDBUpdateGroupTransactionRepository.java +++ b/src/main/java/org/qora/repository/hsqldb/transaction/HSQLDBUpdateGroupTransactionRepository.java @@ -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 { 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)) { if (resultSet == null) return null; @@ -30,9 +30,11 @@ public class HSQLDBUpdateGroupTransactionRepository extends HSQLDBTransactionRep boolean newIsOpen = resultSet.getBoolean(4); ApprovalThreshold newApprovalThreshold = ApprovalThreshold.valueOf(resultSet.getInt(5)); 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, - newApprovalThreshold, groupReference, fee, signature); + newApprovalThreshold, newMinBlockDelay, newMaxBlockDelay, groupReference, fee, signature); } catch (SQLException 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("new_description", updateGroupTransactionData.getNewDescription()).bind("new_is_open", updateGroupTransactionData.getNewIsOpen()) .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()); try { diff --git a/src/main/java/org/qora/settings/Settings.java b/src/main/java/org/qora/settings/Settings.java index 54a92fd8..3e2919eb 100644 --- a/src/main/java/org/qora/settings/Settings.java +++ b/src/main/java/org/qora/settings/Settings.java @@ -27,6 +27,7 @@ public class Settings { private String userpath = ""; private boolean useBitcoinTestNet = false; private boolean wipeUnconfirmedOnStart = false; + private Boolean restrictedApi; private String blockchainConfigPath = "blockchain.json"; /** Maximum number of unconfirmed transactions allowed per account */ private int maxUnconfirmedPerAccount = 100; @@ -143,6 +144,9 @@ public class Settings { if (json.containsKey("apiEnabled")) this.apiEnabled = ((Boolean) json.get("apiEnabled")).booleanValue(); + if (json.containsKey("restrictedApi")) + this.restrictedApi = ((Boolean) json.get("restrictedApi")).booleanValue(); + // Peer-to-peer networking if (json.containsKey("listenPort")) @@ -208,6 +212,14 @@ public class Settings { 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() { return this.listenPort; } diff --git a/src/main/java/org/qora/transaction/GroupApprovalTransaction.java b/src/main/java/org/qora/transaction/GroupApprovalTransaction.java index 03677b1d..00d67287 100644 --- a/src/main/java/org/qora/transaction/GroupApprovalTransaction.java +++ b/src/main/java/org/qora/transaction/GroupApprovalTransaction.java @@ -69,15 +69,14 @@ public class GroupApprovalTransaction extends Transaction { if (pendingTransactionData == null) 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 if (this.repository.getTransactionRepository().getHeightFromSignature(groupApprovalTransactionData.getPendingSignature()) != 0) 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(); // Can't cast approval decision if not an admin diff --git a/src/main/java/org/qora/transaction/GroupInviteTransaction.java b/src/main/java/org/qora/transaction/GroupInviteTransaction.java index 6278ecb9..b2a3414b 100644 --- a/src/main/java/org/qora/transaction/GroupInviteTransaction.java +++ b/src/main/java/org/qora/transaction/GroupInviteTransaction.java @@ -10,7 +10,6 @@ import org.qora.account.PublicKeyAccount; import org.qora.asset.Asset; import org.qora.crypto.Crypto; import org.qora.data.transaction.GroupInviteTransactionData; -import org.qora.data.group.GroupData; import org.qora.data.transaction.TransactionData; import org.qora.group.Group; import org.qora.repository.DataException; @@ -74,6 +73,12 @@ public class GroupInviteTransaction extends Transaction { @Override 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 if (groupInviteTransactionData.getTimeToLive() < 0) return ValidationResult.INVALID_LIFETIME; @@ -82,31 +87,24 @@ public class GroupInviteTransaction extends Transaction { if (!Crypto.isValidAddress(groupInviteTransactionData.getInvitee())) return ValidationResult.INVALID_ADDRESS; - GroupData groupData = this.repository.getGroupRepository().fromGroupId(groupInviteTransactionData.getGroupId()); - // Check group exists - if (groupData == null) + if (!this.repository.getGroupRepository().groupExists(groupId)) 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(); // 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; Account invitee = getInvitee(); // 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; // 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; // Check fee is positive diff --git a/src/main/java/org/qora/transaction/JoinGroupTransaction.java b/src/main/java/org/qora/transaction/JoinGroupTransaction.java index 1368d4e0..a3cd1540 100644 --- a/src/main/java/org/qora/transaction/JoinGroupTransaction.java +++ b/src/main/java/org/qora/transaction/JoinGroupTransaction.java @@ -9,7 +9,6 @@ import org.qora.account.Account; import org.qora.account.PublicKeyAccount; import org.qora.asset.Asset; import org.qora.data.transaction.JoinGroupTransactionData; -import org.qora.data.group.GroupData; import org.qora.data.transaction.TransactionData; import org.qora.group.Group; import org.qora.repository.DataException; @@ -66,23 +65,28 @@ public class JoinGroupTransaction extends Transaction { @Override 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 - if (groupData == null) + if (!this.repository.getGroupRepository().groupExists(groupId)) return ValidationResult.GROUP_DOES_NOT_EXIST; 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; // 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; // 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; // Check fee is positive diff --git a/src/main/java/org/qora/transaction/SetGroupTransaction.java b/src/main/java/org/qora/transaction/SetGroupTransaction.java index 4f3402af..d9412134 100644 --- a/src/main/java/org/qora/transaction/SetGroupTransaction.java +++ b/src/main/java/org/qora/transaction/SetGroupTransaction.java @@ -8,7 +8,6 @@ import java.util.List; import org.qora.account.Account; import org.qora.asset.Asset; import org.qora.data.transaction.SetGroupTransactionData; -import org.qora.data.group.GroupData; import org.qora.data.transaction.TransactionData; import org.qora.group.Group; import org.qora.repository.DataException; @@ -61,10 +60,8 @@ public class SetGroupTransaction extends Transaction { @Override public ValidationResult isValid() throws DataException { - GroupData groupData = this.repository.getGroupRepository().fromGroupId(setGroupTransactionData.getDefaultGroupId()); - // Check group exists - if (groupData == null) + if (!this.repository.getGroupRepository().groupExists(setGroupTransactionData.getDefaultGroupId())) return ValidationResult.GROUP_DOES_NOT_EXIST; Account creator = getCreator(); @@ -94,7 +91,7 @@ public class SetGroupTransaction extends Transaction { Integer previousDefaultGroupId = this.repository.getAccountRepository().getDefaultGroupId(creator.getAddress()); if (previousDefaultGroupId == null) - previousDefaultGroupId = Group.DEFAULT_GROUP; + previousDefaultGroupId = Group.NO_GROUP; setGroupTransactionData.setPreviousDefaultGroupId(previousDefaultGroupId); @@ -118,7 +115,7 @@ public class SetGroupTransaction extends Transaction { Integer previousDefaultGroupId = setGroupTransactionData.getPreviousDefaultGroupId(); if (previousDefaultGroupId == null) - previousDefaultGroupId = Group.DEFAULT_GROUP; + previousDefaultGroupId = Group.NO_GROUP; creator.setDefaultGroupId(previousDefaultGroupId); diff --git a/src/main/java/org/qora/transaction/Transaction.java b/src/main/java/org/qora/transaction/Transaction.java index 6c78185a..667236bb 100644 --- a/src/main/java/org/qora/transaction/Transaction.java +++ b/src/main/java/org/qora/transaction/Transaction.java @@ -16,6 +16,7 @@ import org.qora.account.PrivateKeyAccount; import org.qora.account.PublicKeyAccount; import org.qora.block.BlockChain; import org.qora.data.block.BlockData; +import org.qora.data.group.GroupData; import org.qora.data.transaction.TransactionData; import org.qora.group.Group; import org.qora.repository.DataException; @@ -37,43 +38,43 @@ public abstract class Transaction { // Transaction types public enum TransactionType { // NOTE: must be contiguous or reflection fails - GENESIS(1, true), + GENESIS(1, false), PAYMENT(2, false), - REGISTER_NAME(3, false), - UPDATE_NAME(4, false), + REGISTER_NAME(3, true), + UPDATE_NAME(4, true), SELL_NAME(5, false), CANCEL_SELL_NAME(6, false), BUY_NAME(7, false), - CREATE_POLL(8, false), + CREATE_POLL(8, true), VOTE_ON_POLL(9, false), - ARBITRARY(10, false), - ISSUE_ASSET(11, false), + ARBITRARY(10, true), + ISSUE_ASSET(11, true), TRANSFER_ASSET(12, false), CREATE_ASSET_ORDER(13, false), CANCEL_ASSET_ORDER(14, false), MULTI_PAYMENT(15, false), - DEPLOY_AT(16, false), - MESSAGE(17, false), + DEPLOY_AT(16, true), + MESSAGE(17, true), DELEGATION(18, false), SUPERNODE(19, false), - AIRDROP(20, true), - AT(21, true), - CREATE_GROUP(22, false), + AIRDROP(20, false), + AT(21, false), + CREATE_GROUP(22, true), UPDATE_GROUP(23, true), - ADD_GROUP_ADMIN(24, true), - REMOVE_GROUP_ADMIN(25, true), - GROUP_BAN(26, true), - CANCEL_GROUP_BAN(27, true), - GROUP_KICK(28, true), - GROUP_INVITE(29, true), - CANCEL_GROUP_INVITE(30, true), - JOIN_GROUP(31, true), - LEAVE_GROUP(32, true), - GROUP_APPROVAL(33, true), - SET_GROUP(34, true); + ADD_GROUP_ADMIN(24, false), + REMOVE_GROUP_ADMIN(25, false), + GROUP_BAN(26, false), + CANCEL_GROUP_BAN(27, false), + GROUP_KICK(28, false), + GROUP_INVITE(29, false), + CANCEL_GROUP_INVITE(30, false), + JOIN_GROUP(31, false), + LEAVE_GROUP(32, false), + GROUP_APPROVAL(33, false), + SET_GROUP(34, false); public final int value; - public final boolean skipsApproval; + public final boolean needsApproval; public final String valueString; public final String className; public final Class clazz; @@ -81,9 +82,9 @@ public abstract class Transaction { private final static Map 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.skipsApproval = skipsApproval; + this.needsApproval = needsApproval; this.valueString = String.valueOf(value); String[] classNameParts = this.name().toLowerCase().split("_"); @@ -495,19 +496,20 @@ public abstract class Transaction { } private boolean isValidTxGroupId() throws DataException { - // Does this transaction type bypass approval? - if (this.transactionData.getType().skipsApproval) - return true; + int txGroupId = this.transactionData.getTxGroupId(); - int txGroupId = this.getEffectiveGroupId(); + // Handling 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); - if (group.getGroupData() == null) { - // Group no longer exists? Possibly due to blockchain orphaning undoing group creation? + // Group even exist? + if (!this.repository.getGroupRepository().groupExists(txGroupId)) return false; - } + + // Does this transaction type bypass approval? + if (!this.transactionData.getType().needsApproval) + return true; GroupRepository groupRepository = this.repository.getGroupRepository(); @@ -615,6 +617,17 @@ public abstract class Transaction { if (transaction.getDeadline() <= blockTimestamp || transaction.getDeadline() < NTP.getTime()) 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 if (transaction.isValid() != Transaction.ValidationResult.OK) return false; @@ -628,72 +641,47 @@ public abstract class Transaction { } /** - * Returns transaction's effective groupID, using default values where necessary. - */ - 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. + * Returns whether transaction needs to go through group-admin approval. * * @throws DataException */ public boolean needsGroupApproval() throws DataException { // Does this transaction type bypass approval? - if (this.transactionData.getType().skipsApproval) + if (!this.transactionData.getType().needsApproval) return false; - int txGroupId = this.getEffectiveGroupId(); + int txGroupId = this.transactionData.getTxGroupId(); if (txGroupId == Group.NO_GROUP) return false; - Group group = new Group(repository, txGroupId); - if (group.getGroupData() == null) { + GroupRepository groupRepository = this.repository.getGroupRepository(); + + if (!groupRepository.groupExists(txGroupId)) // Group no longer exists? Possibly due to blockchain orphaning undoing group creation? return true; - } - - GroupRepository groupRepository = this.repository.getGroupRepository(); // If transaction's creator is group admin then auto-approve PublicKeyAccount creator = this.getCreator(); if (groupRepository.adminExists(txGroupId, creator.getAddress())) 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()); } /** diff --git a/src/main/java/org/qora/transform/transaction/CreateGroupTransactionTransformer.java b/src/main/java/org/qora/transform/transaction/CreateGroupTransactionTransformer.java index e3dd313b..8717f421 100644 --- a/src/main/java/org/qora/transform/transaction/CreateGroupTransactionTransformer.java +++ b/src/main/java/org/qora/transform/transaction/CreateGroupTransactionTransformer.java @@ -15,6 +15,7 @@ import org.qora.transform.TransformationException; import org.qora.utils.Serialization; import com.google.common.base.Utf8; +import com.google.common.primitives.Ints; public class CreateGroupTransactionTransformer extends TransactionTransformer { @@ -42,6 +43,8 @@ public class CreateGroupTransactionTransformer extends TransactionTransformer { layout.add("group's description", TransformationType.STRING); layout.add("is group \"open\"?", TransformationType.BOOLEAN); 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("signature", TransformationType.SIGNATURE); } @@ -68,13 +71,17 @@ public class CreateGroupTransactionTransformer extends TransactionTransformer { ApprovalThreshold approvalThreshold = ApprovalThreshold.valueOf(byteBuffer.get()); + int minBlockDelay = byteBuffer.getInt(); + + int maxBlockDelay = byteBuffer.getInt(); + BigDecimal fee = Serialization.deserializeBigDecimal(byteBuffer); byte[] signature = new byte[SIGNATURE_LENGTH]; byteBuffer.get(signature); - return new CreateGroupTransactionData(timestamp, txGroupId, reference, creatorPublicKey, owner, groupName, description, isOpen, approvalThreshold, null, - fee, signature); + return new CreateGroupTransactionData(timestamp, txGroupId, reference, creatorPublicKey, owner, groupName, description, isOpen, approvalThreshold, + minBlockDelay, maxBlockDelay, null, fee, signature); } 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(Ints.toByteArray(createGroupTransactionData.getMinimumBlockDelay())); + + bytes.write(Ints.toByteArray(createGroupTransactionData.getMaximumBlockDelay())); + Serialization.serializeBigDecimal(bytes, createGroupTransactionData.getFee()); if (createGroupTransactionData.getSignature() != null) diff --git a/src/main/java/org/qora/transform/transaction/UpdateGroupTransactionTransformer.java b/src/main/java/org/qora/transform/transaction/UpdateGroupTransactionTransformer.java index ab9490e3..0bb8af74 100644 --- a/src/main/java/org/qora/transform/transaction/UpdateGroupTransactionTransformer.java +++ b/src/main/java/org/qora/transform/transaction/UpdateGroupTransactionTransformer.java @@ -70,13 +70,17 @@ public class UpdateGroupTransactionTransformer extends TransactionTransformer { ApprovalThreshold newApprovalThreshold = ApprovalThreshold.valueOf(byteBuffer.get()); + int newMinBlockDelay = byteBuffer.getInt(); + + int newMaxBlockDelay = byteBuffer.getInt(); + BigDecimal fee = Serialization.deserializeBigDecimal(byteBuffer); byte[] signature = new byte[SIGNATURE_LENGTH]; byteBuffer.get(signature); 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 { @@ -103,6 +107,10 @@ public class UpdateGroupTransactionTransformer extends TransactionTransformer { bytes.write((byte) updateGroupTransactionData.getNewApprovalThreshold().value); + bytes.write(Ints.toByteArray(updateGroupTransactionData.getNewMinimumBlockDelay())); + + bytes.write(Ints.toByteArray(updateGroupTransactionData.getNewMaximumBlockDelay())); + Serialization.serializeBigDecimal(bytes, updateGroupTransactionData.getFee()); if (updateGroupTransactionData.getSignature() != null) diff --git a/src/main/java/org/qora/v1feeder.java b/src/main/java/org/qora/v1feeder.java index 77ca458d..ca4f08d5 100644 --- a/src/main/java/org/qora/v1feeder.java +++ b/src/main/java/org/qora/v1feeder.java @@ -496,13 +496,13 @@ public class v1feeder extends Thread { 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; try { digest = Crypto.digest(AtTransactionTransformer.toBytes(transactionData)); 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) { throw new RuntimeException("Couldn't transform AT Transaction into bytes", e); } diff --git a/src/main/resources/i18n/ApiError_en.properties b/src/main/resources/i18n/ApiError_en.properties index b54bc1bc..91f39c0a 100644 --- a/src/main/resources/i18n/ApiError_en.properties +++ b/src/main/resources/i18n/ApiError_en.properties @@ -7,6 +7,7 @@ NO_BALANCE=not enough balance NOT_YET_RELEASED=that feature is not yet released UNAUTHORIZED=api call unauthorized REPOSITORY_ISSUE=repository error +NON_PRODUCTION=This API call is not permitted for production systems # Validation INVALID_SIGNATURE=invalid signature diff --git a/src/test/java/org/qora/test/ATTests.java b/src/test/java/org/qora/test/ATTests.java index ce9f09ac..fda75011 100644 --- a/src/test/java/org/qora/test/ATTests.java +++ b/src/test/java/org/qora/test/ATTests.java @@ -47,7 +47,7 @@ public class ATTests extends Common { byte[] reference = Base58.decode("2D3jX1pEgu6irsQ7QzJb85QP1D9M45dNyP5M9a3WFHndU5ZywF4F5pnUurcbzMnGMcTwpAY6H7DuLw8cUBU66ao1"); 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); try (final Repository repository = RepositoryManager.getRepository()) { diff --git a/src/test/java/org/qora/test/SaveTests.java b/src/test/java/org/qora/test/SaveTests.java index b166a973..d95b8074 100644 --- a/src/test/java/org/qora/test/SaveTests.java +++ b/src/test/java/org/qora/test/SaveTests.java @@ -23,7 +23,7 @@ public class SaveTests extends Common { byte[] signature = Base58.decode(signature58); 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); repository.getTransactionRepository().save(paymentTransactionData); diff --git a/src/test/java/org/qora/test/TransactionTests.java b/src/test/java/org/qora/test/TransactionTests.java index 211f8a76..c5b32d41 100644 --- a/src/test/java/org/qora/test/TransactionTests.java +++ b/src/test/java/org/qora/test/TransactionTests.java @@ -121,7 +121,7 @@ public class TransactionTests { // Create test generator account 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)); // Create test sender account @@ -129,7 +129,7 @@ public class TransactionTests { // Mock account 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 accountRepository.save(new AccountBalanceData(sender.getAddress(), Asset.QORA, initialSenderBalance)); @@ -147,7 +147,7 @@ public class TransactionTests { BigDecimal amount = genericPaymentAmount; BigDecimal fee = BigDecimal.ONE; 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); paymentTransaction.sign(sender); @@ -164,7 +164,7 @@ public class TransactionTests { BigDecimal amount = BigDecimal.valueOf(1_000L); BigDecimal fee = BigDecimal.ONE; 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); Transaction paymentTransaction = new PaymentTransaction(repository, paymentTransactionData); @@ -225,7 +225,7 @@ public class TransactionTests { BigDecimal fee = BigDecimal.ONE; 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); Transaction registerNameTransaction = new RegisterNameTransaction(repository, registerNameTransactionData); @@ -281,7 +281,7 @@ public class TransactionTests { BigDecimal fee = BigDecimal.ONE; 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); Transaction updateNameTransaction = new UpdateNameTransaction(repository, updateNameTransactionData); @@ -327,7 +327,7 @@ public class TransactionTests { BigDecimal fee = BigDecimal.ONE; 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); sellNameTransaction.sign(sender); @@ -378,7 +378,7 @@ public class TransactionTests { BigDecimal fee = BigDecimal.ONE; 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); cancelSellNameTransaction.sign(sender); @@ -443,7 +443,7 @@ public class TransactionTests { BigDecimal fee = BigDecimal.ONE; 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); Transaction buyNameTransaction = new BuyNameTransaction(repository, buyNameTransactionData); @@ -496,7 +496,7 @@ public class TransactionTests { Account recipient = new PublicKeyAccount(repository, recipientSeed); BigDecimal fee = BigDecimal.ONE; 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); Transaction createPollTransaction = new CreatePollTransaction(repository, createPollTransactionData); @@ -550,7 +550,7 @@ public class TransactionTests { for (int optionIndex = 0; optionIndex <= pollOptionsSize; ++optionIndex) { // 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); Transaction voteOnPollTransaction = new VoteOnPollTransaction(repository, voteOnPollTransactionData); @@ -622,7 +622,7 @@ public class TransactionTests { BigDecimal fee = BigDecimal.ONE; 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); Transaction issueAssetTransaction = new IssueAssetTransaction(repository, issueAssetTransactionData); @@ -712,7 +712,7 @@ public class TransactionTests { BigDecimal fee = BigDecimal.ONE; 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); Transaction transferAssetTransaction = new TransferAssetTransaction(repository, transferAssetTransactionData); @@ -817,7 +817,7 @@ public class TransactionTests { BigDecimal fee = BigDecimal.ONE; 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); Transaction createOrderTransaction = new CreateAssetOrderTransaction(this.repository, createOrderTransactionData); createOrderTransaction.sign(buyer); @@ -898,7 +898,7 @@ public class TransactionTests { BigDecimal fee = BigDecimal.ONE; long timestamp = parentBlockData.getTimestamp() + 1_000; 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); cancelOrderTransaction.sign(buyer); @@ -973,7 +973,7 @@ public class TransactionTests { long timestamp = parentBlockData.getTimestamp() + 1_000; 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); Transaction createOrderTransaction = new CreateAssetOrderTransaction(this.repository, createOrderTransactionData); createOrderTransaction.sign(sender); @@ -1082,7 +1082,7 @@ public class TransactionTests { 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); multiPaymentTransaction.sign(sender); @@ -1151,7 +1151,7 @@ public class TransactionTests { boolean isText = true; 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); Transaction messageTransaction = new MessageTransaction(repository, messageTransactionData);