mirror of
https://github.com/Qortal/qortal.git
synced 2025-03-26 23:44:34 +00:00
Proper JSON unmarshalling for settings, blockchain config, genesis block
GenesisBlock (v4) now supports various transaction types (issue-asset, etc.) with generated signatures (like genesis transaction signature) and missing references inserted. JUnit reverted back to v4 for Eclipse support (for now).
This commit is contained in:
parent
86a35c3b71
commit
16c1b13ab2
@ -29,9 +29,9 @@
|
|||||||
</classpathentry>
|
</classpathentry>
|
||||||
<classpathentry kind="src" path="target/generated-sources/annotations">
|
<classpathentry kind="src" path="target/generated-sources/annotations">
|
||||||
<attributes>
|
<attributes>
|
||||||
|
<attribute name="ignore_optional_problems" value="true"/>
|
||||||
<attribute name="optional" value="true"/>
|
<attribute name="optional" value="true"/>
|
||||||
<attribute name="maven.pomderived" value="true"/>
|
<attribute name="maven.pomderived" value="true"/>
|
||||||
<attribute name="ignore_optional_problems" value="true"/>
|
|
||||||
<attribute name="m2e-apt" value="true"/>
|
<attribute name="m2e-apt" value="true"/>
|
||||||
</attributes>
|
</attributes>
|
||||||
</classpathentry>
|
</classpathentry>
|
||||||
@ -41,6 +41,7 @@
|
|||||||
<attribute name="maven.pomderived" value="true"/>
|
<attribute name="maven.pomderived" value="true"/>
|
||||||
</attributes>
|
</attributes>
|
||||||
</classpathentry>
|
</classpathentry>
|
||||||
|
<classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/4"/>
|
||||||
<classpathentry kind="src" output="target/test-classes" path="target/generated-test-sources/test-annotations">
|
<classpathentry kind="src" output="target/test-classes" path="target/generated-test-sources/test-annotations">
|
||||||
<attributes>
|
<attributes>
|
||||||
<attribute name="optional" value="true"/>
|
<attribute name="optional" value="true"/>
|
||||||
|
2
pom.xml
2
pom.xml
@ -425,6 +425,7 @@
|
|||||||
<version>${swagger-ui.version}</version>
|
<version>${swagger-ui.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<!-- Testing -->
|
<!-- Testing -->
|
||||||
|
<!-- disabled pending better Eclipse support
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.junit.jupiter</groupId>
|
<groupId>org.junit.jupiter</groupId>
|
||||||
<artifactId>junit-jupiter-engine</artifactId>
|
<artifactId>junit-jupiter-engine</artifactId>
|
||||||
@ -435,6 +436,7 @@
|
|||||||
<artifactId>hamcrest-library</artifactId>
|
<artifactId>hamcrest-library</artifactId>
|
||||||
<version>1.3</version>
|
<version>1.3</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
-->
|
||||||
<!-- BouncyCastle for crypto, including TLS secure networking -->
|
<!-- BouncyCastle for crypto, including TLS secure networking -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.bouncycastle</groupId>
|
<groupId>org.bouncycastle</groupId>
|
||||||
|
@ -35,7 +35,7 @@ public class ApiService {
|
|||||||
|
|
||||||
// IP address based access control
|
// IP address based access control
|
||||||
InetAccessHandler accessHandler = new InetAccessHandler();
|
InetAccessHandler accessHandler = new InetAccessHandler();
|
||||||
for (String pattern : Settings.getInstance().getApiAllowed()) {
|
for (String pattern : Settings.getInstance().getApiWhitelist()) {
|
||||||
accessHandler.include(pattern);
|
accessHandler.include(pattern);
|
||||||
}
|
}
|
||||||
this.server.setHandler(accessHandler);
|
this.server.setHandler(accessHandler);
|
||||||
|
@ -64,8 +64,8 @@ public class AddressesResource {
|
|||||||
if (accountData == null)
|
if (accountData == null)
|
||||||
accountData = new AccountData(address, null, null, BlockChain.getInstance().getDefaultGroupId());
|
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 Blockchain config doesn't allow NO_GROUP for approval-needing tx type then change this to blockchain's default groupID
|
||||||
if (accountData.getDefaultGroupId() == Group.NO_GROUP && !BlockChain.getInstance().getGrouplessAllowed())
|
if (accountData.getDefaultGroupId() == Group.NO_GROUP && BlockChain.getInstance().getRequireGroupForApproval())
|
||||||
accountData.setDefaultGroupId(BlockChain.getInstance().getDefaultGroupId());
|
accountData.setDefaultGroupId(BlockChain.getInstance().getDefaultGroupId());
|
||||||
|
|
||||||
return accountData;
|
return accountData;
|
||||||
@ -242,7 +242,7 @@ public class AddressesResource {
|
|||||||
)
|
)
|
||||||
@ApiErrors({ApiError.INVALID_PUBLIC_KEY, ApiError.NON_PRODUCTION, ApiError.REPOSITORY_ISSUE})
|
@ApiErrors({ApiError.INVALID_PUBLIC_KEY, ApiError.NON_PRODUCTION, ApiError.REPOSITORY_ISSUE})
|
||||||
public String fromPublicKey(@PathParam("publickey") String publicKey58) {
|
public String fromPublicKey(@PathParam("publickey") String publicKey58) {
|
||||||
if (Settings.getInstance().isRestrictedApi())
|
if (Settings.getInstance().isApiRestricted())
|
||||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.NON_PRODUCTION);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.NON_PRODUCTION);
|
||||||
|
|
||||||
// Decode public key
|
// Decode public key
|
||||||
|
@ -558,7 +558,7 @@ public class AssetsResource {
|
|||||||
ApiError.NON_PRODUCTION, ApiError.TRANSFORMATION_ERROR, ApiError.REPOSITORY_ISSUE, ApiError.TRANSACTION_INVALID
|
ApiError.NON_PRODUCTION, ApiError.TRANSFORMATION_ERROR, ApiError.REPOSITORY_ISSUE, ApiError.TRANSACTION_INVALID
|
||||||
})
|
})
|
||||||
public String cancelOrder(CancelAssetOrderTransactionData transactionData) {
|
public String cancelOrder(CancelAssetOrderTransactionData transactionData) {
|
||||||
if (Settings.getInstance().isRestrictedApi())
|
if (Settings.getInstance().isApiRestricted())
|
||||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.NON_PRODUCTION);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.NON_PRODUCTION);
|
||||||
|
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
@ -606,7 +606,7 @@ public class AssetsResource {
|
|||||||
ApiError.NON_PRODUCTION, ApiError.TRANSFORMATION_ERROR, ApiError.REPOSITORY_ISSUE, ApiError.TRANSACTION_INVALID
|
ApiError.NON_PRODUCTION, ApiError.TRANSFORMATION_ERROR, ApiError.REPOSITORY_ISSUE, ApiError.TRANSACTION_INVALID
|
||||||
})
|
})
|
||||||
public String issueAsset(IssueAssetTransactionData transactionData) {
|
public String issueAsset(IssueAssetTransactionData transactionData) {
|
||||||
if (Settings.getInstance().isRestrictedApi())
|
if (Settings.getInstance().isApiRestricted())
|
||||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.NON_PRODUCTION);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.NON_PRODUCTION);
|
||||||
|
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
@ -654,7 +654,7 @@ public class AssetsResource {
|
|||||||
ApiError.NON_PRODUCTION, ApiError.TRANSFORMATION_ERROR, ApiError.REPOSITORY_ISSUE, ApiError.TRANSACTION_INVALID
|
ApiError.NON_PRODUCTION, ApiError.TRANSFORMATION_ERROR, ApiError.REPOSITORY_ISSUE, ApiError.TRANSACTION_INVALID
|
||||||
})
|
})
|
||||||
public String createOrder(CreateAssetOrderTransactionData transactionData) {
|
public String createOrder(CreateAssetOrderTransactionData transactionData) {
|
||||||
if (Settings.getInstance().isRestrictedApi())
|
if (Settings.getInstance().isApiRestricted())
|
||||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.NON_PRODUCTION);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.NON_PRODUCTION);
|
||||||
|
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
|
@ -267,7 +267,7 @@ public class GroupsResource {
|
|||||||
)
|
)
|
||||||
@ApiErrors({ApiError.NON_PRODUCTION, ApiError.TRANSACTION_INVALID, ApiError.TRANSFORMATION_ERROR, ApiError.REPOSITORY_ISSUE})
|
@ApiErrors({ApiError.NON_PRODUCTION, ApiError.TRANSACTION_INVALID, ApiError.TRANSFORMATION_ERROR, ApiError.REPOSITORY_ISSUE})
|
||||||
public String createGroup(CreateGroupTransactionData transactionData) {
|
public String createGroup(CreateGroupTransactionData transactionData) {
|
||||||
if (Settings.getInstance().isRestrictedApi())
|
if (Settings.getInstance().isApiRestricted())
|
||||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.NON_PRODUCTION);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.NON_PRODUCTION);
|
||||||
|
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
@ -313,7 +313,7 @@ public class GroupsResource {
|
|||||||
)
|
)
|
||||||
@ApiErrors({ApiError.NON_PRODUCTION, ApiError.TRANSACTION_INVALID, ApiError.TRANSFORMATION_ERROR, ApiError.REPOSITORY_ISSUE})
|
@ApiErrors({ApiError.NON_PRODUCTION, ApiError.TRANSACTION_INVALID, ApiError.TRANSFORMATION_ERROR, ApiError.REPOSITORY_ISSUE})
|
||||||
public String updateGroup(UpdateGroupTransactionData transactionData) {
|
public String updateGroup(UpdateGroupTransactionData transactionData) {
|
||||||
if (Settings.getInstance().isRestrictedApi())
|
if (Settings.getInstance().isApiRestricted())
|
||||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.NON_PRODUCTION);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.NON_PRODUCTION);
|
||||||
|
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
@ -359,7 +359,7 @@ public class GroupsResource {
|
|||||||
)
|
)
|
||||||
@ApiErrors({ApiError.NON_PRODUCTION, ApiError.TRANSACTION_INVALID, ApiError.TRANSFORMATION_ERROR, ApiError.REPOSITORY_ISSUE})
|
@ApiErrors({ApiError.NON_PRODUCTION, ApiError.TRANSACTION_INVALID, ApiError.TRANSFORMATION_ERROR, ApiError.REPOSITORY_ISSUE})
|
||||||
public String addGroupAdmin(AddGroupAdminTransactionData transactionData) {
|
public String addGroupAdmin(AddGroupAdminTransactionData transactionData) {
|
||||||
if (Settings.getInstance().isRestrictedApi())
|
if (Settings.getInstance().isApiRestricted())
|
||||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.NON_PRODUCTION);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.NON_PRODUCTION);
|
||||||
|
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
@ -405,7 +405,7 @@ public class GroupsResource {
|
|||||||
)
|
)
|
||||||
@ApiErrors({ApiError.NON_PRODUCTION, ApiError.TRANSACTION_INVALID, ApiError.TRANSFORMATION_ERROR, ApiError.REPOSITORY_ISSUE})
|
@ApiErrors({ApiError.NON_PRODUCTION, ApiError.TRANSACTION_INVALID, ApiError.TRANSFORMATION_ERROR, ApiError.REPOSITORY_ISSUE})
|
||||||
public String removeGroupAdmin(RemoveGroupAdminTransactionData transactionData) {
|
public String removeGroupAdmin(RemoveGroupAdminTransactionData transactionData) {
|
||||||
if (Settings.getInstance().isRestrictedApi())
|
if (Settings.getInstance().isApiRestricted())
|
||||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.NON_PRODUCTION);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.NON_PRODUCTION);
|
||||||
|
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
@ -451,7 +451,7 @@ public class GroupsResource {
|
|||||||
)
|
)
|
||||||
@ApiErrors({ApiError.NON_PRODUCTION, ApiError.TRANSACTION_INVALID, ApiError.TRANSFORMATION_ERROR, ApiError.REPOSITORY_ISSUE})
|
@ApiErrors({ApiError.NON_PRODUCTION, ApiError.TRANSACTION_INVALID, ApiError.TRANSFORMATION_ERROR, ApiError.REPOSITORY_ISSUE})
|
||||||
public String groupBan(GroupBanTransactionData transactionData) {
|
public String groupBan(GroupBanTransactionData transactionData) {
|
||||||
if (Settings.getInstance().isRestrictedApi())
|
if (Settings.getInstance().isApiRestricted())
|
||||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.NON_PRODUCTION);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.NON_PRODUCTION);
|
||||||
|
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
@ -497,7 +497,7 @@ public class GroupsResource {
|
|||||||
)
|
)
|
||||||
@ApiErrors({ApiError.NON_PRODUCTION, ApiError.TRANSACTION_INVALID, ApiError.TRANSFORMATION_ERROR, ApiError.REPOSITORY_ISSUE})
|
@ApiErrors({ApiError.NON_PRODUCTION, ApiError.TRANSACTION_INVALID, ApiError.TRANSFORMATION_ERROR, ApiError.REPOSITORY_ISSUE})
|
||||||
public String cancelGroupBan(CancelGroupBanTransactionData transactionData) {
|
public String cancelGroupBan(CancelGroupBanTransactionData transactionData) {
|
||||||
if (Settings.getInstance().isRestrictedApi())
|
if (Settings.getInstance().isApiRestricted())
|
||||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.NON_PRODUCTION);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.NON_PRODUCTION);
|
||||||
|
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
@ -543,7 +543,7 @@ public class GroupsResource {
|
|||||||
)
|
)
|
||||||
@ApiErrors({ApiError.NON_PRODUCTION, ApiError.TRANSACTION_INVALID, ApiError.TRANSFORMATION_ERROR, ApiError.REPOSITORY_ISSUE})
|
@ApiErrors({ApiError.NON_PRODUCTION, ApiError.TRANSACTION_INVALID, ApiError.TRANSFORMATION_ERROR, ApiError.REPOSITORY_ISSUE})
|
||||||
public String groupKick(GroupKickTransactionData transactionData) {
|
public String groupKick(GroupKickTransactionData transactionData) {
|
||||||
if (Settings.getInstance().isRestrictedApi())
|
if (Settings.getInstance().isApiRestricted())
|
||||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.NON_PRODUCTION);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.NON_PRODUCTION);
|
||||||
|
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
@ -589,7 +589,7 @@ public class GroupsResource {
|
|||||||
)
|
)
|
||||||
@ApiErrors({ApiError.NON_PRODUCTION, ApiError.TRANSACTION_INVALID, ApiError.TRANSFORMATION_ERROR, ApiError.REPOSITORY_ISSUE})
|
@ApiErrors({ApiError.NON_PRODUCTION, ApiError.TRANSACTION_INVALID, ApiError.TRANSFORMATION_ERROR, ApiError.REPOSITORY_ISSUE})
|
||||||
public String groupInvite(GroupInviteTransactionData transactionData) {
|
public String groupInvite(GroupInviteTransactionData transactionData) {
|
||||||
if (Settings.getInstance().isRestrictedApi())
|
if (Settings.getInstance().isApiRestricted())
|
||||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.NON_PRODUCTION);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.NON_PRODUCTION);
|
||||||
|
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
@ -635,7 +635,7 @@ public class GroupsResource {
|
|||||||
)
|
)
|
||||||
@ApiErrors({ApiError.NON_PRODUCTION, ApiError.TRANSACTION_INVALID, ApiError.TRANSFORMATION_ERROR, ApiError.REPOSITORY_ISSUE})
|
@ApiErrors({ApiError.NON_PRODUCTION, ApiError.TRANSACTION_INVALID, ApiError.TRANSFORMATION_ERROR, ApiError.REPOSITORY_ISSUE})
|
||||||
public String cancelGroupInvite(CancelGroupInviteTransactionData transactionData) {
|
public String cancelGroupInvite(CancelGroupInviteTransactionData transactionData) {
|
||||||
if (Settings.getInstance().isRestrictedApi())
|
if (Settings.getInstance().isApiRestricted())
|
||||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.NON_PRODUCTION);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.NON_PRODUCTION);
|
||||||
|
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
@ -681,7 +681,7 @@ public class GroupsResource {
|
|||||||
)
|
)
|
||||||
@ApiErrors({ApiError.NON_PRODUCTION, ApiError.TRANSACTION_INVALID, ApiError.TRANSFORMATION_ERROR, ApiError.REPOSITORY_ISSUE})
|
@ApiErrors({ApiError.NON_PRODUCTION, ApiError.TRANSACTION_INVALID, ApiError.TRANSFORMATION_ERROR, ApiError.REPOSITORY_ISSUE})
|
||||||
public String joinGroup(JoinGroupTransactionData transactionData) {
|
public String joinGroup(JoinGroupTransactionData transactionData) {
|
||||||
if (Settings.getInstance().isRestrictedApi())
|
if (Settings.getInstance().isApiRestricted())
|
||||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.NON_PRODUCTION);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.NON_PRODUCTION);
|
||||||
|
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
@ -727,7 +727,7 @@ public class GroupsResource {
|
|||||||
)
|
)
|
||||||
@ApiErrors({ApiError.NON_PRODUCTION, ApiError.TRANSACTION_INVALID, ApiError.TRANSFORMATION_ERROR, ApiError.REPOSITORY_ISSUE})
|
@ApiErrors({ApiError.NON_PRODUCTION, ApiError.TRANSACTION_INVALID, ApiError.TRANSFORMATION_ERROR, ApiError.REPOSITORY_ISSUE})
|
||||||
public String leaveGroup(LeaveGroupTransactionData transactionData) {
|
public String leaveGroup(LeaveGroupTransactionData transactionData) {
|
||||||
if (Settings.getInstance().isRestrictedApi())
|
if (Settings.getInstance().isApiRestricted())
|
||||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.NON_PRODUCTION);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.NON_PRODUCTION);
|
||||||
|
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
@ -865,7 +865,7 @@ public class GroupsResource {
|
|||||||
)
|
)
|
||||||
@ApiErrors({ApiError.NON_PRODUCTION, ApiError.TRANSACTION_INVALID, ApiError.TRANSFORMATION_ERROR, ApiError.REPOSITORY_ISSUE})
|
@ApiErrors({ApiError.NON_PRODUCTION, ApiError.TRANSACTION_INVALID, ApiError.TRANSFORMATION_ERROR, ApiError.REPOSITORY_ISSUE})
|
||||||
public String groupApproval(GroupApprovalTransactionData transactionData) {
|
public String groupApproval(GroupApprovalTransactionData transactionData) {
|
||||||
if (Settings.getInstance().isRestrictedApi())
|
if (Settings.getInstance().isApiRestricted())
|
||||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.NON_PRODUCTION);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.NON_PRODUCTION);
|
||||||
|
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
@ -911,7 +911,7 @@ public class GroupsResource {
|
|||||||
)
|
)
|
||||||
@ApiErrors({ApiError.NON_PRODUCTION, ApiError.TRANSACTION_INVALID, ApiError.TRANSFORMATION_ERROR, ApiError.REPOSITORY_ISSUE})
|
@ApiErrors({ApiError.NON_PRODUCTION, ApiError.TRANSACTION_INVALID, ApiError.TRANSFORMATION_ERROR, ApiError.REPOSITORY_ISSUE})
|
||||||
public String setGroup(SetGroupTransactionData transactionData) {
|
public String setGroup(SetGroupTransactionData transactionData) {
|
||||||
if (Settings.getInstance().isRestrictedApi())
|
if (Settings.getInstance().isApiRestricted())
|
||||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.NON_PRODUCTION);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.NON_PRODUCTION);
|
||||||
|
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
|
@ -161,7 +161,7 @@ public class NamesResource {
|
|||||||
)
|
)
|
||||||
@ApiErrors({ApiError.NON_PRODUCTION, ApiError.TRANSACTION_INVALID, ApiError.TRANSFORMATION_ERROR, ApiError.REPOSITORY_ISSUE})
|
@ApiErrors({ApiError.NON_PRODUCTION, ApiError.TRANSACTION_INVALID, ApiError.TRANSFORMATION_ERROR, ApiError.REPOSITORY_ISSUE})
|
||||||
public String registerName(RegisterNameTransactionData transactionData) {
|
public String registerName(RegisterNameTransactionData transactionData) {
|
||||||
if (Settings.getInstance().isRestrictedApi())
|
if (Settings.getInstance().isApiRestricted())
|
||||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.NON_PRODUCTION);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.NON_PRODUCTION);
|
||||||
|
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
@ -207,7 +207,7 @@ public class NamesResource {
|
|||||||
)
|
)
|
||||||
@ApiErrors({ApiError.NON_PRODUCTION, ApiError.TRANSACTION_INVALID, ApiError.TRANSFORMATION_ERROR, ApiError.REPOSITORY_ISSUE})
|
@ApiErrors({ApiError.NON_PRODUCTION, ApiError.TRANSACTION_INVALID, ApiError.TRANSFORMATION_ERROR, ApiError.REPOSITORY_ISSUE})
|
||||||
public String updateName(UpdateNameTransactionData transactionData) {
|
public String updateName(UpdateNameTransactionData transactionData) {
|
||||||
if (Settings.getInstance().isRestrictedApi())
|
if (Settings.getInstance().isApiRestricted())
|
||||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.NON_PRODUCTION);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.NON_PRODUCTION);
|
||||||
|
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
@ -253,7 +253,7 @@ public class NamesResource {
|
|||||||
)
|
)
|
||||||
@ApiErrors({ApiError.NON_PRODUCTION, ApiError.TRANSACTION_INVALID, ApiError.TRANSFORMATION_ERROR, ApiError.REPOSITORY_ISSUE})
|
@ApiErrors({ApiError.NON_PRODUCTION, ApiError.TRANSACTION_INVALID, ApiError.TRANSFORMATION_ERROR, ApiError.REPOSITORY_ISSUE})
|
||||||
public String sellName(SellNameTransactionData transactionData) {
|
public String sellName(SellNameTransactionData transactionData) {
|
||||||
if (Settings.getInstance().isRestrictedApi())
|
if (Settings.getInstance().isApiRestricted())
|
||||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.NON_PRODUCTION);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.NON_PRODUCTION);
|
||||||
|
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
@ -299,7 +299,7 @@ public class NamesResource {
|
|||||||
)
|
)
|
||||||
@ApiErrors({ApiError.NON_PRODUCTION, ApiError.TRANSACTION_INVALID, ApiError.TRANSFORMATION_ERROR, ApiError.REPOSITORY_ISSUE})
|
@ApiErrors({ApiError.NON_PRODUCTION, ApiError.TRANSACTION_INVALID, ApiError.TRANSFORMATION_ERROR, ApiError.REPOSITORY_ISSUE})
|
||||||
public String cancelSellName(CancelSellNameTransactionData transactionData) {
|
public String cancelSellName(CancelSellNameTransactionData transactionData) {
|
||||||
if (Settings.getInstance().isRestrictedApi())
|
if (Settings.getInstance().isApiRestricted())
|
||||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.NON_PRODUCTION);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.NON_PRODUCTION);
|
||||||
|
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
@ -345,7 +345,7 @@ public class NamesResource {
|
|||||||
)
|
)
|
||||||
@ApiErrors({ApiError.NON_PRODUCTION, ApiError.TRANSACTION_INVALID, ApiError.TRANSFORMATION_ERROR, ApiError.REPOSITORY_ISSUE})
|
@ApiErrors({ApiError.NON_PRODUCTION, ApiError.TRANSACTION_INVALID, ApiError.TRANSFORMATION_ERROR, ApiError.REPOSITORY_ISSUE})
|
||||||
public String buyName(BuyNameTransactionData transactionData) {
|
public String buyName(BuyNameTransactionData transactionData) {
|
||||||
if (Settings.getInstance().isRestrictedApi())
|
if (Settings.getInstance().isApiRestricted())
|
||||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.NON_PRODUCTION);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.NON_PRODUCTION);
|
||||||
|
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
|
@ -63,7 +63,7 @@ public class PaymentsResource {
|
|||||||
)
|
)
|
||||||
@ApiErrors({ApiError.NON_PRODUCTION, ApiError.TRANSACTION_INVALID, ApiError.TRANSFORMATION_ERROR, ApiError.REPOSITORY_ISSUE})
|
@ApiErrors({ApiError.NON_PRODUCTION, ApiError.TRANSACTION_INVALID, ApiError.TRANSFORMATION_ERROR, ApiError.REPOSITORY_ISSUE})
|
||||||
public String makePayment(PaymentTransactionData transactionData) {
|
public String makePayment(PaymentTransactionData transactionData) {
|
||||||
if (Settings.getInstance().isRestrictedApi())
|
if (Settings.getInstance().isApiRestricted())
|
||||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.NON_PRODUCTION);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.NON_PRODUCTION);
|
||||||
|
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
|
@ -355,7 +355,7 @@ public class TransactionsResource {
|
|||||||
ApiError.NON_PRODUCTION, ApiError.INVALID_PRIVATE_KEY, ApiError.TRANSFORMATION_ERROR
|
ApiError.NON_PRODUCTION, ApiError.INVALID_PRIVATE_KEY, ApiError.TRANSFORMATION_ERROR
|
||||||
})
|
})
|
||||||
public String signTransaction(SimpleTransactionSignRequest signRequest) {
|
public String signTransaction(SimpleTransactionSignRequest signRequest) {
|
||||||
if (Settings.getInstance().isRestrictedApi())
|
if (Settings.getInstance().isApiRestricted())
|
||||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.NON_PRODUCTION);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.NON_PRODUCTION);
|
||||||
|
|
||||||
if (signRequest.transactionBytes.length == 0)
|
if (signRequest.transactionBytes.length == 0)
|
||||||
|
@ -79,7 +79,7 @@ public class UtilsResource {
|
|||||||
)
|
)
|
||||||
@ApiErrors({ApiError.NON_PRODUCTION, ApiError.INVALID_DATA})
|
@ApiErrors({ApiError.NON_PRODUCTION, ApiError.INVALID_DATA})
|
||||||
public String fromBase64(String base64) {
|
public String fromBase64(String base64) {
|
||||||
if (Settings.getInstance().isRestrictedApi())
|
if (Settings.getInstance().isApiRestricted())
|
||||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.NON_PRODUCTION);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.NON_PRODUCTION);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -115,7 +115,7 @@ public class UtilsResource {
|
|||||||
)
|
)
|
||||||
@ApiErrors({ApiError.NON_PRODUCTION, ApiError.INVALID_DATA})
|
@ApiErrors({ApiError.NON_PRODUCTION, ApiError.INVALID_DATA})
|
||||||
public String base64from58(String base58) {
|
public String base64from58(String base58) {
|
||||||
if (Settings.getInstance().isRestrictedApi())
|
if (Settings.getInstance().isApiRestricted())
|
||||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.NON_PRODUCTION);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.NON_PRODUCTION);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -142,7 +142,7 @@ public class UtilsResource {
|
|||||||
)
|
)
|
||||||
@ApiErrors({ApiError.NON_PRODUCTION})
|
@ApiErrors({ApiError.NON_PRODUCTION})
|
||||||
public String toBase64(@PathParam("hex") String hex) {
|
public String toBase64(@PathParam("hex") String hex) {
|
||||||
if (Settings.getInstance().isRestrictedApi())
|
if (Settings.getInstance().isApiRestricted())
|
||||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.NON_PRODUCTION);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.NON_PRODUCTION);
|
||||||
|
|
||||||
return Base64.getEncoder().encodeToString(HashCode.fromString(hex).asBytes());
|
return Base64.getEncoder().encodeToString(HashCode.fromString(hex).asBytes());
|
||||||
@ -165,7 +165,7 @@ public class UtilsResource {
|
|||||||
)
|
)
|
||||||
@ApiErrors({ApiError.NON_PRODUCTION})
|
@ApiErrors({ApiError.NON_PRODUCTION})
|
||||||
public String toBase58(@PathParam("hex") String hex) {
|
public String toBase58(@PathParam("hex") String hex) {
|
||||||
if (Settings.getInstance().isRestrictedApi())
|
if (Settings.getInstance().isApiRestricted())
|
||||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.NON_PRODUCTION);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.NON_PRODUCTION);
|
||||||
|
|
||||||
return Base58.encode(HashCode.fromString(hex).asBytes());
|
return Base58.encode(HashCode.fromString(hex).asBytes());
|
||||||
@ -190,7 +190,7 @@ public class UtilsResource {
|
|||||||
)
|
)
|
||||||
@ApiErrors({ApiError.NON_PRODUCTION})
|
@ApiErrors({ApiError.NON_PRODUCTION})
|
||||||
public String random(@QueryParam("length") Integer length) {
|
public String random(@QueryParam("length") Integer length) {
|
||||||
if (Settings.getInstance().isRestrictedApi())
|
if (Settings.getInstance().isApiRestricted())
|
||||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.NON_PRODUCTION);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.NON_PRODUCTION);
|
||||||
|
|
||||||
if (length == null)
|
if (length == null)
|
||||||
@ -221,7 +221,7 @@ public class UtilsResource {
|
|||||||
)
|
)
|
||||||
@ApiErrors({ApiError.NON_PRODUCTION, ApiError.INVALID_DATA})
|
@ApiErrors({ApiError.NON_PRODUCTION, ApiError.INVALID_DATA})
|
||||||
public String getMnemonic(@QueryParam("entropy") String suppliedEntropy) {
|
public String getMnemonic(@QueryParam("entropy") String suppliedEntropy) {
|
||||||
if (Settings.getInstance().isRestrictedApi())
|
if (Settings.getInstance().isApiRestricted())
|
||||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.NON_PRODUCTION);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.NON_PRODUCTION);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -290,7 +290,7 @@ public class UtilsResource {
|
|||||||
)
|
)
|
||||||
@ApiErrors({ApiError.NON_PRODUCTION})
|
@ApiErrors({ApiError.NON_PRODUCTION})
|
||||||
public String fromMnemonic(String mnemonic) {
|
public String fromMnemonic(String mnemonic) {
|
||||||
if (Settings.getInstance().isRestrictedApi())
|
if (Settings.getInstance().isApiRestricted())
|
||||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.NON_PRODUCTION);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.NON_PRODUCTION);
|
||||||
|
|
||||||
if (mnemonic.isEmpty())
|
if (mnemonic.isEmpty())
|
||||||
@ -336,7 +336,7 @@ public class UtilsResource {
|
|||||||
)
|
)
|
||||||
@ApiErrors({ApiError.NON_PRODUCTION, ApiError.INVALID_DATA})
|
@ApiErrors({ApiError.NON_PRODUCTION, ApiError.INVALID_DATA})
|
||||||
public String privateKey(@PathParam("entropy") String entropy58) {
|
public String privateKey(@PathParam("entropy") String entropy58) {
|
||||||
if (Settings.getInstance().isRestrictedApi())
|
if (Settings.getInstance().isApiRestricted())
|
||||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.NON_PRODUCTION);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.NON_PRODUCTION);
|
||||||
|
|
||||||
byte[] entropy;
|
byte[] entropy;
|
||||||
@ -372,7 +372,7 @@ public class UtilsResource {
|
|||||||
)
|
)
|
||||||
@ApiErrors({ApiError.NON_PRODUCTION, ApiError.INVALID_DATA})
|
@ApiErrors({ApiError.NON_PRODUCTION, ApiError.INVALID_DATA})
|
||||||
public String publicKey(@PathParam("privateKey") String privateKey58) {
|
public String publicKey(@PathParam("privateKey") String privateKey58) {
|
||||||
if (Settings.getInstance().isRestrictedApi())
|
if (Settings.getInstance().isApiRestricted())
|
||||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.NON_PRODUCTION);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.NON_PRODUCTION);
|
||||||
|
|
||||||
byte[] privateKey;
|
byte[] privateKey;
|
||||||
|
@ -731,18 +731,21 @@ public class Block {
|
|||||||
if (this.blockData.getTimestamp() <= parentBlockData.getTimestamp())
|
if (this.blockData.getTimestamp() <= parentBlockData.getTimestamp())
|
||||||
return ValidationResult.TIMESTAMP_OLDER_THAN_PARENT;
|
return ValidationResult.TIMESTAMP_OLDER_THAN_PARENT;
|
||||||
|
|
||||||
// Check timestamp is not in the future (within configurable ~500ms margin)
|
// These checks are disabled for testnet
|
||||||
if (this.blockData.getTimestamp() - BlockChain.getInstance().getBlockTimestampMargin() > NTP.getTime())
|
if (!BlockChain.getInstance().isTestNet()) {
|
||||||
return ValidationResult.TIMESTAMP_IN_FUTURE;
|
// Check timestamp is not in the future (within configurable ~500ms margin)
|
||||||
|
if (this.blockData.getTimestamp() - BlockChain.getInstance().getBlockTimestampMargin() > NTP.getTime())
|
||||||
|
return ValidationResult.TIMESTAMP_IN_FUTURE;
|
||||||
|
|
||||||
// Legacy gen1 test: check timestamp milliseconds is the same as parent timestamp milliseconds?
|
// Legacy gen1 test: check timestamp milliseconds is the same as parent timestamp milliseconds?
|
||||||
if (this.blockData.getTimestamp() % 1000 != parentBlockData.getTimestamp() % 1000)
|
if (this.blockData.getTimestamp() % 1000 != parentBlockData.getTimestamp() % 1000)
|
||||||
return ValidationResult.TIMESTAMP_MS_INCORRECT;
|
return ValidationResult.TIMESTAMP_MS_INCORRECT;
|
||||||
|
|
||||||
// Too early to forge block?
|
// Too early to forge block?
|
||||||
// XXX DISABLED as it doesn't work - but why?
|
// XXX DISABLED as it doesn't work - but why?
|
||||||
// if (this.blockData.getTimestamp() < parentBlock.getBlockData().getTimestamp() + BlockChain.getInstance().getMinBlockTime())
|
// if (this.blockData.getTimestamp() < parentBlock.getBlockData().getTimestamp() + BlockChain.getInstance().getMinBlockTime())
|
||||||
// return ValidationResult.TIMESTAMP_TOO_SOON;
|
// return ValidationResult.TIMESTAMP_TOO_SOON;
|
||||||
|
}
|
||||||
|
|
||||||
// Check block version
|
// Check block version
|
||||||
if (this.blockData.getVersion() != parentBlock.getNextBlockVersion())
|
if (this.blockData.getVersion() != parentBlock.getNextBlockVersion())
|
||||||
|
@ -1,17 +1,26 @@
|
|||||||
package org.qora.block;
|
package org.qora.block;
|
||||||
|
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
|
import java.io.FileReader;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.Reader;
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.math.MathContext;
|
import java.math.MathContext;
|
||||||
import java.sql.SQLException;
|
import java.sql.SQLException;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
import javax.xml.bind.JAXBContext;
|
||||||
|
import javax.xml.bind.JAXBException;
|
||||||
|
import javax.xml.bind.Unmarshaller;
|
||||||
|
import javax.xml.bind.annotation.XmlAccessType;
|
||||||
|
import javax.xml.bind.annotation.XmlAccessorType;
|
||||||
|
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
|
||||||
|
import javax.xml.transform.stream.StreamSource;
|
||||||
|
|
||||||
import org.apache.logging.log4j.LogManager;
|
import org.apache.logging.log4j.LogManager;
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
|
import org.eclipse.persistence.jaxb.JAXBContextFactory;
|
||||||
import org.json.simple.JSONObject;
|
import org.eclipse.persistence.jaxb.UnmarshallerProperties;
|
||||||
import org.qora.data.asset.AssetData;
|
|
||||||
import org.qora.data.block.BlockData;
|
import org.qora.data.block.BlockData;
|
||||||
import org.qora.group.Group;
|
import org.qora.group.Group;
|
||||||
import org.qora.repository.BlockRepository;
|
import org.qora.repository.BlockRepository;
|
||||||
@ -19,43 +28,59 @@ import org.qora.repository.DataException;
|
|||||||
import org.qora.repository.Repository;
|
import org.qora.repository.Repository;
|
||||||
import org.qora.repository.RepositoryManager;
|
import org.qora.repository.RepositoryManager;
|
||||||
import org.qora.settings.Settings;
|
import org.qora.settings.Settings;
|
||||||
|
import org.qora.utils.StringLongMapXmlAdapter;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class representing the blockchain as a whole.
|
* Class representing the blockchain as a whole.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
// All properties to be converted to JSON via JAXB
|
||||||
|
@XmlAccessorType(XmlAccessType.FIELD)
|
||||||
public class BlockChain {
|
public class BlockChain {
|
||||||
|
|
||||||
private static final Logger LOGGER = LogManager.getLogger(BlockChain.class);
|
private static final Logger LOGGER = LogManager.getLogger(BlockChain.class);
|
||||||
|
|
||||||
public enum FeatureValueType {
|
|
||||||
height,
|
|
||||||
timestamp;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static BlockChain instance = null;
|
private static BlockChain instance = null;
|
||||||
|
|
||||||
// Properties
|
// Properties
|
||||||
private boolean isTestNet;
|
|
||||||
|
private boolean isTestNet = false;
|
||||||
|
/** Maximum coin supply. */
|
||||||
|
private BigDecimal maxBalance;
|
||||||
|
|
||||||
private BigDecimal unitFee;
|
private BigDecimal unitFee;
|
||||||
private BigDecimal maxBytesPerUnitFee;
|
private BigDecimal maxBytesPerUnitFee;
|
||||||
private BigDecimal minFeePerByte;
|
private BigDecimal minFeePerByte;
|
||||||
/** Maximum coin supply. */
|
|
||||||
private BigDecimal maxBalance;
|
|
||||||
/** Number of blocks between recalculating block's generating balance. */
|
/** Number of blocks between recalculating block's generating balance. */
|
||||||
private int blockDifficultyInterval;
|
private int blockDifficultyInterval;
|
||||||
/** Minimum target time between blocks, in seconds. */
|
/** Minimum target time between blocks, in milliseconds. */
|
||||||
private long minBlockTime;
|
private long minBlockTime;
|
||||||
/** Maximum target time between blocks, in seconds. */
|
/** Maximum target time between blocks, in milliseconds. */
|
||||||
private long maxBlockTime;
|
private long maxBlockTime;
|
||||||
/** Maximum acceptable timestamp disagreement offset in milliseconds. */
|
/** Maximum acceptable timestamp disagreement offset in milliseconds. */
|
||||||
private long blockTimestampMargin;
|
private long blockTimestampMargin;
|
||||||
|
|
||||||
/** Whether transactions with txGroupId of NO_GROUP are allowed */
|
/** Whether transactions with txGroupId of NO_GROUP are allowed */
|
||||||
private boolean grouplessAllowed;
|
private boolean requireGroupForApproval;
|
||||||
/** Default groupID when account's default groupID isn't set */
|
/** Default groupID when account's default groupID isn't set */
|
||||||
private int defaultGroupId = Group.NO_GROUP;
|
private int defaultGroupId = Group.NO_GROUP;
|
||||||
|
|
||||||
|
private GenesisBlock.GenesisInfo genesisInfo;
|
||||||
|
|
||||||
|
public enum FeatureTrigger {
|
||||||
|
messageHeight,
|
||||||
|
atHeight,
|
||||||
|
assetsTimestamp,
|
||||||
|
votingTimestamp,
|
||||||
|
arbitraryTimestamp,
|
||||||
|
powfixTimestamp,
|
||||||
|
v2Timestamp;
|
||||||
|
}
|
||||||
|
|
||||||
/** Map of which blockchain features are enabled when (height/timestamp) */
|
/** Map of which blockchain features are enabled when (height/timestamp) */
|
||||||
private Map<String, Map<FeatureValueType, Long>> featureTriggers;
|
@XmlJavaTypeAdapter(StringLongMapXmlAdapter.class)
|
||||||
|
private Map<String, Long> featureTriggers;
|
||||||
|
|
||||||
// This property is slightly different as we need it early and we want to avoid getInstance() loop
|
// This property is slightly different as we need it early and we want to avoid getInstance() loop
|
||||||
private static boolean useBrokenMD160ForAddresses = false;
|
private static boolean useBrokenMD160ForAddresses = false;
|
||||||
@ -73,9 +98,69 @@ public class BlockChain {
|
|||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void fileInstance(String filename) {
|
||||||
|
JAXBContext jc;
|
||||||
|
Unmarshaller unmarshaller;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Create JAXB context aware of Settings
|
||||||
|
jc = JAXBContextFactory.createContext(new Class[] {
|
||||||
|
BlockChain.class, GenesisBlock.GenesisInfo.class
|
||||||
|
}, null);
|
||||||
|
|
||||||
|
// Create unmarshaller
|
||||||
|
unmarshaller = jc.createUnmarshaller();
|
||||||
|
|
||||||
|
// Set the unmarshaller media type to JSON
|
||||||
|
unmarshaller.setProperty(UnmarshallerProperties.MEDIA_TYPE, "application/json");
|
||||||
|
|
||||||
|
// Tell unmarshaller that there's no JSON root element in the JSON input
|
||||||
|
unmarshaller.setProperty(UnmarshallerProperties.JSON_INCLUDE_ROOT, false);
|
||||||
|
|
||||||
|
} catch (JAXBException e) {
|
||||||
|
LOGGER.error("Unable to process blockchain config file", e);
|
||||||
|
throw new RuntimeException("Unable to process blockchain config file", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
BlockChain blockchain = null;
|
||||||
|
|
||||||
|
LOGGER.info("Using blockchain config file: " + filename);
|
||||||
|
|
||||||
|
// Create the StreamSource by creating Reader to the JSON input
|
||||||
|
try (Reader settingsReader = new FileReader(filename)) {
|
||||||
|
StreamSource json = new StreamSource(settingsReader);
|
||||||
|
|
||||||
|
// Attempt to unmarshal JSON stream to BlockChain config
|
||||||
|
blockchain = unmarshaller.unmarshal(json, BlockChain.class).getValue();
|
||||||
|
} catch (FileNotFoundException e) {
|
||||||
|
LOGGER.error("Blockchain config file not found: " + filename);
|
||||||
|
throw new RuntimeException("Blockchain config file not found: " + filename);
|
||||||
|
} catch (JAXBException e) {
|
||||||
|
LOGGER.error("Unable to process blockchain config file", e);
|
||||||
|
throw new RuntimeException("Unable to process blockchain config file", e);
|
||||||
|
} catch (IOException e) {
|
||||||
|
LOGGER.error("Unable to process blockchain config file", e);
|
||||||
|
throw new RuntimeException("Unable to process blockchain config file", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate config
|
||||||
|
blockchain.validateConfig();
|
||||||
|
|
||||||
|
// Minor fix-up
|
||||||
|
blockchain.maxBytesPerUnitFee.setScale(8);
|
||||||
|
blockchain.unitFee.setScale(8);
|
||||||
|
blockchain.minFeePerByte = blockchain.unitFee.divide(blockchain.maxBytesPerUnitFee, MathContext.DECIMAL32);
|
||||||
|
|
||||||
|
// Successfully read config now in effect
|
||||||
|
instance = blockchain;
|
||||||
|
|
||||||
|
// Pass genesis info to GenesisBlock
|
||||||
|
GenesisBlock.newInstance(blockchain.genesisInfo);
|
||||||
|
}
|
||||||
|
|
||||||
// Getters / setters
|
// Getters / setters
|
||||||
|
|
||||||
public boolean getIsTestNet() {
|
public boolean isTestNet() {
|
||||||
return this.isTestNet;
|
return this.isTestNet;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -111,8 +196,9 @@ public class BlockChain {
|
|||||||
return this.blockTimestampMargin;
|
return this.blockTimestampMargin;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean getGrouplessAllowed() {
|
/** Returns true if approval-needing transaction types require a txGroupId other than NO_GROUP. */
|
||||||
return this.grouplessAllowed;
|
public boolean getRequireGroupForApproval() {
|
||||||
|
return this.requireGroupForApproval;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getDefaultGroupId() {
|
public int getDefaultGroupId() {
|
||||||
@ -123,134 +209,62 @@ public class BlockChain {
|
|||||||
return useBrokenMD160ForAddresses;
|
return useBrokenMD160ForAddresses;
|
||||||
}
|
}
|
||||||
|
|
||||||
private long getFeatureTrigger(String feature, FeatureValueType valueType) {
|
|
||||||
Map<FeatureValueType, Long> featureTrigger = featureTriggers.get(feature);
|
|
||||||
if (featureTrigger == null)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
Long value = featureTrigger.get(valueType);
|
|
||||||
if (value == null)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convenience methods for specific blockchain feature triggers
|
// Convenience methods for specific blockchain feature triggers
|
||||||
|
|
||||||
public long getMessageReleaseHeight() {
|
public long getMessageReleaseHeight() {
|
||||||
return getFeatureTrigger("message", FeatureValueType.height);
|
return featureTriggers.get("messageHeight");
|
||||||
}
|
}
|
||||||
|
|
||||||
public long getATReleaseHeight() {
|
public long getATReleaseHeight() {
|
||||||
return getFeatureTrigger("AT", FeatureValueType.height);
|
return featureTriggers.get("atHeight");
|
||||||
}
|
}
|
||||||
|
|
||||||
public long getPowFixReleaseTimestamp() {
|
public long getPowFixReleaseTimestamp() {
|
||||||
return getFeatureTrigger("powfix", FeatureValueType.timestamp);
|
return featureTriggers.get("powfixTimestamp");
|
||||||
}
|
}
|
||||||
|
|
||||||
public long getAssetsReleaseTimestamp() {
|
public long getAssetsReleaseTimestamp() {
|
||||||
return getFeatureTrigger("assets", FeatureValueType.timestamp);
|
return featureTriggers.get("assetsTimestamp");
|
||||||
}
|
}
|
||||||
|
|
||||||
public long getVotingReleaseTimestamp() {
|
public long getVotingReleaseTimestamp() {
|
||||||
return getFeatureTrigger("voting", FeatureValueType.timestamp);
|
return featureTriggers.get("votingTimestamp");
|
||||||
}
|
}
|
||||||
|
|
||||||
public long getArbitraryReleaseTimestamp() {
|
public long getArbitraryReleaseTimestamp() {
|
||||||
return getFeatureTrigger("arbitrary", FeatureValueType.timestamp);
|
return featureTriggers.get("arbitraryTimestamp");
|
||||||
}
|
}
|
||||||
|
|
||||||
public long getQoraV2Timestamp() {
|
public long getQoraV2Timestamp() {
|
||||||
return getFeatureTrigger("v2", FeatureValueType.timestamp);
|
return featureTriggers.get("v2Timestamp");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Blockchain config from JSON
|
/** Validate blockchain config read from JSON */
|
||||||
|
private void validateConfig() {
|
||||||
public static void fromJSON(JSONObject json) {
|
if (this.genesisInfo == null) {
|
||||||
// Determine hash function for generating addresses as we need that to build genesis block, etc.
|
LOGGER.error("No \"genesisInfo\" entry found in blockchain config");
|
||||||
Boolean useBrokenMD160 = null;
|
throw new RuntimeException("No \"genesisInfo\" entry found in blockchain config");
|
||||||
if (json.containsKey("useBrokenMD160ForAddresses"))
|
|
||||||
useBrokenMD160 = (Boolean) Settings.getTypedJson(json, "useBrokenMD160ForAddresses", Boolean.class);
|
|
||||||
|
|
||||||
if (useBrokenMD160 != null)
|
|
||||||
useBrokenMD160ForAddresses = useBrokenMD160.booleanValue();
|
|
||||||
|
|
||||||
Object genesisJson = json.get("genesis");
|
|
||||||
if (genesisJson == null) {
|
|
||||||
LOGGER.error("No \"genesis\" entry found in blockchain config");
|
|
||||||
throw new RuntimeException("No \"genesis\" entry found in blockchain config");
|
|
||||||
}
|
}
|
||||||
GenesisBlock.fromJSON((JSONObject) genesisJson);
|
|
||||||
|
|
||||||
// Simple blockchain properties
|
if (this.featureTriggers == null) {
|
||||||
|
LOGGER.error("No \"featureTriggers\" entry found in blockchain config");
|
||||||
|
throw new RuntimeException("No \"featureTriggers\" entry found in blockchain config");
|
||||||
|
}
|
||||||
|
|
||||||
boolean grouplessAllowed = true;
|
// Check all featureTriggers are present
|
||||||
if (json.containsKey("grouplessAllowed"))
|
for (FeatureTrigger featureTrigger : FeatureTrigger.values())
|
||||||
grouplessAllowed = (Boolean) Settings.getTypedJson(json, "grouplessAllowed", Boolean.class);
|
if (!this.featureTriggers.containsKey(featureTrigger.name())) {
|
||||||
|
LOGGER.error(String.format("Missing feature trigger \"%s\" in blockchain config", featureTrigger.name()));
|
||||||
|
throw new RuntimeException("Missing feature trigger in blockchain config");
|
||||||
|
}
|
||||||
|
|
||||||
Integer defaultGroupId = null;
|
// If groupless approval-needing transactions are not allowed the defaultGroupId needs to be set
|
||||||
if (json.containsKey("defaultGroupId"))
|
|
||||||
defaultGroupId = ((Long) Settings.getTypedJson(json, "defaultGroupId", Long.class)).intValue();
|
|
||||||
|
|
||||||
// If groupless is not allowed the defaultGroupId needs to be set
|
|
||||||
// XXX we could also check groupID exists, or at least created in genesis block, or in blockchain config
|
// XXX we could also check groupID exists, or at least created in genesis block, or in blockchain config
|
||||||
if (!grouplessAllowed && (defaultGroupId == null || defaultGroupId == Group.NO_GROUP)) {
|
if (!this.requireGroupForApproval && this.defaultGroupId == Group.NO_GROUP) {
|
||||||
LOGGER.error("defaultGroupId must be set to valid groupID in blockchain config if groupless transactions are not allowed");
|
LOGGER.error("defaultGroupId must be set to valid groupID in blockchain config if groupless approval-needing transactions are not allowed");
|
||||||
throw new RuntimeException("defaultGroupId must be set to valid groupID in blockchain config if groupless transactions are not allowed");
|
throw new RuntimeException(
|
||||||
|
"defaultGroupId must be set to valid groupID in blockchain config if groupless approval-needing 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");
|
|
||||||
int blockDifficultyInterval = ((Long) Settings.getTypedJson(json, "blockDifficultyInterval", Long.class)).intValue();
|
|
||||||
long minBlockTime = 1000L * (Long) Settings.getTypedJson(json, "minBlockTime", Long.class); // config entry in seconds
|
|
||||||
long maxBlockTime = 1000L * (Long) Settings.getTypedJson(json, "maxBlockTime", Long.class); // config entry in seconds
|
|
||||||
long blockTimestampMargin = (Long) Settings.getTypedJson(json, "blockTimestampMargin", Long.class); // config entry in milliseconds
|
|
||||||
|
|
||||||
// blockchain feature triggers
|
|
||||||
Map<String, Map<FeatureValueType, Long>> featureTriggers = new HashMap<>();
|
|
||||||
JSONObject featuresJson = (JSONObject) Settings.getTypedJson(json, "featureTriggers", JSONObject.class);
|
|
||||||
for (Object feature : featuresJson.keySet()) {
|
|
||||||
String featureKey = (String) feature;
|
|
||||||
JSONObject trigger = (JSONObject) Settings.getTypedJson(featuresJson, featureKey, JSONObject.class);
|
|
||||||
|
|
||||||
if (!trigger.containsKey("height") && !trigger.containsKey("timestamp")) {
|
|
||||||
LOGGER.error("Feature trigger \"" + featureKey + "\" must contain \"height\" or \"timestamp\" in blockchain config file");
|
|
||||||
throw new RuntimeException("Feature trigger \"" + featureKey + "\" must contain \"height\" or \"timestamp\" in blockchain config file");
|
|
||||||
}
|
|
||||||
|
|
||||||
String triggerKey = (String) trigger.keySet().iterator().next();
|
|
||||||
FeatureValueType featureValueType = FeatureValueType.valueOf(triggerKey);
|
|
||||||
if (featureValueType == null) {
|
|
||||||
LOGGER.error("Unrecognised feature trigger value type \"" + triggerKey + "\" for feature \"" + featureKey + "\" in blockchain config file");
|
|
||||||
throw new RuntimeException(
|
|
||||||
"Unrecognised feature trigger value type \"" + triggerKey + "\" for feature \"" + featureKey + "\" in blockchain config file");
|
|
||||||
}
|
|
||||||
|
|
||||||
Long value = (Long) Settings.getJsonQuotedLong(trigger, triggerKey);
|
|
||||||
|
|
||||||
featureTriggers.put(featureKey, Collections.singletonMap(featureValueType, value));
|
|
||||||
}
|
|
||||||
|
|
||||||
instance = new BlockChain();
|
|
||||||
instance.isTestNet = isTestNet;
|
|
||||||
instance.unitFee = unitFee;
|
|
||||||
instance.maxBytesPerUnitFee = BigDecimal.valueOf(maxBytesPerUnitFee).setScale(8);
|
|
||||||
instance.minFeePerByte = unitFee.divide(instance.maxBytesPerUnitFee, MathContext.DECIMAL32);
|
|
||||||
instance.maxBalance = maxBalance;
|
|
||||||
instance.blockDifficultyInterval = blockDifficultyInterval;
|
|
||||||
instance.minBlockTime = minBlockTime;
|
|
||||||
instance.maxBlockTime = maxBlockTime;
|
|
||||||
instance.blockTimestampMargin = blockTimestampMargin;
|
|
||||||
instance.grouplessAllowed = grouplessAllowed;
|
|
||||||
if (defaultGroupId != null)
|
|
||||||
instance.defaultGroupId = defaultGroupId;
|
|
||||||
instance.featureTriggers = featureTriggers;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -287,11 +301,6 @@ public class BlockChain {
|
|||||||
|
|
||||||
GenesisBlock genesisBlock = GenesisBlock.getInstance(repository);
|
GenesisBlock genesisBlock = GenesisBlock.getInstance(repository);
|
||||||
|
|
||||||
// Add initial assets
|
|
||||||
// NOTE: Asset's [transaction] reference doesn't exist as a transaction!
|
|
||||||
for (AssetData assetData : genesisBlock.getInitialAssets())
|
|
||||||
repository.getAssetRepository().save(assetData);
|
|
||||||
|
|
||||||
// Add Genesis Block to blockchain
|
// Add Genesis Block to blockchain
|
||||||
genesisBlock.process();
|
genesisBlock.process();
|
||||||
|
|
||||||
|
@ -28,8 +28,6 @@ public class BlockGenerator extends Thread {
|
|||||||
// Properties
|
// Properties
|
||||||
private byte[] generatorPrivateKey;
|
private byte[] generatorPrivateKey;
|
||||||
private PrivateKeyAccount generator;
|
private PrivateKeyAccount generator;
|
||||||
private Block previousBlock;
|
|
||||||
private Block newBlock;
|
|
||||||
private boolean running;
|
private boolean running;
|
||||||
|
|
||||||
// Other properties
|
// Other properties
|
||||||
@ -39,8 +37,6 @@ public class BlockGenerator extends Thread {
|
|||||||
|
|
||||||
public BlockGenerator(byte[] generatorPrivateKey) {
|
public BlockGenerator(byte[] generatorPrivateKey) {
|
||||||
this.generatorPrivateKey = generatorPrivateKey;
|
this.generatorPrivateKey = generatorPrivateKey;
|
||||||
this.previousBlock = null;
|
|
||||||
this.newBlock = null;
|
|
||||||
this.running = true;
|
this.running = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -66,6 +62,8 @@ public class BlockGenerator extends Thread {
|
|||||||
|
|
||||||
// Going to need this a lot...
|
// Going to need this a lot...
|
||||||
BlockRepository blockRepository = repository.getBlockRepository();
|
BlockRepository blockRepository = repository.getBlockRepository();
|
||||||
|
Block previousBlock = null;
|
||||||
|
Block newBlock = null;
|
||||||
|
|
||||||
while (running) {
|
while (running) {
|
||||||
// Check blockchain hasn't changed
|
// Check blockchain hasn't changed
|
||||||
@ -82,39 +80,41 @@ public class BlockGenerator extends Thread {
|
|||||||
// Make sure we're the only thread modifying the blockchain
|
// Make sure we're the only thread modifying the blockchain
|
||||||
Lock blockchainLock = Controller.getInstance().getBlockchainLock();
|
Lock blockchainLock = Controller.getInstance().getBlockchainLock();
|
||||||
if (blockchainLock.tryLock())
|
if (blockchainLock.tryLock())
|
||||||
try {
|
generation: try {
|
||||||
// Is new block valid yet? (Before adding unconfirmed transactions)
|
// Is new block valid yet? (Before adding unconfirmed transactions)
|
||||||
if (newBlock.isValid() == ValidationResult.OK) {
|
if (newBlock.isValid() != ValidationResult.OK)
|
||||||
// Delete invalid transactions
|
break generation;
|
||||||
deleteInvalidTransactions(repository);
|
|
||||||
|
|
||||||
// Add unconfirmed transactions
|
// Delete invalid transactions
|
||||||
addUnconfirmedTransactions(repository, newBlock);
|
deleteInvalidTransactions(repository);
|
||||||
|
|
||||||
// Sign to create block's signature
|
// Add unconfirmed transactions
|
||||||
newBlock.sign();
|
addUnconfirmedTransactions(repository, newBlock);
|
||||||
|
|
||||||
// If newBlock is still valid then we can use it
|
// Sign to create block's signature
|
||||||
ValidationResult validationResult = newBlock.isValid();
|
newBlock.sign();
|
||||||
if (validationResult == ValidationResult.OK) {
|
|
||||||
// Add to blockchain - something else will notice and broadcast new block to network
|
|
||||||
try {
|
|
||||||
newBlock.process();
|
|
||||||
LOGGER.info("Generated new block: " + newBlock.getBlockData().getHeight());
|
|
||||||
repository.saveChanges();
|
|
||||||
|
|
||||||
// Notify controller
|
// Is newBlock still valid?
|
||||||
Controller.getInstance().onGeneratedBlock(newBlock.getBlockData());
|
ValidationResult validationResult = newBlock.isValid();
|
||||||
} catch (DataException e) {
|
if (validationResult != ValidationResult.OK) {
|
||||||
// Unable to process block - report and discard
|
// No longer valid? Report and discard
|
||||||
LOGGER.error("Unable to process newly generated block?", e);
|
LOGGER.error("Valid, generated block now invalid '" + validationResult.name() + "' after adding unconfirmed transactions?");
|
||||||
newBlock = null;
|
newBlock = null;
|
||||||
}
|
break generation;
|
||||||
} else {
|
}
|
||||||
// No longer valid? Report and discard
|
|
||||||
LOGGER.error("Valid, generated block now invalid '" + validationResult.name() + "' after adding unconfirmed transactions?");
|
// Add to blockchain - something else will notice and broadcast new block to network
|
||||||
newBlock = null;
|
try {
|
||||||
}
|
newBlock.process();
|
||||||
|
LOGGER.info("Generated new block: " + newBlock.getBlockData().getHeight());
|
||||||
|
repository.saveChanges();
|
||||||
|
|
||||||
|
// Notify controller
|
||||||
|
Controller.getInstance().onGeneratedBlock(newBlock.getBlockData());
|
||||||
|
} catch (DataException e) {
|
||||||
|
// Unable to process block - report and discard
|
||||||
|
LOGGER.error("Unable to process newly generated block?", e);
|
||||||
|
newBlock = null;
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
blockchainLock.unlock();
|
blockchainLock.unlock();
|
||||||
@ -134,7 +134,7 @@ public class BlockGenerator extends Thread {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void deleteInvalidTransactions(Repository repository) throws DataException {
|
private static void deleteInvalidTransactions(Repository repository) throws DataException {
|
||||||
List<TransactionData> invalidTransactions = Transaction.getInvalidTransactions(repository);
|
List<TransactionData> invalidTransactions = Transaction.getInvalidTransactions(repository);
|
||||||
|
|
||||||
// Actually delete invalid transactions from database
|
// Actually delete invalid transactions from database
|
||||||
@ -145,7 +145,7 @@ public class BlockGenerator extends Thread {
|
|||||||
repository.saveChanges();
|
repository.saveChanges();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addUnconfirmedTransactions(Repository repository, Block newBlock) throws DataException {
|
private static void addUnconfirmedTransactions(Repository repository, Block newBlock) throws DataException {
|
||||||
// Grab all valid unconfirmed transactions (already sorted)
|
// Grab all valid unconfirmed transactions (already sorted)
|
||||||
List<TransactionData> unconfirmedTransactions = Transaction.getUnconfirmedTransactions(repository);
|
List<TransactionData> unconfirmedTransactions = Transaction.getUnconfirmedTransactions(repository);
|
||||||
|
|
||||||
@ -200,4 +200,41 @@ public class BlockGenerator extends Thread {
|
|||||||
this.interrupt();
|
this.interrupt();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void generateTestingBlock(Repository repository, PrivateKeyAccount generator) throws DataException {
|
||||||
|
if (!BlockChain.getInstance().isTestNet()) {
|
||||||
|
LOGGER.warn("Attempt to generating testing block but not in testnet mode!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
BlockData previousBlockData = repository.getBlockRepository().getLastBlock();
|
||||||
|
|
||||||
|
Block newBlock = new Block(repository, previousBlockData, generator);
|
||||||
|
|
||||||
|
// Make sure we're the only thread modifying the blockchain
|
||||||
|
Lock blockchainLock = Controller.getInstance().getBlockchainLock();
|
||||||
|
if (blockchainLock.tryLock())
|
||||||
|
try {
|
||||||
|
// Delete invalid transactions
|
||||||
|
deleteInvalidTransactions(repository);
|
||||||
|
|
||||||
|
// Add unconfirmed transactions
|
||||||
|
addUnconfirmedTransactions(repository, newBlock);
|
||||||
|
|
||||||
|
// Sign to create block's signature
|
||||||
|
newBlock.sign();
|
||||||
|
|
||||||
|
// Is newBlock still valid?
|
||||||
|
ValidationResult validationResult = newBlock.isValid();
|
||||||
|
if (validationResult != ValidationResult.OK)
|
||||||
|
throw new IllegalStateException(
|
||||||
|
"Valid, generated block now invalid '" + validationResult.name() + "' after adding unconfirmed transactions?");
|
||||||
|
|
||||||
|
// Add to blockchain
|
||||||
|
newBlock.process();
|
||||||
|
repository.saveChanges();
|
||||||
|
} finally {
|
||||||
|
blockchainLock.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -3,26 +3,30 @@ package org.qora.block;
|
|||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import javax.xml.bind.annotation.XmlAccessType;
|
||||||
|
import javax.xml.bind.annotation.XmlAccessorType;
|
||||||
|
|
||||||
import org.apache.logging.log4j.LogManager;
|
import org.apache.logging.log4j.LogManager;
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
import org.bitcoinj.core.Base58;
|
import org.qora.account.Account;
|
||||||
import org.json.simple.JSONArray;
|
|
||||||
import org.json.simple.JSONObject;
|
|
||||||
import org.qora.account.GenesisAccount;
|
import org.qora.account.GenesisAccount;
|
||||||
|
import org.qora.account.PublicKeyAccount;
|
||||||
import org.qora.crypto.Crypto;
|
import org.qora.crypto.Crypto;
|
||||||
import org.qora.data.asset.AssetData;
|
import org.qora.data.asset.AssetData;
|
||||||
import org.qora.data.block.BlockData;
|
import org.qora.data.block.BlockData;
|
||||||
import org.qora.data.transaction.GenesisTransactionData;
|
import org.qora.data.transaction.IssueAssetTransactionData;
|
||||||
import org.qora.data.transaction.TransactionData;
|
import org.qora.data.transaction.TransactionData;
|
||||||
import org.qora.repository.DataException;
|
import org.qora.repository.DataException;
|
||||||
import org.qora.repository.Repository;
|
import org.qora.repository.Repository;
|
||||||
import org.qora.settings.Settings;
|
|
||||||
import org.qora.transaction.Transaction;
|
import org.qora.transaction.Transaction;
|
||||||
|
import org.qora.transaction.Transaction.TransactionType;
|
||||||
|
import org.qora.transform.TransformationException;
|
||||||
|
import org.qora.transform.transaction.TransactionTransformer;
|
||||||
|
|
||||||
import com.google.common.primitives.Bytes;
|
import com.google.common.primitives.Bytes;
|
||||||
import com.google.common.primitives.Longs;
|
import com.google.common.primitives.Longs;
|
||||||
@ -34,7 +38,18 @@ public class GenesisBlock extends Block {
|
|||||||
private static final byte[] GENESIS_REFERENCE = new byte[] {
|
private static final byte[] GENESIS_REFERENCE = new byte[] {
|
||||||
1, 1, 1, 1, 1, 1, 1, 1
|
1, 1, 1, 1, 1, 1, 1, 1
|
||||||
}; // NOTE: Neither 64 nor 128 bytes!
|
}; // NOTE: Neither 64 nor 128 bytes!
|
||||||
private static final byte[] GENESIS_GENERATOR_PUBLIC_KEY = GenesisAccount.PUBLIC_KEY; // NOTE: 8 bytes not 32 bytes!
|
|
||||||
|
@XmlAccessorType(XmlAccessType.FIELD)
|
||||||
|
public static class GenesisInfo {
|
||||||
|
public int version = 1;
|
||||||
|
public long timestamp;
|
||||||
|
public BigDecimal generatingBalance;
|
||||||
|
|
||||||
|
public TransactionData[] transactions;
|
||||||
|
|
||||||
|
public GenesisInfo() {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Properties
|
// Properties
|
||||||
private static BlockData blockData;
|
private static BlockData blockData;
|
||||||
@ -53,95 +68,74 @@ public class GenesisBlock extends Block {
|
|||||||
|
|
||||||
// Construction from JSON
|
// Construction from JSON
|
||||||
|
|
||||||
public static void fromJSON(JSONObject json) {
|
/** Construct block data from blockchain config */
|
||||||
// All parsing first, then if successful we can proceed to construction
|
public static void newInstance(GenesisInfo info) {
|
||||||
|
// Should be safe to make this call as BlockChain's instance is set
|
||||||
|
// so we won't be blocked trying to re-enter synchronzied Settings.getInstance()
|
||||||
|
BlockChain blockchain = BlockChain.getInstance();
|
||||||
|
|
||||||
// Version
|
// Timestamp of zero means "now" but only valid for test nets!
|
||||||
int version = 1; // but could be bumped later
|
if (info.timestamp == 0) {
|
||||||
|
if (!blockchain.isTestNet()) {
|
||||||
// Timestamp
|
LOGGER.error("Genesis timestamp of zero (i.e. now) not valid for non-testnet blockchain configs");
|
||||||
String timestampStr = (String) Settings.getTypedJson(json, "timestamp", String.class);
|
throw new RuntimeException("Genesis timestamp of zero (i.e. now) not valid for non-testnet blockchain configs");
|
||||||
long timestamp;
|
|
||||||
|
|
||||||
if (timestampStr.equals("now"))
|
|
||||||
timestamp = System.currentTimeMillis();
|
|
||||||
else
|
|
||||||
try {
|
|
||||||
timestamp = Long.parseUnsignedLong(timestampStr);
|
|
||||||
} catch (NumberFormatException e) {
|
|
||||||
LOGGER.error("Unable to parse genesis timestamp: " + timestampStr);
|
|
||||||
throw new RuntimeException("Unable to parse genesis timestamp");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generating balance
|
// This will only take effect if there is no current genesis block in blockchain
|
||||||
BigDecimal generatingBalance = Settings.getJsonBigDecimal(json, "generatingBalance");
|
info.timestamp = System.currentTimeMillis();
|
||||||
|
|
||||||
// Transactions
|
|
||||||
JSONArray transactionsJson = (JSONArray) Settings.getTypedJson(json, "transactions", JSONArray.class);
|
|
||||||
List<TransactionData> transactions = new ArrayList<>();
|
|
||||||
|
|
||||||
for (Object transactionObj : transactionsJson) {
|
|
||||||
if (!(transactionObj instanceof JSONObject)) {
|
|
||||||
LOGGER.error("Genesis transaction malformed in blockchain config file");
|
|
||||||
throw new RuntimeException("Genesis transaction malformed in blockchain config file");
|
|
||||||
}
|
|
||||||
|
|
||||||
JSONObject transactionJson = (JSONObject) transactionObj;
|
|
||||||
|
|
||||||
String recipient = (String) Settings.getTypedJson(transactionJson, "recipient", String.class);
|
|
||||||
BigDecimal amount = Settings.getJsonBigDecimal(transactionJson, "amount");
|
|
||||||
|
|
||||||
// assetId is optional
|
|
||||||
if (transactionJson.containsKey("assetId")) {
|
|
||||||
long assetId = (Long) Settings.getTypedJson(transactionJson, "assetId", Long.class);
|
|
||||||
|
|
||||||
// We're into version 4 genesis block territory now
|
|
||||||
version = 4;
|
|
||||||
|
|
||||||
transactions.add(new GenesisTransactionData(timestamp, recipient, amount, assetId));
|
|
||||||
} else {
|
|
||||||
transactions.add(new GenesisTransactionData(timestamp, recipient, amount));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Assets
|
transactionsData = Arrays.asList(info.transactions);
|
||||||
JSONArray assetsJson = (JSONArray) Settings.getTypedJson(json, "assets", JSONArray.class);
|
|
||||||
String genesisAddress = Crypto.toAddress(GenesisAccount.PUBLIC_KEY);
|
|
||||||
List<AssetData> assets = new ArrayList<>();
|
|
||||||
|
|
||||||
for (Object assetObj : assetsJson) {
|
// Add default values to transactions
|
||||||
if (!(assetObj instanceof JSONObject)) {
|
transactionsData.stream().forEach(transactionData -> {
|
||||||
LOGGER.error("Genesis asset malformed in blockchain config file");
|
if (transactionData.getFee() == null)
|
||||||
throw new RuntimeException("Genesis asset malformed in blockchain config file");
|
transactionData.setFee(BigDecimal.ZERO.setScale(8));
|
||||||
|
|
||||||
|
if (transactionData.getCreatorPublicKey() == null)
|
||||||
|
transactionData.setCreatorPublicKey(GenesisAccount.PUBLIC_KEY);
|
||||||
|
|
||||||
|
if (transactionData.getTimestamp() == 0)
|
||||||
|
transactionData.setTimestamp(info.timestamp);
|
||||||
|
});
|
||||||
|
|
||||||
|
// For version 1, extract any ISSUE_ASSET transactions into initialAssets and only allow GENESIS transactions
|
||||||
|
if (info.version == 1) {
|
||||||
|
List<TransactionData> issueAssetTransactions = transactionsData.stream()
|
||||||
|
.filter(transactionData -> transactionData.getType() == TransactionType.ISSUE_ASSET).collect(Collectors.toList());
|
||||||
|
transactionsData.removeAll(issueAssetTransactions);
|
||||||
|
|
||||||
|
// There should be only GENESIS transactions left;
|
||||||
|
if (transactionsData.stream().anyMatch(transactionData -> transactionData.getType() != TransactionType.GENESIS)) {
|
||||||
|
LOGGER.error("Version 1 genesis block only allowed to contain GENESIS transctions (after issue-asset processing)");
|
||||||
|
throw new RuntimeException("Version 1 genesis block only allowed to contain GENESIS transctions (after issue-asset processing)");
|
||||||
}
|
}
|
||||||
|
|
||||||
JSONObject assetJson = (JSONObject) assetObj;
|
// Convert ISSUE_ASSET transactions into initial assets
|
||||||
|
issueAssetTransactions.stream().map(transactionData -> {
|
||||||
|
IssueAssetTransactionData issueAssetTransactionData = (IssueAssetTransactionData) transactionData;
|
||||||
|
|
||||||
String name = (String) Settings.getTypedJson(assetJson, "name", String.class);
|
return new AssetData(issueAssetTransactionData.getOwner(), issueAssetTransactionData.getAssetName(), issueAssetTransactionData.getDescription(),
|
||||||
String description = (String) Settings.getTypedJson(assetJson, "description", String.class);
|
issueAssetTransactionData.getQuantity(), issueAssetTransactionData.getIsDivisible(), issueAssetTransactionData.getReference());
|
||||||
String reference58 = (String) Settings.getTypedJson(assetJson, "reference", String.class);
|
}).collect(Collectors.toList());
|
||||||
byte[] reference = Base58.decode(reference58);
|
|
||||||
long quantity = (Long) Settings.getTypedJson(assetJson, "quantity", Long.class);
|
|
||||||
boolean isDivisible = (Boolean) Settings.getTypedJson(assetJson, "isDivisible", Boolean.class);
|
|
||||||
|
|
||||||
assets.add(new AssetData(genesisAddress, name, description, quantity, isDivisible, reference));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Minor fix-up
|
||||||
|
info.generatingBalance.setScale(8);
|
||||||
|
|
||||||
byte[] reference = GENESIS_REFERENCE;
|
byte[] reference = GENESIS_REFERENCE;
|
||||||
int transactionCount = transactions.size();
|
int transactionCount = transactionsData.size();
|
||||||
BigDecimal totalFees = BigDecimal.ZERO.setScale(8);
|
BigDecimal totalFees = BigDecimal.ZERO.setScale(8);
|
||||||
byte[] generatorPublicKey = GENESIS_GENERATOR_PUBLIC_KEY;
|
byte[] generatorPublicKey = GenesisAccount.PUBLIC_KEY;
|
||||||
byte[] bytesForSignature = getBytesForSignature(version, reference, generatingBalance, generatorPublicKey);
|
byte[] bytesForSignature = getBytesForSignature(info.version, reference, info.generatingBalance, generatorPublicKey);
|
||||||
byte[] generatorSignature = calcSignature(bytesForSignature);
|
byte[] generatorSignature = calcSignature(bytesForSignature);
|
||||||
byte[] transactionsSignature = generatorSignature;
|
byte[] transactionsSignature = generatorSignature;
|
||||||
int height = 1;
|
int height = 1;
|
||||||
int atCount = 0;
|
int atCount = 0;
|
||||||
BigDecimal atFees = BigDecimal.ZERO.setScale(8);
|
BigDecimal atFees = BigDecimal.ZERO.setScale(8);
|
||||||
|
|
||||||
blockData = new BlockData(version, reference, transactionCount, totalFees, transactionsSignature, height, timestamp, generatingBalance,
|
blockData = new BlockData(info.version, reference, transactionCount, totalFees, transactionsSignature, height, info.timestamp, info.generatingBalance,
|
||||||
generatorPublicKey, generatorSignature, atCount, atFees);
|
generatorPublicKey, generatorSignature, atCount, atFees);
|
||||||
transactionsData = transactions;
|
|
||||||
initialAssets = assets;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// More information
|
// More information
|
||||||
@ -283,4 +277,45 @@ public class GenesisBlock extends Block {
|
|||||||
return ValidationResult.OK;
|
return ValidationResult.OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void process() throws DataException {
|
||||||
|
LOGGER.info(String.format("Using genesis block timestamp of %d", blockData.getTimestamp()));
|
||||||
|
|
||||||
|
// If we're a version 1 genesis block, create assets now
|
||||||
|
if (blockData.getVersion() == 1)
|
||||||
|
for (AssetData assetData : initialAssets)
|
||||||
|
repository.getAssetRepository().save(assetData);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Some transactions will be missing references and signatures,
|
||||||
|
* so we generate them by trial-processing transactions and using
|
||||||
|
* account's last-reference to fill in the gaps for reference,
|
||||||
|
* and a duplicated SHA256 digest for signature
|
||||||
|
*/
|
||||||
|
this.repository.setSavepoint();
|
||||||
|
try {
|
||||||
|
for (Transaction transaction : this.getTransactions()) {
|
||||||
|
TransactionData transactionData = transaction.getTransactionData();
|
||||||
|
Account creator = new PublicKeyAccount(this.repository, transactionData.getCreatorPublicKey());
|
||||||
|
|
||||||
|
if (transactionData.getReference() == null)
|
||||||
|
transactionData.setReference(creator.getLastReference());
|
||||||
|
if (transactionData.getSignature() == null) {
|
||||||
|
byte[] digest = Crypto.digest(TransactionTransformer.toBytesForSigning(transactionData));
|
||||||
|
byte[] signature = Bytes.concat(digest, digest);
|
||||||
|
|
||||||
|
transactionData.setSignature(signature);
|
||||||
|
}
|
||||||
|
|
||||||
|
transaction.process();
|
||||||
|
}
|
||||||
|
} catch (TransformationException e) {
|
||||||
|
throw new RuntimeException("Can't process genesis block transaction", e);
|
||||||
|
} finally {
|
||||||
|
this.repository.rollbackToSavepoint();
|
||||||
|
}
|
||||||
|
|
||||||
|
super.process();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,7 @@ import java.math.BigDecimal;
|
|||||||
import javax.xml.bind.annotation.XmlAccessType;
|
import javax.xml.bind.annotation.XmlAccessType;
|
||||||
import javax.xml.bind.annotation.XmlAccessorType;
|
import javax.xml.bind.annotation.XmlAccessorType;
|
||||||
|
|
||||||
// All properties to be converted to JSON via JAX-RS
|
// All properties to be converted to JSON via JAXB
|
||||||
@XmlAccessorType(XmlAccessType.FIELD)
|
@XmlAccessorType(XmlAccessType.FIELD)
|
||||||
public class PaymentData {
|
public class PaymentData {
|
||||||
|
|
||||||
@ -16,7 +16,7 @@ public class PaymentData {
|
|||||||
|
|
||||||
// Constructors
|
// Constructors
|
||||||
|
|
||||||
// For JAX-RS
|
// For JAXB
|
||||||
protected PaymentData() {
|
protected PaymentData() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,6 +6,7 @@ import javax.xml.bind.annotation.XmlAccessType;
|
|||||||
import javax.xml.bind.annotation.XmlAccessorType;
|
import javax.xml.bind.annotation.XmlAccessorType;
|
||||||
import javax.xml.bind.annotation.XmlElement;
|
import javax.xml.bind.annotation.XmlElement;
|
||||||
|
|
||||||
|
import org.eclipse.persistence.oxm.annotations.XmlDiscriminatorValue;
|
||||||
import org.qora.group.Group.ApprovalThreshold;
|
import org.qora.group.Group.ApprovalThreshold;
|
||||||
import org.qora.transaction.Transaction.TransactionType;
|
import org.qora.transaction.Transaction.TransactionType;
|
||||||
|
|
||||||
@ -19,6 +20,8 @@ import io.swagger.v3.oas.annotations.media.Schema.AccessMode;
|
|||||||
TransactionData.class
|
TransactionData.class
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
//JAXB: use this subclass if XmlDiscriminatorNode matches XmlDiscriminatorValue below:
|
||||||
|
@XmlDiscriminatorValue("CREATE_GROUP")
|
||||||
public class CreateGroupTransactionData extends TransactionData {
|
public class CreateGroupTransactionData extends TransactionData {
|
||||||
|
|
||||||
// Properties
|
// Properties
|
||||||
|
@ -5,6 +5,7 @@ import java.math.BigDecimal;
|
|||||||
import javax.xml.bind.annotation.XmlAccessType;
|
import javax.xml.bind.annotation.XmlAccessType;
|
||||||
import javax.xml.bind.annotation.XmlAccessorType;
|
import javax.xml.bind.annotation.XmlAccessorType;
|
||||||
|
|
||||||
|
import org.eclipse.persistence.oxm.annotations.XmlDiscriminatorValue;
|
||||||
import org.qora.account.GenesisAccount;
|
import org.qora.account.GenesisAccount;
|
||||||
import org.qora.asset.Asset;
|
import org.qora.asset.Asset;
|
||||||
import org.qora.transaction.Transaction.TransactionType;
|
import org.qora.transaction.Transaction.TransactionType;
|
||||||
@ -18,6 +19,8 @@ import io.swagger.v3.oas.annotations.media.Schema;
|
|||||||
TransactionData.class
|
TransactionData.class
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
//JAXB: use this subclass if XmlDiscriminatorNode matches XmlDiscriminatorValue below:
|
||||||
|
@XmlDiscriminatorValue("GENESIS")
|
||||||
public class GenesisTransactionData extends TransactionData {
|
public class GenesisTransactionData extends TransactionData {
|
||||||
|
|
||||||
// Properties
|
// Properties
|
||||||
|
@ -6,6 +6,9 @@ import javax.xml.bind.Unmarshaller;
|
|||||||
import javax.xml.bind.annotation.XmlAccessType;
|
import javax.xml.bind.annotation.XmlAccessType;
|
||||||
import javax.xml.bind.annotation.XmlAccessorType;
|
import javax.xml.bind.annotation.XmlAccessorType;
|
||||||
|
|
||||||
|
import org.eclipse.persistence.oxm.annotations.XmlDiscriminatorValue;
|
||||||
|
import org.qora.account.GenesisAccount;
|
||||||
|
import org.qora.block.GenesisBlock;
|
||||||
import org.qora.transaction.Transaction.TransactionType;
|
import org.qora.transaction.Transaction.TransactionType;
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
@ -14,6 +17,8 @@ import io.swagger.v3.oas.annotations.media.Schema.AccessMode;
|
|||||||
// All properties to be converted to JSON via JAXB
|
// All properties to be converted to JSON via JAXB
|
||||||
@XmlAccessorType(XmlAccessType.FIELD)
|
@XmlAccessorType(XmlAccessType.FIELD)
|
||||||
@Schema(allOf = { TransactionData.class })
|
@Schema(allOf = { TransactionData.class })
|
||||||
|
// JAXB: use this subclass if XmlDiscriminatorNode matches XmlDiscriminatorValue below:
|
||||||
|
@XmlDiscriminatorValue("ISSUE_ASSET")
|
||||||
public class IssueAssetTransactionData extends TransactionData {
|
public class IssueAssetTransactionData extends TransactionData {
|
||||||
|
|
||||||
// Properties
|
// Properties
|
||||||
@ -41,6 +46,9 @@ public class IssueAssetTransactionData extends TransactionData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void afterUnmarshal(Unmarshaller u, Object parent) {
|
public void afterUnmarshal(Unmarshaller u, Object parent) {
|
||||||
|
if (parent instanceof GenesisBlock.GenesisInfo && this.issuerPublicKey == null)
|
||||||
|
this.issuerPublicKey = GenesisAccount.PUBLIC_KEY;
|
||||||
|
|
||||||
this.creatorPublicKey = this.issuerPublicKey;
|
this.creatorPublicKey = this.issuerPublicKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,6 +10,7 @@ import javax.xml.bind.annotation.XmlElement;
|
|||||||
import javax.xml.bind.annotation.XmlSeeAlso;
|
import javax.xml.bind.annotation.XmlSeeAlso;
|
||||||
import javax.xml.bind.annotation.XmlTransient;
|
import javax.xml.bind.annotation.XmlTransient;
|
||||||
|
|
||||||
|
import org.eclipse.persistence.oxm.annotations.XmlDiscriminatorNode;
|
||||||
import org.qora.crypto.Crypto;
|
import org.qora.crypto.Crypto;
|
||||||
import org.qora.transaction.Transaction.TransactionType;
|
import org.qora.transaction.Transaction.TransactionType;
|
||||||
|
|
||||||
@ -39,6 +40,8 @@ import io.swagger.v3.oas.annotations.media.Schema.AccessMode;
|
|||||||
})
|
})
|
||||||
//All properties to be converted to JSON via JAXB
|
//All properties to be converted to JSON via JAXB
|
||||||
@XmlAccessorType(XmlAccessType.FIELD)
|
@XmlAccessorType(XmlAccessType.FIELD)
|
||||||
|
// EclipseLink JAXB (MOXy) specific: use "type" field to determine subclass
|
||||||
|
@XmlDiscriminatorNode("type")
|
||||||
public abstract class TransactionData {
|
public abstract class TransactionData {
|
||||||
|
|
||||||
// Properties shared with all transaction types
|
// Properties shared with all transaction types
|
||||||
@ -97,6 +100,10 @@ public abstract class TransactionData {
|
|||||||
return this.timestamp;
|
return this.timestamp;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setTimestamp(long timestamp) {
|
||||||
|
this.timestamp = timestamp;
|
||||||
|
}
|
||||||
|
|
||||||
public int getTxGroupId() {
|
public int getTxGroupId() {
|
||||||
return this.txGroupId;
|
return this.txGroupId;
|
||||||
}
|
}
|
||||||
@ -105,6 +112,10 @@ public abstract class TransactionData {
|
|||||||
return this.reference;
|
return this.reference;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setReference(byte[] reference) {
|
||||||
|
this.reference = reference;
|
||||||
|
}
|
||||||
|
|
||||||
public byte[] getCreatorPublicKey() {
|
public byte[] getCreatorPublicKey() {
|
||||||
return this.creatorPublicKey;
|
return this.creatorPublicKey;
|
||||||
}
|
}
|
||||||
@ -118,6 +129,10 @@ public abstract class TransactionData {
|
|||||||
return this.fee;
|
return this.fee;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setFee(BigDecimal fee) {
|
||||||
|
this.fee = fee;
|
||||||
|
}
|
||||||
|
|
||||||
public byte[] getSignature() {
|
public byte[] getSignature() {
|
||||||
return this.signature;
|
return this.signature;
|
||||||
}
|
}
|
||||||
|
@ -1,233 +1,197 @@
|
|||||||
package org.qora.settings;
|
package org.qora.settings;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
|
import java.io.FileReader;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.math.BigDecimal;
|
import java.io.Reader;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Arrays;
|
import javax.xml.bind.JAXBContext;
|
||||||
import java.util.List;
|
import javax.xml.bind.JAXBException;
|
||||||
|
import javax.xml.bind.Unmarshaller;
|
||||||
|
import javax.xml.bind.annotation.XmlAccessType;
|
||||||
|
import javax.xml.bind.annotation.XmlAccessorType;
|
||||||
|
import javax.xml.transform.stream.StreamSource;
|
||||||
|
|
||||||
import org.apache.logging.log4j.LogManager;
|
import org.apache.logging.log4j.LogManager;
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
|
import org.eclipse.persistence.jaxb.JAXBContextFactory;
|
||||||
import org.json.simple.JSONArray;
|
import org.eclipse.persistence.jaxb.UnmarshallerProperties;
|
||||||
import org.json.simple.JSONObject;
|
|
||||||
import org.json.simple.JSONValue;
|
|
||||||
import org.qora.block.BlockChain;
|
import org.qora.block.BlockChain;
|
||||||
|
|
||||||
import com.google.common.base.Charsets;
|
// All properties to be converted to JSON via JAXB
|
||||||
import com.google.common.io.Files;
|
@XmlAccessorType(XmlAccessType.FIELD)
|
||||||
|
|
||||||
public class Settings {
|
public class Settings {
|
||||||
|
|
||||||
|
public static final int DEFAULT_LISTEN_PORT = 9084;
|
||||||
|
|
||||||
private static final Logger LOGGER = LogManager.getLogger(Settings.class);
|
private static final Logger LOGGER = LogManager.getLogger(Settings.class);
|
||||||
|
private static final String SETTINGS_FILENAME = "settings.json";
|
||||||
|
|
||||||
// Properties
|
// Properties
|
||||||
private static Settings instance;
|
private static Settings instance;
|
||||||
private String userpath = "";
|
|
||||||
private boolean useBitcoinTestNet = false;
|
// Settings, and other config files
|
||||||
|
private String userPath;
|
||||||
|
|
||||||
|
// API-related
|
||||||
|
private boolean apiEnabled = true;
|
||||||
|
private int apiPort = 9085;
|
||||||
|
private String[] apiWhitelist = new String[] {
|
||||||
|
"::1", "127.0.0.1"
|
||||||
|
};
|
||||||
|
private Boolean apiRestricted;
|
||||||
|
|
||||||
|
// Specific to this node
|
||||||
private boolean wipeUnconfirmedOnStart = false;
|
private boolean wipeUnconfirmedOnStart = false;
|
||||||
private Boolean restrictedApi;
|
|
||||||
private String blockchainConfigPath = "blockchain.json";
|
|
||||||
/** Maximum number of unconfirmed transactions allowed per account */
|
/** Maximum number of unconfirmed transactions allowed per account */
|
||||||
private int maxUnconfirmedPerAccount = 100;
|
private int maxUnconfirmedPerAccount = 100;
|
||||||
/** Max milliseconds into future for accepting new, unconfirmed transactions */
|
/** Max milliseconds into future for accepting new, unconfirmed transactions */
|
||||||
private long maxTransactionTimestampFuture = 24 * 60 * 60 * 1000; // milliseconds
|
private int maxTransactionTimestampFuture = 24 * 60 * 60 * 1000; // milliseconds
|
||||||
|
|
||||||
// API
|
// Peer-to-peer related
|
||||||
private int apiPort = 9085;
|
|
||||||
private List<String> apiAllowed = new ArrayList<String>(Arrays.asList("127.0.0.1", "::1")); // ipv4, ipv6
|
|
||||||
private boolean apiEnabled = true;
|
|
||||||
|
|
||||||
// Peer-to-peer networking
|
|
||||||
public static final int DEFAULT_LISTEN_PORT = 9084;
|
|
||||||
private int listenPort = DEFAULT_LISTEN_PORT;
|
private int listenPort = DEFAULT_LISTEN_PORT;
|
||||||
private String bindAddress = null; // listen on all local addresses
|
private String bindAddress = null; // listen on all local addresses
|
||||||
private int minPeers = 3;
|
private int minPeers = 3;
|
||||||
private int maxPeers = 10;
|
private int maxPeers = 10;
|
||||||
|
|
||||||
// Constants
|
// Which blockchains this node is running
|
||||||
private static final String SETTINGS_FILENAME = "settings.json";
|
private String blockchainConfig = "blockchain.json";
|
||||||
|
private boolean useBitcoinTestNet = false;
|
||||||
|
|
||||||
// Constructors
|
// Constructors
|
||||||
|
|
||||||
private Settings() {
|
private Settings() {
|
||||||
}
|
}
|
||||||
|
|
||||||
private Settings(String filename) {
|
|
||||||
// Read from file
|
|
||||||
String path = "";
|
|
||||||
|
|
||||||
try {
|
|
||||||
do {
|
|
||||||
File file = new File(path + filename);
|
|
||||||
|
|
||||||
if (!file.exists()) {
|
|
||||||
// log lack of settings file
|
|
||||||
LOGGER.info("Settings file not found: " + path + filename);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
LOGGER.info("Using settings file: " + path + filename);
|
|
||||||
List<String> lines = Files.readLines(file, Charsets.UTF_8);
|
|
||||||
|
|
||||||
// Concatenate lines for JSON parsing
|
|
||||||
String jsonString = "";
|
|
||||||
for (String line : lines) {
|
|
||||||
// Escape single backslashes in "userpath" entries, typically Windows-style paths
|
|
||||||
if (line.contains("userpath"))
|
|
||||||
line.replace("\\", "\\\\");
|
|
||||||
|
|
||||||
jsonString += line;
|
|
||||||
}
|
|
||||||
|
|
||||||
JSONObject settingsJSON = (JSONObject) JSONValue.parse(jsonString);
|
|
||||||
|
|
||||||
String userpath = (String) settingsJSON.get("userpath");
|
|
||||||
if (userpath != null) {
|
|
||||||
path = userpath;
|
|
||||||
|
|
||||||
// Add trailing directory separator if needed
|
|
||||||
if (!path.endsWith(File.separator))
|
|
||||||
path += File.separator;
|
|
||||||
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.userpath = path;
|
|
||||||
process(settingsJSON);
|
|
||||||
|
|
||||||
break;
|
|
||||||
} while (true);
|
|
||||||
} catch (IOException | ClassCastException e) {
|
|
||||||
LOGGER.error("Unable to parse settings file: " + path + filename);
|
|
||||||
throw new RuntimeException("Unable to parse settings file", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Other methods
|
// Other methods
|
||||||
|
|
||||||
public static synchronized Settings getInstance() {
|
public static synchronized Settings getInstance() {
|
||||||
if (instance == null)
|
if (instance == null)
|
||||||
instance = new Settings(SETTINGS_FILENAME);
|
fileInstance(SETTINGS_FILENAME);
|
||||||
|
|
||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void test(JSONObject settingsJSON) {
|
public static void fileInstance(String filename) {
|
||||||
// Discard previous settings
|
JAXBContext jc;
|
||||||
if (instance != null)
|
Unmarshaller unmarshaller;
|
||||||
instance = null;
|
|
||||||
|
|
||||||
instance = new Settings();
|
|
||||||
getInstance().process(settingsJSON);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void process(JSONObject json) {
|
|
||||||
// API
|
|
||||||
if (json.containsKey("apiPort"))
|
|
||||||
this.apiPort = ((Long) json.get("apiPort")).intValue();
|
|
||||||
|
|
||||||
if (json.containsKey("apiAllowed")) {
|
|
||||||
JSONArray allowedArray = (JSONArray) json.get("apiAllowed");
|
|
||||||
|
|
||||||
this.apiAllowed = new ArrayList<String>();
|
|
||||||
|
|
||||||
for (Object entry : allowedArray) {
|
|
||||||
if (!(entry instanceof String))
|
|
||||||
throw new RuntimeException("Entry inside 'apiAllowed' is not string");
|
|
||||||
|
|
||||||
this.apiAllowed.add((String) entry);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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"))
|
|
||||||
this.listenPort = ((Long) getTypedJson(json, "listenPort", Long.class)).intValue();
|
|
||||||
|
|
||||||
if (json.containsKey("bindAddress"))
|
|
||||||
this.bindAddress = (String) getTypedJson(json, "bindAddress", String.class);
|
|
||||||
|
|
||||||
if (json.containsKey("minPeers"))
|
|
||||||
this.minPeers = ((Long) getTypedJson(json, "minPeers", Long.class)).intValue();
|
|
||||||
|
|
||||||
if (json.containsKey("maxPeers"))
|
|
||||||
this.maxPeers = ((Long) getTypedJson(json, "maxPeers", Long.class)).intValue();
|
|
||||||
|
|
||||||
// Node-specific behaviour
|
|
||||||
|
|
||||||
if (json.containsKey("wipeUnconfirmedOnStart"))
|
|
||||||
this.wipeUnconfirmedOnStart = (Boolean) getTypedJson(json, "wipeUnconfirmedOnStart", Boolean.class);
|
|
||||||
|
|
||||||
if (json.containsKey("maxUnconfirmedPerAccount"))
|
|
||||||
this.maxUnconfirmedPerAccount = ((Long) getTypedJson(json, "maxUnconfirmedPerAccount", Long.class)).intValue();
|
|
||||||
|
|
||||||
if (json.containsKey("maxTransactionTimestampFuture"))
|
|
||||||
this.maxTransactionTimestampFuture = (Long) getTypedJson(json, "maxTransactionTimestampFuture", Long.class);
|
|
||||||
|
|
||||||
// Blockchain config
|
|
||||||
|
|
||||||
if (json.containsKey("blockchainConfig"))
|
|
||||||
blockchainConfigPath = (String) getTypedJson(json, "blockchainConfig", String.class);
|
|
||||||
|
|
||||||
File file = new File(this.userpath + blockchainConfigPath);
|
|
||||||
|
|
||||||
if (!file.exists()) {
|
|
||||||
LOGGER.info("Blockchain config file not found: " + this.userpath + blockchainConfigPath);
|
|
||||||
throw new RuntimeException("Unable to read blockchain config file");
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
List<String> lines = Files.readLines(file, Charsets.UTF_8);
|
// Create JAXB context aware of Settings
|
||||||
JSONObject blockchainJSON = (JSONObject) JSONValue.parse(String.join("\n", lines));
|
jc = JAXBContextFactory.createContext(new Class[] {
|
||||||
BlockChain.fromJSON(blockchainJSON);
|
Settings.class
|
||||||
} catch (IOException e) {
|
}, null);
|
||||||
LOGGER.error("Unable to parse blockchain config file: " + this.userpath + blockchainConfigPath);
|
|
||||||
throw new RuntimeException("Unable to parse blockchain config file", e);
|
// Create unmarshaller
|
||||||
|
unmarshaller = jc.createUnmarshaller();
|
||||||
|
|
||||||
|
// Set the unmarshaller media type to JSON
|
||||||
|
unmarshaller.setProperty(UnmarshallerProperties.MEDIA_TYPE, "application/json");
|
||||||
|
|
||||||
|
// Tell unmarshaller that there's no JSON root element in the JSON input
|
||||||
|
unmarshaller.setProperty(UnmarshallerProperties.JSON_INCLUDE_ROOT, false);
|
||||||
|
|
||||||
|
} catch (JAXBException e) {
|
||||||
|
LOGGER.error("Unable to process settings file", e);
|
||||||
|
throw new RuntimeException("Unable to process settings file", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Settings settings = null;
|
||||||
|
String path = "";
|
||||||
|
|
||||||
|
do {
|
||||||
|
LOGGER.info("Using settings file: " + path + filename);
|
||||||
|
|
||||||
|
// Create the StreamSource by creating Reader to the JSON input
|
||||||
|
try (Reader settingsReader = new FileReader(filename)) {
|
||||||
|
StreamSource json = new StreamSource(settingsReader);
|
||||||
|
|
||||||
|
// Attempt to unmarshal JSON stream to Settings
|
||||||
|
settings = unmarshaller.unmarshal(json, Settings.class).getValue();
|
||||||
|
} catch (FileNotFoundException e) {
|
||||||
|
LOGGER.error("Settings file not found: " + path + filename);
|
||||||
|
throw new RuntimeException("Settings file not found: " + path + filename);
|
||||||
|
} catch (JAXBException e) {
|
||||||
|
LOGGER.error("Unable to process settings file", e);
|
||||||
|
throw new RuntimeException("Unable to process settings file", e);
|
||||||
|
} catch (IOException e) {
|
||||||
|
LOGGER.error("Unable to process settings file", e);
|
||||||
|
throw new RuntimeException("Unable to process settings file", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (settings.userPath != null) {
|
||||||
|
// Adjust filename and go round again
|
||||||
|
path = settings.userPath;
|
||||||
|
|
||||||
|
// Add trailing directory separator if needed
|
||||||
|
if (!path.endsWith(File.separator))
|
||||||
|
path += File.separator;
|
||||||
|
|
||||||
|
}
|
||||||
|
} while (settings.userPath != null);
|
||||||
|
|
||||||
|
// Validate settings
|
||||||
|
settings.validate();
|
||||||
|
|
||||||
|
// Minor fix-up
|
||||||
|
if (settings.userPath == null)
|
||||||
|
settings.userPath = "";
|
||||||
|
|
||||||
|
// Successfully read settings now in effect
|
||||||
|
instance = settings;
|
||||||
|
|
||||||
|
// Now read blockchain config
|
||||||
|
BlockChain.fileInstance(settings.getUserPath() + settings.getBlockchainConfig());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void validate() {
|
||||||
|
// Validation goes here
|
||||||
}
|
}
|
||||||
|
|
||||||
// Getters / setters
|
// Getters / setters
|
||||||
|
|
||||||
public String getUserpath() {
|
public String getUserPath() {
|
||||||
return this.userpath;
|
return this.userPath;
|
||||||
}
|
|
||||||
|
|
||||||
public int getApiPort() {
|
|
||||||
return this.apiPort;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<String> getApiAllowed() {
|
|
||||||
return this.apiAllowed;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isApiEnabled() {
|
public boolean isApiEnabled() {
|
||||||
return this.apiEnabled;
|
return this.apiEnabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isRestrictedApi() {
|
public int getApiPort() {
|
||||||
if (this.restrictedApi != null)
|
return this.apiPort;
|
||||||
return this.restrictedApi;
|
}
|
||||||
|
|
||||||
|
public String[] getApiWhitelist() {
|
||||||
|
return this.apiWhitelist;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isApiRestricted() {
|
||||||
|
// Explicitly set value takes precedence
|
||||||
|
if (this.apiRestricted != null)
|
||||||
|
return this.apiRestricted;
|
||||||
|
|
||||||
// Not set in config file, so restrict if not testnet
|
// Not set in config file, so restrict if not testnet
|
||||||
return !BlockChain.getInstance().getIsTestNet();
|
return !BlockChain.getInstance().isTestNet();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean getWipeUnconfirmedOnStart() {
|
||||||
|
return this.wipeUnconfirmedOnStart;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getMaxUnconfirmedPerAccount() {
|
||||||
|
return this.maxUnconfirmedPerAccount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getMaxTransactionTimestampFuture() {
|
||||||
|
return this.maxTransactionTimestampFuture;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getListenPort() {
|
public int getListenPort() {
|
||||||
return this.listenPort;
|
return this.listenPort;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getDefaultListenPort() {
|
|
||||||
return DEFAULT_LISTEN_PORT;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getBindAddress() {
|
public String getBindAddress() {
|
||||||
return this.bindAddress;
|
return this.bindAddress;
|
||||||
}
|
}
|
||||||
@ -240,55 +204,12 @@ public class Settings {
|
|||||||
return this.maxPeers;
|
return this.maxPeers;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getBlockchainConfig() {
|
||||||
|
return this.blockchainConfig;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean useBitcoinTestNet() {
|
public boolean useBitcoinTestNet() {
|
||||||
return this.useBitcoinTestNet;
|
return this.useBitcoinTestNet;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean getWipeUnconfirmedOnStart() {
|
|
||||||
return this.wipeUnconfirmedOnStart;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getMaxUnconfirmedPerAccount() {
|
|
||||||
return this.maxUnconfirmedPerAccount;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getMaxTransactionTimestampFuture() {
|
|
||||||
return this.maxTransactionTimestampFuture;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Config parsing
|
|
||||||
|
|
||||||
public static Object getTypedJson(JSONObject json, String key, Class<?> clazz) {
|
|
||||||
if (!json.containsKey(key)) {
|
|
||||||
LOGGER.error("Missing \"" + key + "\" in blockchain config file");
|
|
||||||
throw new RuntimeException("Missing \"" + key + "\" in blockchain config file");
|
|
||||||
}
|
|
||||||
|
|
||||||
Object value = json.get(key);
|
|
||||||
if (!clazz.isInstance(value)) {
|
|
||||||
LOGGER.error("\"" + key + "\" not " + clazz.getSimpleName() + " in blockchain config file");
|
|
||||||
throw new RuntimeException("\"" + key + "\" not " + clazz.getSimpleName() + " in blockchain config file");
|
|
||||||
}
|
|
||||||
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static BigDecimal getJsonBigDecimal(JSONObject json, String key) {
|
|
||||||
try {
|
|
||||||
return new BigDecimal((String) getTypedJson(json, key, String.class));
|
|
||||||
} catch (NumberFormatException e) {
|
|
||||||
LOGGER.error("Unable to parse \"" + key + "\" in blockchain config file");
|
|
||||||
throw new RuntimeException("Unable to parse \"" + key + "\" in blockchain config file");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Long getJsonQuotedLong(JSONObject json, String key) {
|
|
||||||
try {
|
|
||||||
return Long.parseLong((String) getTypedJson(json, key, String.class));
|
|
||||||
} catch (NumberFormatException e) {
|
|
||||||
LOGGER.error("Unable to parse \"" + key + "\" in blockchain config file");
|
|
||||||
throw new RuntimeException("Unable to parse \"" + key + "\" in blockchain config file");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -149,7 +149,7 @@ public class ArbitraryTransaction extends Transaction {
|
|||||||
Account sender = this.getSender();
|
Account sender = this.getSender();
|
||||||
int blockHeight = this.repository.getBlockRepository().getBlockchainHeight();
|
int blockHeight = this.repository.getBlockRepository().getBlockchainHeight();
|
||||||
|
|
||||||
String senderPathname = Settings.getInstance().getUserpath() + "arbitrary" + File.separator + sender.getAddress();
|
String senderPathname = Settings.getInstance().getUserPath() + "arbitrary" + File.separator + sender.getAddress();
|
||||||
String blockPathname = senderPathname + File.separator + blockHeight;
|
String blockPathname = senderPathname + File.separator + blockHeight;
|
||||||
String dataPathname = blockPathname + File.separator + Base58.encode(arbitraryTransactionData.getSignature()) + "-"
|
String dataPathname = blockPathname + File.separator + Base58.encode(arbitraryTransactionData.getSignature()) + "-"
|
||||||
+ arbitraryTransactionData.getService() + ".raw";
|
+ arbitraryTransactionData.getService() + ".raw";
|
||||||
@ -187,7 +187,7 @@ public class ArbitraryTransaction extends Transaction {
|
|||||||
Account sender = this.getSender();
|
Account sender = this.getSender();
|
||||||
int blockHeight = this.repository.getBlockRepository().getBlockchainHeight();
|
int blockHeight = this.repository.getBlockRepository().getBlockchainHeight();
|
||||||
|
|
||||||
String senderPathname = Settings.getInstance().getUserpath() + "arbitrary" + File.separator + sender.getAddress();
|
String senderPathname = Settings.getInstance().getUserPath() + "arbitrary" + File.separator + sender.getAddress();
|
||||||
String blockPathname = senderPathname + File.separator + blockHeight;
|
String blockPathname = senderPathname + File.separator + blockHeight;
|
||||||
String dataPathname = blockPathname + File.separator + Base58.encode(arbitraryTransactionData.getSignature()) + "-"
|
String dataPathname = blockPathname + File.separator + Base58.encode(arbitraryTransactionData.getSignature()) + "-"
|
||||||
+ arbitraryTransactionData.getService() + ".raw";
|
+ arbitraryTransactionData.getService() + ".raw";
|
||||||
|
@ -184,6 +184,7 @@ public abstract class Transaction {
|
|||||||
INVALID_GROUP_ID(64),
|
INVALID_GROUP_ID(64),
|
||||||
TRANSACTION_UNKNOWN(65),
|
TRANSACTION_UNKNOWN(65),
|
||||||
TRANSACTION_ALREADY_CONFIRMED(66),
|
TRANSACTION_ALREADY_CONFIRMED(66),
|
||||||
|
INVALID_TX_GROUP_ID(67),
|
||||||
NOT_YET_RELEASED(1000);
|
NOT_YET_RELEASED(1000);
|
||||||
|
|
||||||
public final int value;
|
public final int value;
|
||||||
@ -480,7 +481,7 @@ public abstract class Transaction {
|
|||||||
|
|
||||||
// Check transaction's txGroupId
|
// Check transaction's txGroupId
|
||||||
if (!this.isValidTxGroupId())
|
if (!this.isValidTxGroupId())
|
||||||
return ValidationResult.INVALID_GROUP_ID;
|
return ValidationResult.INVALID_TX_GROUP_ID;
|
||||||
|
|
||||||
creator.setLastReference(creator.getUnconfirmedLastReference());
|
creator.setLastReference(creator.getUnconfirmedLastReference());
|
||||||
ValidationResult result = this.isValid();
|
ValidationResult result = this.isValid();
|
||||||
@ -498,19 +499,19 @@ public abstract class Transaction {
|
|||||||
private boolean isValidTxGroupId() throws DataException {
|
private boolean isValidTxGroupId() throws DataException {
|
||||||
int txGroupId = this.transactionData.getTxGroupId();
|
int txGroupId = this.transactionData.getTxGroupId();
|
||||||
|
|
||||||
|
// If transaction type doesn't need approval then we insist on NO_GROUP
|
||||||
|
if (!this.transactionData.getType().needsApproval && txGroupId != Group.NO_GROUP)
|
||||||
|
return false;
|
||||||
|
|
||||||
// Handling NO_GROUP
|
// Handling NO_GROUP
|
||||||
if (txGroupId == Group.NO_GROUP)
|
if (txGroupId == Group.NO_GROUP)
|
||||||
// true if NO_GROUP allowed, false otherwise
|
// true if NO_GROUP txGroupId is allowed for approval-needing tx types
|
||||||
return BlockChain.getInstance().getGrouplessAllowed();
|
return !BlockChain.getInstance().getRequireGroupForApproval();
|
||||||
|
|
||||||
// Group even exist?
|
// Group even exist?
|
||||||
if (!this.repository.getGroupRepository().groupExists(txGroupId))
|
if (!this.repository.getGroupRepository().groupExists(txGroupId))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
// Does this transaction type bypass approval?
|
|
||||||
if (!this.transactionData.getType().needsApproval)
|
|
||||||
return true;
|
|
||||||
|
|
||||||
GroupRepository groupRepository = this.repository.getGroupRepository();
|
GroupRepository groupRepository = this.repository.getGroupRepository();
|
||||||
|
|
||||||
// Is transaction's creator is group member?
|
// Is transaction's creator is group member?
|
||||||
@ -642,6 +643,9 @@ public abstract class Transaction {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns whether transaction needs to go through group-admin approval.
|
* Returns whether transaction needs to go through group-admin approval.
|
||||||
|
* <p>
|
||||||
|
* This test is more than simply "does this transaction type need approval?"
|
||||||
|
* because group admins bypass approval for transactions attached to their group.
|
||||||
*
|
*
|
||||||
* @throws DataException
|
* @throws DataException
|
||||||
*/
|
*/
|
||||||
@ -659,7 +663,7 @@ public abstract class Transaction {
|
|||||||
|
|
||||||
if (!groupRepository.groupExists(txGroupId))
|
if (!groupRepository.groupExists(txGroupId))
|
||||||
// Group no longer exists? Possibly due to blockchain orphaning undoing group creation?
|
// Group no longer exists? Possibly due to blockchain orphaning undoing group creation?
|
||||||
return true;
|
return true; // stops tx being included in block but it will eventually expire
|
||||||
|
|
||||||
// If transaction's creator is group admin then auto-approve
|
// If transaction's creator is group admin then auto-approve
|
||||||
PublicKeyAccount creator = this.getCreator();
|
PublicKeyAccount creator = this.getCreator();
|
||||||
@ -678,7 +682,8 @@ public abstract class Transaction {
|
|||||||
// Is transaction is outside of min/max approval period?
|
// Is transaction is outside of min/max approval period?
|
||||||
int creationBlockHeight = this.repository.getBlockRepository().getHeightFromTimestamp(this.transactionData.getTimestamp());
|
int creationBlockHeight = this.repository.getBlockRepository().getHeightFromTimestamp(this.transactionData.getTimestamp());
|
||||||
int currentBlockHeight = this.repository.getBlockRepository().getBlockchainHeight();
|
int currentBlockHeight = this.repository.getBlockRepository().getBlockchainHeight();
|
||||||
if (currentBlockHeight < creationBlockHeight + groupData.getMinimumBlockDelay() || currentBlockHeight > creationBlockHeight + groupData.getMaximumBlockDelay())
|
if (currentBlockHeight < creationBlockHeight + groupData.getMinimumBlockDelay()
|
||||||
|
|| currentBlockHeight > creationBlockHeight + groupData.getMaximumBlockDelay())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
return group.getGroupData().getApprovalThreshold().meetsApprovalThreshold(repository, txGroupId, this.transactionData.getSignature());
|
return group.getGroupData().getApprovalThreshold().meetsApprovalThreshold(repository, txGroupId, this.transactionData.getSignature());
|
||||||
|
53
src/main/java/org/qora/utils/StringLongMapXmlAdapter.java
Normal file
53
src/main/java/org/qora/utils/StringLongMapXmlAdapter.java
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
package org.qora.utils;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Map.Entry;
|
||||||
|
|
||||||
|
import javax.xml.bind.annotation.XmlTransient;
|
||||||
|
import javax.xml.bind.annotation.XmlValue;
|
||||||
|
import javax.xml.bind.annotation.adapters.XmlAdapter;
|
||||||
|
|
||||||
|
import org.eclipse.persistence.oxm.annotations.XmlVariableNode;
|
||||||
|
|
||||||
|
public class StringLongMapXmlAdapter extends XmlAdapter<StringLongMapXmlAdapter.StringLongMap, Map<String, Long>> {
|
||||||
|
|
||||||
|
public static class StringLongMap {
|
||||||
|
@XmlVariableNode("key")
|
||||||
|
List<MapEntry> entries = new ArrayList<MapEntry>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class MapEntry {
|
||||||
|
@XmlTransient
|
||||||
|
public String key;
|
||||||
|
|
||||||
|
@XmlValue
|
||||||
|
public Long value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, Long> unmarshal(StringLongMap stringLongMap) throws Exception {
|
||||||
|
Map<String, Long> map = new HashMap<>(stringLongMap.entries.size());
|
||||||
|
|
||||||
|
for (MapEntry entry : stringLongMap.entries)
|
||||||
|
map.put(entry.key, entry.value);
|
||||||
|
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public StringLongMap marshal(Map<String, Long> map) throws Exception {
|
||||||
|
StringLongMap output = new StringLongMap();
|
||||||
|
|
||||||
|
for (Entry<String, Long> entry : map.entrySet()) {
|
||||||
|
MapEntry mapEntry = new MapEntry();
|
||||||
|
mapEntry.key = entry.getKey();
|
||||||
|
mapEntry.value = entry.getValue();
|
||||||
|
output.entries.add(mapEntry);
|
||||||
|
}
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,6 @@
|
|||||||
package org.qora.test;
|
package org.qora.test;
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.Test;
|
||||||
import org.qora.asset.Asset;
|
import org.qora.asset.Asset;
|
||||||
import org.qora.data.at.ATStateData;
|
import org.qora.data.at.ATStateData;
|
||||||
import org.qora.data.block.BlockData;
|
import org.qora.data.block.BlockData;
|
||||||
@ -14,7 +14,7 @@ import org.qora.transaction.DeployAtTransaction;
|
|||||||
import org.qora.transform.TransformationException;
|
import org.qora.transform.TransformationException;
|
||||||
import org.qora.utils.Base58;
|
import org.qora.utils.Base58;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.*;
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
@ -3,7 +3,7 @@ package org.qora.test;
|
|||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.Test;
|
||||||
import org.qora.block.Block;
|
import org.qora.block.Block;
|
||||||
import org.qora.block.GenesisBlock;
|
import org.qora.block.GenesisBlock;
|
||||||
import org.qora.data.block.BlockData;
|
import org.qora.data.block.BlockData;
|
||||||
@ -15,7 +15,7 @@ import org.qora.transaction.Transaction;
|
|||||||
import org.qora.transform.TransformationException;
|
import org.qora.transform.TransformationException;
|
||||||
import org.qora.transform.block.BlockTransformer;
|
import org.qora.transform.block.BlockTransformer;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.*;
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
public class BlockTests extends Common {
|
public class BlockTests extends Common {
|
||||||
|
|
||||||
@ -66,7 +66,7 @@ public class BlockTests extends Common {
|
|||||||
// Block 949 has lots of varied transactions
|
// Block 949 has lots of varied transactions
|
||||||
// Blocks 390 & 754 have only payment transactions
|
// Blocks 390 & 754 have only payment transactions
|
||||||
BlockData blockData = repository.getBlockRepository().fromHeight(754);
|
BlockData blockData = repository.getBlockRepository().fromHeight(754);
|
||||||
assertNotNull(blockData, "Block 754 is required for this test");
|
assertNotNull("Block 754 is required for this test", blockData);
|
||||||
|
|
||||||
Block block = new Block(repository, blockData);
|
Block block = new Block(repository, blockData);
|
||||||
assertTrue(block.isSignatureValid());
|
assertTrue(block.isSignatureValid());
|
||||||
@ -107,7 +107,7 @@ public class BlockTests extends Common {
|
|||||||
// Block 949 has lots of varied transactions
|
// Block 949 has lots of varied transactions
|
||||||
// Blocks 390 & 754 have only payment transactions
|
// Blocks 390 & 754 have only payment transactions
|
||||||
BlockData blockData = repository.getBlockRepository().fromHeight(754);
|
BlockData blockData = repository.getBlockRepository().fromHeight(754);
|
||||||
assertNotNull(blockData, "Block 754 is required for this test");
|
assertNotNull("Block 754 is required for this test", blockData);
|
||||||
|
|
||||||
Block block = new Block(repository, blockData);
|
Block block = new Block(repository, blockData);
|
||||||
assertTrue(block.isSignatureValid());
|
assertTrue(block.isSignatureValid());
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
package org.qora.test;
|
package org.qora.test;
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.Test;
|
||||||
import org.qora.block.BlockChain;
|
import org.qora.block.BlockChain;
|
||||||
import org.qora.repository.DataException;
|
import org.qora.repository.DataException;
|
||||||
|
|
||||||
|
@ -1,24 +1,54 @@
|
|||||||
package org.qora.test;
|
package org.qora.test;
|
||||||
|
|
||||||
import org.junit.jupiter.api.BeforeAll;
|
import static org.junit.Assert.assertEquals;
|
||||||
import org.qora.controller.Controller;
|
|
||||||
|
import java.security.Security;
|
||||||
|
|
||||||
|
import org.bitcoinj.core.Base58;
|
||||||
|
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||||
|
import org.bouncycastle.jsse.provider.BouncyCastleJsseProvider;
|
||||||
|
import org.junit.AfterClass;
|
||||||
|
import org.junit.BeforeClass;
|
||||||
import org.qora.repository.DataException;
|
import org.qora.repository.DataException;
|
||||||
|
import org.qora.repository.Repository;
|
||||||
import org.qora.repository.RepositoryFactory;
|
import org.qora.repository.RepositoryFactory;
|
||||||
import org.qora.repository.RepositoryManager;
|
import org.qora.repository.RepositoryManager;
|
||||||
import org.qora.repository.hsqldb.HSQLDBRepositoryFactory;
|
import org.qora.repository.hsqldb.HSQLDBRepositoryFactory;
|
||||||
import org.junit.jupiter.api.AfterAll;
|
import org.qora.settings.Settings;
|
||||||
|
|
||||||
public class Common {
|
public class Common {
|
||||||
|
|
||||||
@BeforeAll
|
public static final String testConnectionUrl = "jdbc:hsqldb:mem:testdb";
|
||||||
|
public static final String testSettingsFilename = "test-settings.json";
|
||||||
|
|
||||||
|
public static final byte[] v2testPrivateKey = Base58.decode("A9MNsATgQgruBUjxy2rjWY36Yf19uRioKZbiLFT2P7c6");
|
||||||
|
public static final byte[] v2testPublicKey = Base58.decode("2tiMr5LTpaWCgbRvkPK8TFd7k63DyHJMMFFsz9uBf1ZP");
|
||||||
|
public static final String v2testAddress = "QgV4s3xnzLhVBEJxcYui4u4q11yhUHsd9v";
|
||||||
|
|
||||||
|
static {
|
||||||
|
// This must go before any calls to LogManager/Logger
|
||||||
|
System.setProperty("java.util.logging.manager", "org.apache.logging.log4j.jul.LogManager");
|
||||||
|
|
||||||
|
Security.insertProviderAt(new BouncyCastleProvider(), 0);
|
||||||
|
Security.insertProviderAt(new BouncyCastleJsseProvider(), 1);
|
||||||
|
|
||||||
|
// Load/check settings, which potentially sets up blockchain config, etc.
|
||||||
|
Settings.fileInstance(testSettingsFilename);
|
||||||
|
}
|
||||||
|
|
||||||
|
@BeforeClass
|
||||||
public static void setRepository() throws DataException {
|
public static void setRepository() throws DataException {
|
||||||
RepositoryFactory repositoryFactory = new HSQLDBRepositoryFactory(Controller.connectionUrl);
|
RepositoryFactory repositoryFactory = new HSQLDBRepositoryFactory(testConnectionUrl);
|
||||||
RepositoryManager.setRepositoryFactory(repositoryFactory);
|
RepositoryManager.setRepositoryFactory(repositoryFactory);
|
||||||
}
|
}
|
||||||
|
|
||||||
@AfterAll
|
@AfterClass
|
||||||
public static void closeRepository() throws DataException {
|
public static void closeRepository() throws DataException {
|
||||||
RepositoryManager.closeRepositoryFactory();
|
RepositoryManager.closeRepositoryFactory();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void assetEmptyBlockchain(Repository repository) throws DataException {
|
||||||
|
assertEquals("Blockchain should be empty for this test", 0, repository.getBlockRepository().getBlockchainHeight());
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
package org.qora.test;
|
package org.qora.test;
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.Test;
|
||||||
import org.qora.data.transaction.TransactionData;
|
import org.qora.data.transaction.TransactionData;
|
||||||
import org.qora.transaction.CreateAssetOrderTransaction;
|
import org.qora.transaction.CreateAssetOrderTransaction;
|
||||||
import org.qora.transaction.CreatePollTransaction;
|
import org.qora.transaction.CreatePollTransaction;
|
||||||
@ -8,7 +8,7 @@ import org.qora.transaction.IssueAssetTransaction;
|
|||||||
import org.qora.transform.TransformationException;
|
import org.qora.transform.TransformationException;
|
||||||
import org.qora.transform.transaction.TransactionTransformer;
|
import org.qora.transform.transaction.TransactionTransformer;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.*;
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
import com.google.common.hash.HashCode;
|
import com.google.common.hash.HashCode;
|
||||||
|
|
||||||
|
@ -1,16 +1,17 @@
|
|||||||
package org.qora.test;
|
package org.qora.test;
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.Test;
|
||||||
|
import org.qora.block.BlockChain;
|
||||||
import org.qora.crypto.Crypto;
|
import org.qora.crypto.Crypto;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.*;
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
import com.google.common.hash.HashCode;
|
import com.google.common.hash.HashCode;
|
||||||
|
|
||||||
public class CryptoTests {
|
public class CryptoTests extends Common {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testCryptoDigest() {
|
public void testDigest() {
|
||||||
byte[] input = HashCode.fromString("00").asBytes();
|
byte[] input = HashCode.fromString("00").asBytes();
|
||||||
byte[] digest = Crypto.digest(input);
|
byte[] digest = Crypto.digest(input);
|
||||||
byte[] expected = HashCode.fromString("6e340b9cffb37a989ca544e6bb780a2c78901d3fb33738768511a30617afa01d").asBytes();
|
byte[] expected = HashCode.fromString("6e340b9cffb37a989ca544e6bb780a2c78901d3fb33738768511a30617afa01d").asBytes();
|
||||||
@ -19,7 +20,7 @@ public class CryptoTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testCryptoDoubleDigest() {
|
public void testDoubleDigest() {
|
||||||
byte[] input = HashCode.fromString("00").asBytes();
|
byte[] input = HashCode.fromString("00").asBytes();
|
||||||
byte[] digest = Crypto.doubleDigest(input);
|
byte[] digest = Crypto.doubleDigest(input);
|
||||||
byte[] expected = HashCode.fromString("1406e05881e299367766d313e26c05564ec91bf721d31726bd6e46e60689539a").asBytes();
|
byte[] expected = HashCode.fromString("1406e05881e299367766d313e26c05564ec91bf721d31726bd6e46e60689539a").asBytes();
|
||||||
@ -28,9 +29,9 @@ public class CryptoTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testCryptoQoraAddress() {
|
public void testPublicKeyToAddress() {
|
||||||
byte[] publicKey = HashCode.fromString("775ada64a48a30b3bfc4f1db16bca512d4088704975a62bde78781ce0cba90d6").asBytes();
|
byte[] publicKey = HashCode.fromString("775ada64a48a30b3bfc4f1db16bca512d4088704975a62bde78781ce0cba90d6").asBytes();
|
||||||
String expected = "QUD9y7NZqTtNwvSAUfewd7zKUGoVivVnTW";
|
String expected = BlockChain.getUseBrokenMD160ForAddresses() ? "QUD9y7NZqTtNwvSAUfewd7zKUGoVivVnTW" : "QPc6TvGJ5RjW6LpwUtafx7XRCdRvyN6rsA";
|
||||||
|
|
||||||
assertEquals(expected, Crypto.toAddress(publicKey));
|
assertEquals(expected, Crypto.toAddress(publicKey));
|
||||||
}
|
}
|
||||||
|
@ -1,90 +0,0 @@
|
|||||||
package org.qora.test;
|
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
import org.qora.block.Block;
|
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.*;
|
|
||||||
|
|
||||||
public class ExceptionTests {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Proof of concept for block processing throwing transaction-related SQLException rather than savepoint-rollback-related SQLException.
|
|
||||||
* <p>
|
|
||||||
* See {@link Block#isValid(Connection)}.
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
public void testBlockProcessingExceptions() {
|
|
||||||
try {
|
|
||||||
simulateThrow();
|
|
||||||
fail("Should not return result");
|
|
||||||
} catch (Exception e) {
|
|
||||||
assertEquals("Transaction issue", e.getMessage());
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
boolean result = simulateFalse();
|
|
||||||
assertFalse(result);
|
|
||||||
} catch (Exception e) {
|
|
||||||
fail("Unexpected exception: " + e.getMessage());
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
boolean result = simulateTrue();
|
|
||||||
assertTrue(result);
|
|
||||||
} catch (Exception e) {
|
|
||||||
fail("Unexpected exception: " + e.getMessage());
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean simulateThrow() throws Exception {
|
|
||||||
// simulate create savepoint (no-op)
|
|
||||||
|
|
||||||
try {
|
|
||||||
// simulate processing transactions but an exception is thrown
|
|
||||||
throw new Exception("Transaction issue");
|
|
||||||
} finally {
|
|
||||||
// attempt to rollback
|
|
||||||
try {
|
|
||||||
// simulate failing to rollback due to prior exception
|
|
||||||
throw new Exception("Rollback issue");
|
|
||||||
} catch (Exception e) {
|
|
||||||
// test discard of rollback exception, leaving prior exception
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean simulateFalse() throws Exception {
|
|
||||||
// simulate create savepoint (no-op)
|
|
||||||
|
|
||||||
try {
|
|
||||||
// simulate processing transactions but false returned
|
|
||||||
return false;
|
|
||||||
} finally {
|
|
||||||
// attempt to rollback
|
|
||||||
try {
|
|
||||||
// simulate successful rollback (no-op)
|
|
||||||
} catch (Exception e) {
|
|
||||||
// test discard of rollback exception, leaving prior exception
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean simulateTrue() throws Exception {
|
|
||||||
// simulate create savepoint (no-op)
|
|
||||||
|
|
||||||
try {
|
|
||||||
// simulate processing transactions successfully
|
|
||||||
} finally {
|
|
||||||
// attempt to rollback
|
|
||||||
try {
|
|
||||||
// simulate successful rollback (no-op)
|
|
||||||
} catch (Exception e) {
|
|
||||||
// test discard of rollback exception, leaving prior exception
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -3,53 +3,33 @@ package org.qora.test;
|
|||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.Test;
|
||||||
import org.qora.account.Account;
|
|
||||||
import org.qora.asset.Asset;
|
|
||||||
import org.qora.block.Block;
|
import org.qora.block.Block;
|
||||||
import org.qora.block.GenesisBlock;
|
import org.qora.block.GenesisBlock;
|
||||||
import org.qora.data.transaction.TransactionData;
|
import org.qora.data.transaction.TransactionData;
|
||||||
import org.qora.repository.DataException;
|
import org.qora.repository.DataException;
|
||||||
import org.qora.repository.Repository;
|
import org.qora.repository.Repository;
|
||||||
import org.qora.repository.RepositoryFactory;
|
|
||||||
import org.qora.repository.RepositoryManager;
|
import org.qora.repository.RepositoryManager;
|
||||||
import org.qora.repository.hsqldb.HSQLDBRepositoryFactory;
|
|
||||||
import org.qora.transaction.Transaction;
|
import org.qora.transaction.Transaction;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.*;
|
import static org.junit.Assert.*;
|
||||||
import org.junit.jupiter.api.BeforeAll;
|
|
||||||
import org.junit.jupiter.api.AfterAll;
|
|
||||||
|
|
||||||
// Don't extend Common as we want an in-memory database
|
public class GenesisTests extends Common {
|
||||||
public class GenesisTests {
|
|
||||||
|
|
||||||
public static final String connectionUrl = "jdbc:hsqldb:mem:db/blockchain;create=true";
|
|
||||||
|
|
||||||
@BeforeAll
|
|
||||||
public static void setRepository() throws DataException {
|
|
||||||
RepositoryFactory repositoryFactory = new HSQLDBRepositoryFactory(connectionUrl);
|
|
||||||
RepositoryManager.setRepositoryFactory(repositoryFactory);
|
|
||||||
}
|
|
||||||
|
|
||||||
@AfterAll
|
|
||||||
public static void closeRepository() throws DataException {
|
|
||||||
RepositoryManager.closeRepositoryFactory();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testGenesisBlockTransactions() throws DataException {
|
public void testGenesisBlockTransactions() throws DataException {
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
assertEquals(0, repository.getBlockRepository().getBlockchainHeight(), "Blockchain should be empty for this test");
|
assertEquals("Blockchain should be empty for this test", 0, repository.getBlockRepository().getBlockchainHeight());
|
||||||
|
|
||||||
GenesisBlock block = GenesisBlock.getInstance(repository);
|
GenesisBlock block = GenesisBlock.getInstance(repository);
|
||||||
|
|
||||||
assertNotNull(block);
|
assertNotNull("No genesis block?", block);
|
||||||
assertTrue(block.isSignatureValid());
|
assertTrue(block.isSignatureValid());
|
||||||
// Note: only true if blockchain is empty
|
// Note: only true if blockchain is empty
|
||||||
assertEquals(Block.ValidationResult.OK, block.isValid());
|
assertEquals("Block invalid", Block.ValidationResult.OK, block.isValid());
|
||||||
|
|
||||||
List<Transaction> transactions = block.getTransactions();
|
List<Transaction> transactions = block.getTransactions();
|
||||||
assertNotNull(transactions);
|
assertNotNull("No transactions?", transactions);
|
||||||
|
|
||||||
for (Transaction transaction : transactions) {
|
for (Transaction transaction : transactions) {
|
||||||
assertNotNull(transaction);
|
assertNotNull(transaction);
|
||||||
@ -67,26 +47,6 @@ public class GenesisTests {
|
|||||||
// Actually try to process genesis block onto empty blockchain
|
// Actually try to process genesis block onto empty blockchain
|
||||||
block.process();
|
block.process();
|
||||||
repository.saveChanges();
|
repository.saveChanges();
|
||||||
|
|
||||||
// Attempt to load first transaction directly from database
|
|
||||||
TransactionData transactionData = repository.getTransactionRepository().fromSignature(transactions.get(0).getTransactionData().getSignature());
|
|
||||||
assertNotNull(transactionData);
|
|
||||||
|
|
||||||
assertEquals(Transaction.TransactionType.GENESIS, transactionData.getType());
|
|
||||||
assertTrue(transactionData.getFee().compareTo(BigDecimal.ZERO) == 0);
|
|
||||||
assertNull(transactionData.getReference());
|
|
||||||
|
|
||||||
Transaction transaction = Transaction.fromData(repository, transactionData);
|
|
||||||
assertNotNull(transaction);
|
|
||||||
|
|
||||||
assertTrue(transaction.isSignatureValid());
|
|
||||||
assertEquals(Transaction.ValidationResult.OK, transaction.isValid());
|
|
||||||
|
|
||||||
// Check known balance
|
|
||||||
Account testAccount = new Account(repository, "QegT2Ws5YjLQzEZ9YMzWsAZMBE8cAygHZN");
|
|
||||||
BigDecimal testBalance = testAccount.getConfirmedBalance(Asset.QORA);
|
|
||||||
BigDecimal expectedBalance = new BigDecimal("12606834").setScale(8);
|
|
||||||
assertTrue(testBalance.compareTo(expectedBalance) == 0);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
99
src/test/java/org/qora/test/GroupApprovalTests.java
Normal file
99
src/test/java/org/qora/test/GroupApprovalTests.java
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
package org.qora.test;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.qora.account.PrivateKeyAccount;
|
||||||
|
import org.qora.block.BlockChain;
|
||||||
|
import org.qora.block.BlockGenerator;
|
||||||
|
import org.qora.data.transaction.CreateGroupTransactionData;
|
||||||
|
import org.qora.data.transaction.PaymentTransactionData;
|
||||||
|
import org.qora.data.transaction.TransactionData;
|
||||||
|
import org.qora.group.Group;
|
||||||
|
import org.qora.group.Group.ApprovalThreshold;
|
||||||
|
import org.qora.repository.DataException;
|
||||||
|
import org.qora.repository.Repository;
|
||||||
|
import org.qora.repository.RepositoryManager;
|
||||||
|
import org.qora.transaction.CreateGroupTransaction;
|
||||||
|
import org.qora.transaction.PaymentTransaction;
|
||||||
|
import org.qora.transaction.Transaction;
|
||||||
|
import org.qora.transaction.Transaction.ValidationResult;
|
||||||
|
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
|
||||||
|
public class GroupApprovalTests extends Common {
|
||||||
|
|
||||||
|
/** Check that a tx type that doesn't need approval doesn't accept txGroupId apart from NO_GROUP */
|
||||||
|
@Test
|
||||||
|
public void testNonApprovalTxGroupId() throws DataException {
|
||||||
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
|
BlockChain.validate();
|
||||||
|
|
||||||
|
TransactionData transactionData = buildPayment(repository, Group.NO_GROUP);
|
||||||
|
Transaction transaction = new PaymentTransaction(repository, transactionData);
|
||||||
|
assertEquals(ValidationResult.OK, transaction.isValidUnconfirmed());
|
||||||
|
|
||||||
|
int groupId = createGroup(repository);
|
||||||
|
|
||||||
|
transactionData = buildPayment(repository, groupId);
|
||||||
|
transaction = new PaymentTransaction(repository, transactionData);
|
||||||
|
assertEquals(ValidationResult.INVALID_TX_GROUP_ID, transaction.isValidUnconfirmed());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private PaymentTransactionData buildPayment(Repository repository, int txGroupId) throws DataException {
|
||||||
|
long timestamp = System.currentTimeMillis() - 1000L;
|
||||||
|
byte[] reference = repository.getAccountRepository().getLastReference(v2testAddress);
|
||||||
|
byte[] senderPublicKey = v2testPublicKey;
|
||||||
|
String recipient = v2testAddress;
|
||||||
|
BigDecimal amount = BigDecimal.ONE.setScale(8);
|
||||||
|
BigDecimal fee = BigDecimal.ONE.setScale(8);
|
||||||
|
|
||||||
|
return new PaymentTransactionData(timestamp, txGroupId, reference, senderPublicKey, recipient, amount, fee);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int createGroup(Repository repository) throws DataException {
|
||||||
|
long timestamp = System.currentTimeMillis() - 1000L;
|
||||||
|
int txGroupId = Group.NO_GROUP;
|
||||||
|
byte[] reference = repository.getAccountRepository().getLastReference(v2testAddress);
|
||||||
|
byte[] creatorPublicKey = v2testPublicKey;
|
||||||
|
String owner = v2testAddress;
|
||||||
|
String groupName = "test-group";
|
||||||
|
String description = "test group description";
|
||||||
|
boolean isOpen = false;
|
||||||
|
ApprovalThreshold approvalThreshold = ApprovalThreshold.ONE;
|
||||||
|
int minimumBlockDelay = 0;
|
||||||
|
int maximumBlockDelay = 1440;
|
||||||
|
Integer groupId = null;
|
||||||
|
BigDecimal fee = BigDecimal.ONE.setScale(8);
|
||||||
|
byte[] signature = null;
|
||||||
|
|
||||||
|
TransactionData transactionData = new CreateGroupTransactionData(timestamp, txGroupId, reference, creatorPublicKey, owner, groupName, description,
|
||||||
|
isOpen, approvalThreshold, minimumBlockDelay, maximumBlockDelay, groupId, fee, signature);
|
||||||
|
Transaction transaction = new CreateGroupTransaction(repository, transactionData);
|
||||||
|
|
||||||
|
// Sign transaction
|
||||||
|
PrivateKeyAccount signer = new PrivateKeyAccount(repository, v2testPrivateKey);
|
||||||
|
transaction.sign(signer);
|
||||||
|
|
||||||
|
// Add to unconfirmed
|
||||||
|
if (!transaction.isSignatureValid())
|
||||||
|
throw new RuntimeException("CREATE_GROUP transaction's signature invalid");
|
||||||
|
|
||||||
|
ValidationResult result = transaction.isValidUnconfirmed();
|
||||||
|
if (result != ValidationResult.OK)
|
||||||
|
throw new RuntimeException(String.format("CREATE_GROUP transaction invalid: %s", result.name()));
|
||||||
|
|
||||||
|
repository.getTransactionRepository().save(transactionData);
|
||||||
|
repository.getTransactionRepository().unconfirmTransaction(transactionData);
|
||||||
|
repository.saveChanges();
|
||||||
|
|
||||||
|
// Generate block
|
||||||
|
BlockGenerator.generateTestingBlock(repository, signer);
|
||||||
|
|
||||||
|
// Return assigned groupId
|
||||||
|
transactionData = repository.getTransactionRepository().fromSignature(transactionData.getSignature());
|
||||||
|
return ((CreateGroupTransactionData) transactionData).getGroupId();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,6 +1,6 @@
|
|||||||
package org.qora.test;
|
package org.qora.test;
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.Test;
|
||||||
import org.qora.account.PublicKeyAccount;
|
import org.qora.account.PublicKeyAccount;
|
||||||
import org.qora.data.transaction.PaymentTransactionData;
|
import org.qora.data.transaction.PaymentTransactionData;
|
||||||
import org.qora.data.transaction.TransactionData;
|
import org.qora.data.transaction.TransactionData;
|
||||||
@ -11,7 +11,7 @@ import org.qora.repository.TransactionRepository;
|
|||||||
import org.qora.transaction.Transaction.TransactionType;
|
import org.qora.transaction.Transaction.TransactionType;
|
||||||
import org.qora.utils.Base58;
|
import org.qora.utils.Base58;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.*;
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
public class LoadTests extends Common {
|
public class LoadTests extends Common {
|
||||||
|
|
||||||
@ -20,15 +20,14 @@ public class LoadTests extends Common {
|
|||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
TransactionRepository transactionRepository = repository.getTransactionRepository();
|
TransactionRepository transactionRepository = repository.getTransactionRepository();
|
||||||
|
|
||||||
assertTrue(repository.getBlockRepository().getBlockchainHeight() >= 49778,
|
assertTrue("Migrate from old database to at least block 49778 before running this test", repository.getBlockRepository().getBlockchainHeight() >= 49778);
|
||||||
"Migrate from old database to at least block 49778 before running this test");
|
|
||||||
|
|
||||||
String signature58 = "1211ZPwG3hk5evWzXCZi9hMDRpwumWmkENjwWkeTCik9xA5uoYnxzF7rwR5hmHH3kG2RXo7ToCAaRc7dvnynByJt";
|
String signature58 = "1211ZPwG3hk5evWzXCZi9hMDRpwumWmkENjwWkeTCik9xA5uoYnxzF7rwR5hmHH3kG2RXo7ToCAaRc7dvnynByJt";
|
||||||
byte[] signature = Base58.decode(signature58);
|
byte[] signature = Base58.decode(signature58);
|
||||||
|
|
||||||
TransactionData transactionData = transactionRepository.fromSignature(signature);
|
TransactionData transactionData = transactionRepository.fromSignature(signature);
|
||||||
assertNotNull(transactionData, "Transaction data not loaded from repository");
|
assertNotNull("Transaction data not loaded from repository", transactionData);
|
||||||
assertEquals(TransactionType.PAYMENT, transactionData.getType(), "Transaction data not PAYMENT type");
|
assertEquals("Transaction data not PAYMENT type", TransactionType.PAYMENT, transactionData.getType());
|
||||||
assertEquals("QXwu8924WdgPoRmtiWQBUMF6eedmp1Hu2E", PublicKeyAccount.getAddress(transactionData.getCreatorPublicKey()));
|
assertEquals("QXwu8924WdgPoRmtiWQBUMF6eedmp1Hu2E", PublicKeyAccount.getAddress(transactionData.getCreatorPublicKey()));
|
||||||
|
|
||||||
PaymentTransactionData paymentTransactionData = (PaymentTransactionData) transactionData;
|
PaymentTransactionData paymentTransactionData = (PaymentTransactionData) transactionData;
|
||||||
@ -47,8 +46,7 @@ public class LoadTests extends Common {
|
|||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
TransactionRepository transactionRepository = repository.getTransactionRepository();
|
TransactionRepository transactionRepository = repository.getTransactionRepository();
|
||||||
|
|
||||||
assertTrue(repository.getBlockRepository().getBlockchainHeight() >= 49778,
|
assertTrue("Migrate from old database to at least block 49778 before running this test", repository.getBlockRepository().getBlockchainHeight() >= 49778);
|
||||||
"Migrate from old database to at least block 49778 before running this test");
|
|
||||||
|
|
||||||
String signature58 = "1211ZPwG3hk5evWzXCZi9hMDRpwumWmkENjwWkeTCik9xA5uoYnxzF7rwR5hmHH3kG2RXo7ToCAaRc7dvnynByJt";
|
String signature58 = "1211ZPwG3hk5evWzXCZi9hMDRpwumWmkENjwWkeTCik9xA5uoYnxzF7rwR5hmHH3kG2RXo7ToCAaRc7dvnynByJt";
|
||||||
byte[] signature = Base58.decode(signature58);
|
byte[] signature = Base58.decode(signature58);
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
package org.qora.test;
|
package org.qora.test;
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.Test;
|
||||||
import org.qora.data.block.BlockData;
|
import org.qora.data.block.BlockData;
|
||||||
import org.qora.data.transaction.TransactionData;
|
import org.qora.data.transaction.TransactionData;
|
||||||
import org.qora.repository.DataException;
|
import org.qora.repository.DataException;
|
||||||
@ -10,7 +10,7 @@ import org.qora.repository.TransactionRepository;
|
|||||||
import org.qora.transaction.Transaction.TransactionType;
|
import org.qora.transaction.Transaction.TransactionType;
|
||||||
import org.qora.utils.Base58;
|
import org.qora.utils.Base58;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.*;
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
public class NavigationTests extends Common {
|
public class NavigationTests extends Common {
|
||||||
|
|
||||||
@ -19,8 +19,7 @@ public class NavigationTests extends Common {
|
|||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
TransactionRepository transactionRepository = repository.getTransactionRepository();
|
TransactionRepository transactionRepository = repository.getTransactionRepository();
|
||||||
|
|
||||||
assertTrue(repository.getBlockRepository().getBlockchainHeight() >= 49778,
|
assertTrue("Migrate from old database to at least block 49778 before running this test", repository.getBlockRepository().getBlockchainHeight() >= 49778);
|
||||||
"Migrate from old database to at least block 49778 before running this test");
|
|
||||||
|
|
||||||
String signature58 = "1211ZPwG3hk5evWzXCZi9hMDRpwumWmkENjwWkeTCik9xA5uoYnxzF7rwR5hmHH3kG2RXo7ToCAaRc7dvnynByJt";
|
String signature58 = "1211ZPwG3hk5evWzXCZi9hMDRpwumWmkENjwWkeTCik9xA5uoYnxzF7rwR5hmHH3kG2RXo7ToCAaRc7dvnynByJt";
|
||||||
byte[] signature = Base58.decode(signature58);
|
byte[] signature = Base58.decode(signature58);
|
||||||
@ -28,15 +27,15 @@ public class NavigationTests extends Common {
|
|||||||
System.out.println("Navigating to Block from transaction " + signature58);
|
System.out.println("Navigating to Block from transaction " + signature58);
|
||||||
|
|
||||||
TransactionData transactionData = transactionRepository.fromSignature(signature);
|
TransactionData transactionData = transactionRepository.fromSignature(signature);
|
||||||
assertNotNull(transactionData, "Transaction data not loaded from repository");
|
assertNotNull("Transaction data not loaded from repository", transactionData);
|
||||||
assertEquals(TransactionType.PAYMENT, transactionData.getType(), "Transaction data not PAYMENT type");
|
assertEquals("Transaction data not PAYMENT type", TransactionType.PAYMENT, transactionData.getType());
|
||||||
|
|
||||||
int transactionHeight = transactionRepository.getHeightFromSignature(signature);
|
int transactionHeight = transactionRepository.getHeightFromSignature(signature);
|
||||||
assertNotEquals(0, transactionHeight, "Transaction not found or transaction's block not found");
|
assertFalse("Transaction not found or transaction's block not found", transactionHeight == 0);
|
||||||
assertEquals(49778, transactionHeight, "Transaction's block height expected to be 49778");
|
assertEquals("Transaction's block height expected to be 49778", 49778, transactionHeight);
|
||||||
|
|
||||||
BlockData blockData = repository.getBlockRepository().fromHeight(transactionHeight);
|
BlockData blockData = repository.getBlockRepository().fromHeight(transactionHeight);
|
||||||
assertNotNull(blockData, "Block 49778 not loaded from database");
|
assertNotNull("Block 49778 not loaded from database", blockData);
|
||||||
System.out.println("Block " + blockData.getHeight() + ", signature: " + Base58.encode(blockData.getSignature()));
|
System.out.println("Block " + blockData.getHeight() + ", signature: " + Base58.encode(blockData.getSignature()));
|
||||||
|
|
||||||
assertEquals((Integer) 49778, blockData.getHeight());
|
assertEquals((Integer) 49778, blockData.getHeight());
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
package org.qora.test;
|
package org.qora.test;
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.Test;
|
||||||
import org.qora.repository.DataException;
|
import org.qora.repository.DataException;
|
||||||
import org.qora.repository.Repository;
|
import org.qora.repository.Repository;
|
||||||
import org.qora.repository.RepositoryManager;
|
import org.qora.repository.RepositoryManager;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.*;
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
import org.apache.logging.log4j.LogManager;
|
import org.apache.logging.log4j.LogManager;
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
|
@ -3,7 +3,7 @@ package org.qora.test;
|
|||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.Test;
|
||||||
import org.qora.account.PublicKeyAccount;
|
import org.qora.account.PublicKeyAccount;
|
||||||
import org.qora.data.transaction.PaymentTransactionData;
|
import org.qora.data.transaction.PaymentTransactionData;
|
||||||
import org.qora.group.Group;
|
import org.qora.group.Group;
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
package org.qora.test;
|
package org.qora.test;
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.Test;
|
||||||
import org.qora.block.Block;
|
import org.qora.block.Block;
|
||||||
import org.qora.block.GenesisBlock;
|
import org.qora.block.GenesisBlock;
|
||||||
import org.qora.data.block.BlockData;
|
import org.qora.data.block.BlockData;
|
||||||
@ -15,7 +15,7 @@ import org.qora.transaction.Transaction.TransactionType;
|
|||||||
import org.qora.transform.TransformationException;
|
import org.qora.transform.TransformationException;
|
||||||
import org.qora.transform.transaction.TransactionTransformer;
|
import org.qora.transform.transaction.TransactionTransformer;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.*;
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -60,15 +60,15 @@ public class SerializationTests extends Common {
|
|||||||
|
|
||||||
TransactionData parsedTransactionData = TransactionTransformer.fromBytes(bytes);
|
TransactionData parsedTransactionData = TransactionTransformer.fromBytes(bytes);
|
||||||
|
|
||||||
assertTrue(Arrays.equals(transactionData.getSignature(), parsedTransactionData.getSignature()), "Transaction signature mismatch");
|
assertTrue("Transaction signature mismatch", Arrays.equals(transactionData.getSignature(), parsedTransactionData.getSignature()));
|
||||||
|
|
||||||
assertEquals(bytes.length, TransactionTransformer.getDataLength(transactionData), "Data length mismatch");
|
assertEquals("Data length mismatch", bytes.length, TransactionTransformer.getDataLength(transactionData));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void testSpecificBlockTransactions(int height, TransactionType type) throws DataException, TransformationException {
|
private void testSpecificBlockTransactions(int height, TransactionType type) throws DataException, TransformationException {
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
BlockData blockData = repository.getBlockRepository().fromHeight(height);
|
BlockData blockData = repository.getBlockRepository().fromHeight(height);
|
||||||
assertNotNull(blockData, "Block " + height + " is required for this test");
|
assertNotNull("Block " + height + " is required for this test", blockData);
|
||||||
|
|
||||||
Block block = new Block(repository, blockData);
|
Block block = new Block(repository, blockData);
|
||||||
|
|
||||||
|
64
src/test/java/org/qora/test/SettingsTests.java
Normal file
64
src/test/java/org/qora/test/SettingsTests.java
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
package org.qora.test;
|
||||||
|
|
||||||
|
import java.io.FileReader;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.Reader;
|
||||||
|
|
||||||
|
import javax.xml.bind.JAXBContext;
|
||||||
|
import javax.xml.bind.JAXBException;
|
||||||
|
import javax.xml.bind.Unmarshaller;
|
||||||
|
import javax.xml.transform.stream.StreamSource;
|
||||||
|
|
||||||
|
import org.eclipse.persistence.jaxb.JAXBContextFactory;
|
||||||
|
import org.eclipse.persistence.jaxb.UnmarshallerProperties;
|
||||||
|
import org.qora.block.BlockChain;
|
||||||
|
import org.qora.data.transaction.TransactionData;
|
||||||
|
import org.qora.settings.Settings;
|
||||||
|
|
||||||
|
public class SettingsTests {
|
||||||
|
|
||||||
|
public static void main(String[] args) throws JAXBException, IOException {
|
||||||
|
// JAXBContext jc = JAXBContext.newInstance(SettingsData.class);
|
||||||
|
JAXBContext jc = JAXBContextFactory.createContext(new Class[] {Settings.class, BlockChain.class, TransactionData.class}, null);
|
||||||
|
|
||||||
|
// Create the Unmarshaller Object using the JaxB Context
|
||||||
|
Unmarshaller unmarshaller = jc.createUnmarshaller();
|
||||||
|
|
||||||
|
// Set the Unmarshaller media type to JSON or XML
|
||||||
|
unmarshaller.setProperty(UnmarshallerProperties.MEDIA_TYPE, "application/json");
|
||||||
|
|
||||||
|
// Set it to true if you need to include the JSON root element in the JSON input
|
||||||
|
unmarshaller.setProperty(UnmarshallerProperties.JSON_INCLUDE_ROOT, false);
|
||||||
|
|
||||||
|
Settings settings = null;
|
||||||
|
|
||||||
|
// Create the StreamSource by creating Reader to the JSON input
|
||||||
|
try (Reader settingsReader = new FileReader("settings.json")) {
|
||||||
|
StreamSource json = new StreamSource(settingsReader);
|
||||||
|
|
||||||
|
// Getting the SettingsData pojo from the json
|
||||||
|
settings = unmarshaller.unmarshal(json, Settings.class).getValue();
|
||||||
|
|
||||||
|
System.out.println("API settings:");
|
||||||
|
System.out.println(String.format("Enabled: %s, port: %d, restricted: %s, whitelist: %s", yn(settings.isApiEnabled()), settings.getApiPort(),
|
||||||
|
yn(settings.isApiRestricted()), String.join(", ", settings.getApiWhitelist())));
|
||||||
|
}
|
||||||
|
|
||||||
|
String blockchainConfig = settings.getBlockchainConfig();
|
||||||
|
if (blockchainConfig != null)
|
||||||
|
try (Reader settingsReader = new FileReader(blockchainConfig)) {
|
||||||
|
StreamSource json = new StreamSource(settingsReader);
|
||||||
|
|
||||||
|
// Getting the BlockChainData pojo from the JSON
|
||||||
|
BlockChain blockchain = unmarshaller.unmarshal(json, BlockChain.class).getValue();
|
||||||
|
|
||||||
|
System.out.println("BlockChain settings:");
|
||||||
|
System.out.println(String.format("TestNet: %s", yn(blockchain.isTestNet())));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String yn(boolean flag) {
|
||||||
|
return flag ? "yes" : "no";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,6 +1,6 @@
|
|||||||
package org.qora.test;
|
package org.qora.test;
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.Test;
|
||||||
import org.qora.account.PrivateKeyAccount;
|
import org.qora.account.PrivateKeyAccount;
|
||||||
import org.qora.block.Block;
|
import org.qora.block.Block;
|
||||||
import org.qora.block.GenesisBlock;
|
import org.qora.block.GenesisBlock;
|
||||||
@ -11,7 +11,7 @@ import org.qora.repository.RepositoryManager;
|
|||||||
import org.qora.utils.Base58;
|
import org.qora.utils.Base58;
|
||||||
import org.qora.utils.NTP;
|
import org.qora.utils.NTP;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.*;
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package org.qora.test;
|
package org.qora.test;
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.After;
|
||||||
|
import org.junit.Test;
|
||||||
import org.qora.account.Account;
|
import org.qora.account.Account;
|
||||||
import org.qora.account.PrivateKeyAccount;
|
import org.qora.account.PrivateKeyAccount;
|
||||||
import org.qora.account.PublicKeyAccount;
|
import org.qora.account.PublicKeyAccount;
|
||||||
@ -37,10 +38,7 @@ import org.qora.repository.AccountRepository;
|
|||||||
import org.qora.repository.AssetRepository;
|
import org.qora.repository.AssetRepository;
|
||||||
import org.qora.repository.DataException;
|
import org.qora.repository.DataException;
|
||||||
import org.qora.repository.Repository;
|
import org.qora.repository.Repository;
|
||||||
import org.qora.repository.RepositoryFactory;
|
|
||||||
import org.qora.repository.RepositoryManager;
|
import org.qora.repository.RepositoryManager;
|
||||||
import org.qora.repository.hsqldb.HSQLDBRepositoryFactory;
|
|
||||||
import org.qora.settings.Settings;
|
|
||||||
import org.qora.transaction.BuyNameTransaction;
|
import org.qora.transaction.BuyNameTransaction;
|
||||||
import org.qora.transaction.CancelAssetOrderTransaction;
|
import org.qora.transaction.CancelAssetOrderTransaction;
|
||||||
import org.qora.transaction.CancelSellNameTransaction;
|
import org.qora.transaction.CancelSellNameTransaction;
|
||||||
@ -58,8 +56,7 @@ import org.qora.transaction.UpdateNameTransaction;
|
|||||||
import org.qora.transaction.VoteOnPollTransaction;
|
import org.qora.transaction.VoteOnPollTransaction;
|
||||||
import org.qora.transaction.Transaction.ValidationResult;
|
import org.qora.transaction.Transaction.ValidationResult;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.*;
|
import static org.junit.Assert.*;
|
||||||
import org.junit.jupiter.api.AfterEach;
|
|
||||||
|
|
||||||
import java.io.UnsupportedEncodingException;
|
import java.io.UnsupportedEncodingException;
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
@ -68,14 +65,9 @@ import java.util.ArrayList;
|
|||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import org.json.simple.JSONObject;
|
|
||||||
|
|
||||||
import com.google.common.hash.HashCode;
|
import com.google.common.hash.HashCode;
|
||||||
|
|
||||||
// Don't extend Common as we want to use an in-memory database
|
public class TransactionTests extends Common {
|
||||||
public class TransactionTests {
|
|
||||||
|
|
||||||
private static final String connectionUrl = "jdbc:hsqldb:mem:db/blockchain;create=true";
|
|
||||||
|
|
||||||
private static final byte[] generatorSeed = HashCode.fromString("0123456789abcdeffedcba98765432100123456789abcdeffedcba9876543210").asBytes();
|
private static final byte[] generatorSeed = HashCode.fromString("0123456789abcdeffedcba98765432100123456789abcdeffedcba9876543210").asBytes();
|
||||||
private static final byte[] senderSeed = HashCode.fromString("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef").asBytes();
|
private static final byte[] senderSeed = HashCode.fromString("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef").asBytes();
|
||||||
@ -92,22 +84,11 @@ public class TransactionTests {
|
|||||||
private PrivateKeyAccount generator;
|
private PrivateKeyAccount generator;
|
||||||
private byte[] reference;
|
private byte[] reference;
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public void createTestAccounts(Long genesisTimestamp) throws DataException {
|
public void createTestAccounts(Long genesisTimestamp) throws DataException {
|
||||||
RepositoryFactory repositoryFactory = new HSQLDBRepositoryFactory(connectionUrl);
|
|
||||||
RepositoryManager.setRepositoryFactory(repositoryFactory);
|
|
||||||
|
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
assertEquals(0, repository.getBlockRepository().getBlockchainHeight(), "Blockchain should be empty for this test");
|
assertEquals("Blockchain should be empty for this test", 0, repository.getBlockRepository().getBlockchainHeight());
|
||||||
}
|
}
|
||||||
|
|
||||||
// [Un]set genesis timestamp as required by test
|
|
||||||
JSONObject settingsJSON = new JSONObject();
|
|
||||||
if (genesisTimestamp != null)
|
|
||||||
settingsJSON.put("testnetstamp", genesisTimestamp);
|
|
||||||
|
|
||||||
Settings.test(settingsJSON);
|
|
||||||
|
|
||||||
// This needs to be called outside of acquiring our own repository or it will deadlock
|
// This needs to be called outside of acquiring our own repository or it will deadlock
|
||||||
BlockChain.validate();
|
BlockChain.validate();
|
||||||
|
|
||||||
@ -137,9 +118,9 @@ public class TransactionTests {
|
|||||||
repository.saveChanges();
|
repository.saveChanges();
|
||||||
}
|
}
|
||||||
|
|
||||||
@AfterEach
|
@After
|
||||||
public void closeRepository() throws DataException {
|
public void afterTest() throws DataException {
|
||||||
RepositoryManager.closeRepositoryFactory();
|
repository.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
private Transaction createPayment(PrivateKeyAccount sender, String recipient) throws DataException {
|
private Transaction createPayment(PrivateKeyAccount sender, String recipient) throws DataException {
|
||||||
@ -147,7 +128,8 @@ public class TransactionTests {
|
|||||||
BigDecimal amount = genericPaymentAmount;
|
BigDecimal amount = genericPaymentAmount;
|
||||||
BigDecimal fee = BigDecimal.ONE;
|
BigDecimal fee = BigDecimal.ONE;
|
||||||
long timestamp = parentBlockData.getTimestamp() + 1_000;
|
long timestamp = parentBlockData.getTimestamp() + 1_000;
|
||||||
PaymentTransactionData paymentTransactionData = new PaymentTransactionData(timestamp, Group.NO_GROUP, reference, sender.getPublicKey(), recipient, amount, fee);
|
PaymentTransactionData paymentTransactionData = new PaymentTransactionData(timestamp, Group.NO_GROUP, reference, sender.getPublicKey(), recipient,
|
||||||
|
amount, fee);
|
||||||
|
|
||||||
Transaction paymentTransaction = new PaymentTransaction(repository, paymentTransactionData);
|
Transaction paymentTransaction = new PaymentTransaction(repository, paymentTransactionData);
|
||||||
paymentTransaction.sign(sender);
|
paymentTransaction.sign(sender);
|
||||||
@ -164,8 +146,8 @@ public class TransactionTests {
|
|||||||
BigDecimal amount = BigDecimal.valueOf(1_000L);
|
BigDecimal amount = BigDecimal.valueOf(1_000L);
|
||||||
BigDecimal fee = BigDecimal.ONE;
|
BigDecimal fee = BigDecimal.ONE;
|
||||||
long timestamp = parentBlockData.getTimestamp() + 1_000;
|
long timestamp = parentBlockData.getTimestamp() + 1_000;
|
||||||
PaymentTransactionData paymentTransactionData = new PaymentTransactionData(timestamp, Group.NO_GROUP, reference, sender.getPublicKey(), recipient.getAddress(),
|
PaymentTransactionData paymentTransactionData = new PaymentTransactionData(timestamp, Group.NO_GROUP, reference, sender.getPublicKey(),
|
||||||
amount, fee);
|
recipient.getAddress(), amount, fee);
|
||||||
|
|
||||||
Transaction paymentTransaction = new PaymentTransaction(repository, paymentTransactionData);
|
Transaction paymentTransaction = new PaymentTransaction(repository, paymentTransactionData);
|
||||||
paymentTransaction.sign(sender);
|
paymentTransaction.sign(sender);
|
||||||
@ -177,8 +159,8 @@ public class TransactionTests {
|
|||||||
block.addTransaction(paymentTransactionData);
|
block.addTransaction(paymentTransactionData);
|
||||||
block.sign();
|
block.sign();
|
||||||
|
|
||||||
assertTrue(block.isSignatureValid(), "Block signatures invalid");
|
assertTrue("Block signatures invalid", block.isSignatureValid());
|
||||||
assertEquals(Block.ValidationResult.OK, block.isValid(), "Block is invalid");
|
assertEquals("Block is invalid", Block.ValidationResult.OK, block.isValid());
|
||||||
|
|
||||||
block.process();
|
block.process();
|
||||||
repository.saveChanges();
|
repository.saveChanges();
|
||||||
@ -186,21 +168,21 @@ public class TransactionTests {
|
|||||||
// Check sender's balance
|
// Check sender's balance
|
||||||
BigDecimal expectedBalance = initialSenderBalance.subtract(amount).subtract(fee);
|
BigDecimal expectedBalance = initialSenderBalance.subtract(amount).subtract(fee);
|
||||||
BigDecimal actualBalance = accountRepository.getBalance(sender.getAddress(), Asset.QORA).getBalance();
|
BigDecimal actualBalance = accountRepository.getBalance(sender.getAddress(), Asset.QORA).getBalance();
|
||||||
assertTrue(expectedBalance.compareTo(actualBalance) == 0, "Sender's new balance incorrect");
|
assertTrue("Sender's new balance incorrect", expectedBalance.compareTo(actualBalance) == 0);
|
||||||
|
|
||||||
// Fee should be in generator's balance
|
// Fee should be in generator's balance
|
||||||
expectedBalance = initialGeneratorBalance.add(fee);
|
expectedBalance = initialGeneratorBalance.add(fee);
|
||||||
actualBalance = accountRepository.getBalance(generator.getAddress(), Asset.QORA).getBalance();
|
actualBalance = accountRepository.getBalance(generator.getAddress(), Asset.QORA).getBalance();
|
||||||
assertTrue(expectedBalance.compareTo(actualBalance) == 0, "Generator's new balance incorrect");
|
assertTrue("Generator's new balance incorrect", expectedBalance.compareTo(actualBalance) == 0);
|
||||||
|
|
||||||
// Amount should be in recipient's balance
|
// Amount should be in recipient's balance
|
||||||
expectedBalance = amount;
|
expectedBalance = amount;
|
||||||
actualBalance = accountRepository.getBalance(recipient.getAddress(), Asset.QORA).getBalance();
|
actualBalance = accountRepository.getBalance(recipient.getAddress(), Asset.QORA).getBalance();
|
||||||
assertTrue(expectedBalance.compareTo(actualBalance) == 0, "Recipient's new balance incorrect");
|
assertTrue("Recipient's new balance incorrect", expectedBalance.compareTo(actualBalance) == 0);
|
||||||
|
|
||||||
// Check recipient's reference
|
// Check recipient's reference
|
||||||
byte[] recipientsReference = recipient.getLastReference();
|
byte[] recipientsReference = recipient.getLastReference();
|
||||||
assertTrue(Arrays.equals(paymentTransaction.getTransactionData().getSignature(), recipientsReference), "Recipient's new reference incorrect");
|
assertTrue("Recipient's new reference incorrect", Arrays.equals(paymentTransaction.getTransactionData().getSignature(), recipientsReference));
|
||||||
|
|
||||||
// Orphan block
|
// Orphan block
|
||||||
block.orphan();
|
block.orphan();
|
||||||
@ -208,11 +190,11 @@ public class TransactionTests {
|
|||||||
|
|
||||||
// Check sender's balance
|
// Check sender's balance
|
||||||
actualBalance = accountRepository.getBalance(sender.getAddress(), Asset.QORA).getBalance();
|
actualBalance = accountRepository.getBalance(sender.getAddress(), Asset.QORA).getBalance();
|
||||||
assertTrue(initialSenderBalance.compareTo(actualBalance) == 0, "Sender's reverted balance incorrect");
|
assertTrue("Sender's reverted balance incorrect", initialSenderBalance.compareTo(actualBalance) == 0);
|
||||||
|
|
||||||
// Check generator's balance
|
// Check generator's balance
|
||||||
actualBalance = accountRepository.getBalance(generator.getAddress(), Asset.QORA).getBalance();
|
actualBalance = accountRepository.getBalance(generator.getAddress(), Asset.QORA).getBalance();
|
||||||
assertTrue(initialGeneratorBalance.compareTo(actualBalance) == 0, "Generator's new balance incorrect");
|
assertTrue("Generator's new balance incorrect", initialGeneratorBalance.compareTo(actualBalance) == 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -225,8 +207,8 @@ public class TransactionTests {
|
|||||||
|
|
||||||
BigDecimal fee = BigDecimal.ONE;
|
BigDecimal fee = BigDecimal.ONE;
|
||||||
long timestamp = parentBlockData.getTimestamp() + 1_000;
|
long timestamp = parentBlockData.getTimestamp() + 1_000;
|
||||||
RegisterNameTransactionData registerNameTransactionData = new RegisterNameTransactionData(timestamp, Group.NO_GROUP, reference, sender.getPublicKey(), sender.getAddress(),
|
RegisterNameTransactionData registerNameTransactionData = new RegisterNameTransactionData(timestamp, Group.NO_GROUP, reference, sender.getPublicKey(),
|
||||||
name, data, fee);
|
sender.getAddress(), name, data, fee);
|
||||||
|
|
||||||
Transaction registerNameTransaction = new RegisterNameTransaction(repository, registerNameTransactionData);
|
Transaction registerNameTransaction = new RegisterNameTransaction(repository, registerNameTransactionData);
|
||||||
registerNameTransaction.sign(sender);
|
registerNameTransaction.sign(sender);
|
||||||
@ -238,8 +220,8 @@ public class TransactionTests {
|
|||||||
block.addTransaction(registerNameTransactionData);
|
block.addTransaction(registerNameTransactionData);
|
||||||
block.sign();
|
block.sign();
|
||||||
|
|
||||||
assertTrue(block.isSignatureValid(), "Block signatures invalid");
|
assertTrue("Block signatures invalid", block.isSignatureValid());
|
||||||
assertEquals(Block.ValidationResult.OK, block.isValid(), "Block is invalid");
|
assertEquals("Block is invalid", Block.ValidationResult.OK, block.isValid());
|
||||||
|
|
||||||
block.process();
|
block.process();
|
||||||
repository.saveChanges();
|
repository.saveChanges();
|
||||||
@ -247,19 +229,19 @@ public class TransactionTests {
|
|||||||
// Check sender's balance
|
// Check sender's balance
|
||||||
BigDecimal expectedBalance = initialSenderBalance.subtract(fee);
|
BigDecimal expectedBalance = initialSenderBalance.subtract(fee);
|
||||||
BigDecimal actualBalance = accountRepository.getBalance(sender.getAddress(), Asset.QORA).getBalance();
|
BigDecimal actualBalance = accountRepository.getBalance(sender.getAddress(), Asset.QORA).getBalance();
|
||||||
assertTrue(expectedBalance.compareTo(actualBalance) == 0, "Sender's new balance incorrect");
|
assertTrue("Sender's new balance incorrect", expectedBalance.compareTo(actualBalance) == 0);
|
||||||
|
|
||||||
// Fee should be in generator's balance
|
// Fee should be in generator's balance
|
||||||
expectedBalance = initialGeneratorBalance.add(fee);
|
expectedBalance = initialGeneratorBalance.add(fee);
|
||||||
actualBalance = accountRepository.getBalance(generator.getAddress(), Asset.QORA).getBalance();
|
actualBalance = accountRepository.getBalance(generator.getAddress(), Asset.QORA).getBalance();
|
||||||
assertTrue(expectedBalance.compareTo(actualBalance) == 0, "Generator's new balance incorrect");
|
assertTrue("Generator's new balance incorrect", expectedBalance.compareTo(actualBalance) == 0);
|
||||||
|
|
||||||
// Check name was registered
|
// Check name was registered
|
||||||
NameData actualNameData = this.repository.getNameRepository().fromName(name);
|
NameData actualNameData = this.repository.getNameRepository().fromName(name);
|
||||||
assertNotNull(actualNameData);
|
assertNotNull(actualNameData);
|
||||||
|
|
||||||
// Check sender's reference
|
// Check sender's reference
|
||||||
assertTrue(Arrays.equals(registerNameTransactionData.getSignature(), sender.getLastReference()), "Sender's new reference incorrect");
|
assertTrue("Sender's new reference incorrect", Arrays.equals(registerNameTransactionData.getSignature(), sender.getLastReference()));
|
||||||
|
|
||||||
// Update variables for use by other tests
|
// Update variables for use by other tests
|
||||||
reference = sender.getLastReference();
|
reference = sender.getLastReference();
|
||||||
@ -294,8 +276,8 @@ public class TransactionTests {
|
|||||||
block.addTransaction(updateNameTransactionData);
|
block.addTransaction(updateNameTransactionData);
|
||||||
block.sign();
|
block.sign();
|
||||||
|
|
||||||
assertTrue(block.isSignatureValid(), "Block signatures invalid");
|
assertTrue("Block signatures invalid", block.isSignatureValid());
|
||||||
assertEquals(Block.ValidationResult.OK, block.isValid(), "Block is invalid");
|
assertEquals("Block is invalid", Block.ValidationResult.OK, block.isValid());
|
||||||
|
|
||||||
block.process();
|
block.process();
|
||||||
repository.saveChanges();
|
repository.saveChanges();
|
||||||
@ -327,7 +309,8 @@ public class TransactionTests {
|
|||||||
|
|
||||||
BigDecimal fee = BigDecimal.ONE;
|
BigDecimal fee = BigDecimal.ONE;
|
||||||
long timestamp = parentBlockData.getTimestamp() + 1_000;
|
long timestamp = parentBlockData.getTimestamp() + 1_000;
|
||||||
SellNameTransactionData sellNameTransactionData = new SellNameTransactionData(timestamp, Group.NO_GROUP, reference, sender.getPublicKey(), name, amount, fee);
|
SellNameTransactionData sellNameTransactionData = new SellNameTransactionData(timestamp, Group.NO_GROUP, reference, sender.getPublicKey(), name, amount,
|
||||||
|
fee);
|
||||||
|
|
||||||
Transaction sellNameTransaction = new SellNameTransaction(repository, sellNameTransactionData);
|
Transaction sellNameTransaction = new SellNameTransaction(repository, sellNameTransactionData);
|
||||||
sellNameTransaction.sign(sender);
|
sellNameTransaction.sign(sender);
|
||||||
@ -339,8 +322,8 @@ public class TransactionTests {
|
|||||||
block.addTransaction(sellNameTransactionData);
|
block.addTransaction(sellNameTransactionData);
|
||||||
block.sign();
|
block.sign();
|
||||||
|
|
||||||
assertTrue(block.isSignatureValid(), "Block signatures invalid");
|
assertTrue("Block signatures invalid", block.isSignatureValid());
|
||||||
assertEquals(Block.ValidationResult.OK, block.isValid(), "Block is invalid");
|
assertEquals("Block is invalid", Block.ValidationResult.OK, block.isValid());
|
||||||
|
|
||||||
block.process();
|
block.process();
|
||||||
repository.saveChanges();
|
repository.saveChanges();
|
||||||
@ -378,7 +361,8 @@ public class TransactionTests {
|
|||||||
|
|
||||||
BigDecimal fee = BigDecimal.ONE;
|
BigDecimal fee = BigDecimal.ONE;
|
||||||
long timestamp = parentBlockData.getTimestamp() + 1_000;
|
long timestamp = parentBlockData.getTimestamp() + 1_000;
|
||||||
CancelSellNameTransactionData cancelSellNameTransactionData = new CancelSellNameTransactionData(timestamp, Group.NO_GROUP, reference, sender.getPublicKey(), name, fee);
|
CancelSellNameTransactionData cancelSellNameTransactionData = new CancelSellNameTransactionData(timestamp, Group.NO_GROUP, reference,
|
||||||
|
sender.getPublicKey(), name, fee);
|
||||||
|
|
||||||
Transaction cancelSellNameTransaction = new CancelSellNameTransaction(repository, cancelSellNameTransactionData);
|
Transaction cancelSellNameTransaction = new CancelSellNameTransaction(repository, cancelSellNameTransactionData);
|
||||||
cancelSellNameTransaction.sign(sender);
|
cancelSellNameTransaction.sign(sender);
|
||||||
@ -390,8 +374,8 @@ public class TransactionTests {
|
|||||||
block.addTransaction(cancelSellNameTransactionData);
|
block.addTransaction(cancelSellNameTransactionData);
|
||||||
block.sign();
|
block.sign();
|
||||||
|
|
||||||
assertTrue(block.isSignatureValid(), "Block signatures invalid");
|
assertTrue("Block signatures invalid", block.isSignatureValid());
|
||||||
assertEquals(Block.ValidationResult.OK, block.isValid(), "Block is invalid");
|
assertEquals("Block is invalid", Block.ValidationResult.OK, block.isValid());
|
||||||
|
|
||||||
block.process();
|
block.process();
|
||||||
repository.saveChanges();
|
repository.saveChanges();
|
||||||
@ -443,8 +427,8 @@ public class TransactionTests {
|
|||||||
|
|
||||||
BigDecimal fee = BigDecimal.ONE;
|
BigDecimal fee = BigDecimal.ONE;
|
||||||
long timestamp = parentBlockData.getTimestamp() + 1_000;
|
long timestamp = parentBlockData.getTimestamp() + 1_000;
|
||||||
BuyNameTransactionData buyNameTransactionData = new BuyNameTransactionData(timestamp, Group.NO_GROUP, buyersReference, buyer.getPublicKey(),
|
BuyNameTransactionData buyNameTransactionData = new BuyNameTransactionData(timestamp, Group.NO_GROUP, buyersReference, buyer.getPublicKey(), name,
|
||||||
name, originalNameData.getSalePrice(), seller, nameReference, fee);
|
originalNameData.getSalePrice(), seller, nameReference, fee);
|
||||||
|
|
||||||
Transaction buyNameTransaction = new BuyNameTransaction(repository, buyNameTransactionData);
|
Transaction buyNameTransaction = new BuyNameTransaction(repository, buyNameTransactionData);
|
||||||
buyNameTransaction.sign(buyer);
|
buyNameTransaction.sign(buyer);
|
||||||
@ -456,8 +440,8 @@ public class TransactionTests {
|
|||||||
block.addTransaction(buyNameTransactionData);
|
block.addTransaction(buyNameTransactionData);
|
||||||
block.sign();
|
block.sign();
|
||||||
|
|
||||||
assertTrue(block.isSignatureValid(), "Block signatures invalid");
|
assertTrue("Block signatures invalid", block.isSignatureValid());
|
||||||
assertEquals(Block.ValidationResult.OK, block.isValid(), "Block is invalid");
|
assertEquals("Block is invalid", Block.ValidationResult.OK, block.isValid());
|
||||||
|
|
||||||
block.process();
|
block.process();
|
||||||
repository.saveChanges();
|
repository.saveChanges();
|
||||||
@ -496,8 +480,8 @@ public class TransactionTests {
|
|||||||
Account recipient = new PublicKeyAccount(repository, recipientSeed);
|
Account recipient = new PublicKeyAccount(repository, recipientSeed);
|
||||||
BigDecimal fee = BigDecimal.ONE;
|
BigDecimal fee = BigDecimal.ONE;
|
||||||
long timestamp = parentBlockData.getTimestamp() + 1_000;
|
long timestamp = parentBlockData.getTimestamp() + 1_000;
|
||||||
CreatePollTransactionData createPollTransactionData = new CreatePollTransactionData(timestamp, Group.NO_GROUP, reference,
|
CreatePollTransactionData createPollTransactionData = new CreatePollTransactionData(timestamp, Group.NO_GROUP, reference, sender.getPublicKey(),
|
||||||
sender.getPublicKey(), recipient.getAddress(), pollName, description, pollOptions, fee);
|
recipient.getAddress(), pollName, description, pollOptions, fee);
|
||||||
|
|
||||||
Transaction createPollTransaction = new CreatePollTransaction(repository, createPollTransactionData);
|
Transaction createPollTransaction = new CreatePollTransaction(repository, createPollTransactionData);
|
||||||
createPollTransaction.sign(sender);
|
createPollTransaction.sign(sender);
|
||||||
@ -509,8 +493,8 @@ public class TransactionTests {
|
|||||||
block.addTransaction(createPollTransactionData);
|
block.addTransaction(createPollTransactionData);
|
||||||
block.sign();
|
block.sign();
|
||||||
|
|
||||||
assertTrue(block.isSignatureValid(), "Block signatures invalid");
|
assertTrue("Block signatures invalid", block.isSignatureValid());
|
||||||
assertEquals(Block.ValidationResult.OK, block.isValid(), "Block is invalid");
|
assertEquals("Block is invalid", Block.ValidationResult.OK, block.isValid());
|
||||||
|
|
||||||
block.process();
|
block.process();
|
||||||
repository.saveChanges();
|
repository.saveChanges();
|
||||||
@ -518,19 +502,19 @@ public class TransactionTests {
|
|||||||
// Check sender's balance
|
// Check sender's balance
|
||||||
BigDecimal expectedBalance = initialSenderBalance.subtract(fee);
|
BigDecimal expectedBalance = initialSenderBalance.subtract(fee);
|
||||||
BigDecimal actualBalance = accountRepository.getBalance(sender.getAddress(), Asset.QORA).getBalance();
|
BigDecimal actualBalance = accountRepository.getBalance(sender.getAddress(), Asset.QORA).getBalance();
|
||||||
assertTrue(expectedBalance.compareTo(actualBalance) == 0, "Sender's new balance incorrect");
|
assertTrue("Sender's new balance incorrect", expectedBalance.compareTo(actualBalance) == 0);
|
||||||
|
|
||||||
// Fee should be in generator's balance
|
// Fee should be in generator's balance
|
||||||
expectedBalance = initialGeneratorBalance.add(fee);
|
expectedBalance = initialGeneratorBalance.add(fee);
|
||||||
actualBalance = accountRepository.getBalance(generator.getAddress(), Asset.QORA).getBalance();
|
actualBalance = accountRepository.getBalance(generator.getAddress(), Asset.QORA).getBalance();
|
||||||
assertTrue(expectedBalance.compareTo(actualBalance) == 0, "Generator's new balance incorrect");
|
assertTrue("Generator's new balance incorrect", expectedBalance.compareTo(actualBalance) == 0);
|
||||||
|
|
||||||
// Check poll was created
|
// Check poll was created
|
||||||
PollData actualPollData = this.repository.getVotingRepository().fromPollName(pollName);
|
PollData actualPollData = this.repository.getVotingRepository().fromPollName(pollName);
|
||||||
assertNotNull(actualPollData);
|
assertNotNull(actualPollData);
|
||||||
|
|
||||||
// Check sender's reference
|
// Check sender's reference
|
||||||
assertTrue(Arrays.equals(createPollTransactionData.getSignature(), sender.getLastReference()), "Sender's new reference incorrect");
|
assertTrue("Sender's new reference incorrect", Arrays.equals(createPollTransactionData.getSignature(), sender.getLastReference()));
|
||||||
|
|
||||||
// Update variables for use by other tests
|
// Update variables for use by other tests
|
||||||
reference = sender.getLastReference();
|
reference = sender.getLastReference();
|
||||||
@ -550,8 +534,8 @@ public class TransactionTests {
|
|||||||
|
|
||||||
for (int optionIndex = 0; optionIndex <= pollOptionsSize; ++optionIndex) {
|
for (int optionIndex = 0; optionIndex <= pollOptionsSize; ++optionIndex) {
|
||||||
// Make a vote-on-poll transaction
|
// Make a vote-on-poll transaction
|
||||||
VoteOnPollTransactionData voteOnPollTransactionData = new VoteOnPollTransactionData(timestamp, Group.NO_GROUP, reference, sender.getPublicKey(), pollName,
|
VoteOnPollTransactionData voteOnPollTransactionData = new VoteOnPollTransactionData(timestamp, Group.NO_GROUP, reference, sender.getPublicKey(),
|
||||||
optionIndex, fee);
|
pollName, optionIndex, fee);
|
||||||
|
|
||||||
Transaction voteOnPollTransaction = new VoteOnPollTransaction(repository, voteOnPollTransactionData);
|
Transaction voteOnPollTransaction = new VoteOnPollTransaction(repository, voteOnPollTransactionData);
|
||||||
voteOnPollTransaction.sign(sender);
|
voteOnPollTransaction.sign(sender);
|
||||||
@ -568,8 +552,8 @@ public class TransactionTests {
|
|||||||
block.addTransaction(voteOnPollTransactionData);
|
block.addTransaction(voteOnPollTransactionData);
|
||||||
block.sign();
|
block.sign();
|
||||||
|
|
||||||
assertTrue(block.isSignatureValid(), "Block signatures invalid");
|
assertTrue("Block signatures invalid", block.isSignatureValid());
|
||||||
assertEquals(Block.ValidationResult.OK, block.isValid(), "Block is invalid");
|
assertEquals("Block is invalid", Block.ValidationResult.OK, block.isValid());
|
||||||
|
|
||||||
block.process();
|
block.process();
|
||||||
repository.saveChanges();
|
repository.saveChanges();
|
||||||
@ -589,10 +573,10 @@ public class TransactionTests {
|
|||||||
List<VoteOnPollData> votes = repository.getVotingRepository().getVotes(pollName);
|
List<VoteOnPollData> votes = repository.getVotingRepository().getVotes(pollName);
|
||||||
assertNotNull(votes);
|
assertNotNull(votes);
|
||||||
|
|
||||||
assertEquals(1, votes.size(), "Only one vote expected");
|
assertEquals("Only one vote expected", 1, votes.size());
|
||||||
|
|
||||||
assertEquals(pollOptionsSize - 1, votes.get(0).getOptionIndex(), "Wrong vote option index");
|
assertEquals("Wrong vote option index", pollOptionsSize - 1, votes.get(0).getOptionIndex());
|
||||||
assertTrue(Arrays.equals(sender.getPublicKey(), votes.get(0).getVoterPublicKey()), "Wrong voter public key");
|
assertTrue("Wrong voter public key", Arrays.equals(sender.getPublicKey(), votes.get(0).getVoterPublicKey()));
|
||||||
|
|
||||||
// Orphan last block
|
// Orphan last block
|
||||||
BlockData lastBlockData = repository.getBlockRepository().getLastBlock();
|
BlockData lastBlockData = repository.getBlockRepository().getLastBlock();
|
||||||
@ -604,10 +588,10 @@ public class TransactionTests {
|
|||||||
votes = repository.getVotingRepository().getVotes(pollName);
|
votes = repository.getVotingRepository().getVotes(pollName);
|
||||||
assertNotNull(votes);
|
assertNotNull(votes);
|
||||||
|
|
||||||
assertEquals(1, votes.size(), "Only one vote expected");
|
assertEquals("Only one vote expected", 1, votes.size());
|
||||||
|
|
||||||
assertEquals(pollOptionsSize - 1 - 1, votes.get(0).getOptionIndex(), "Wrong vote option index");
|
assertEquals("Wrong vote option index", pollOptionsSize - 1 - 1, votes.get(0).getOptionIndex());
|
||||||
assertTrue(Arrays.equals(sender.getPublicKey(), votes.get(0).getVoterPublicKey()), "Wrong voter public key");
|
assertTrue("Wrong voter public key", Arrays.equals(sender.getPublicKey(), votes.get(0).getVoterPublicKey()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -635,8 +619,8 @@ public class TransactionTests {
|
|||||||
block.addTransaction(issueAssetTransactionData);
|
block.addTransaction(issueAssetTransactionData);
|
||||||
block.sign();
|
block.sign();
|
||||||
|
|
||||||
assertTrue(block.isSignatureValid(), "Block signatures invalid");
|
assertTrue("Block signatures invalid", block.isSignatureValid());
|
||||||
assertEquals(Block.ValidationResult.OK, block.isValid(), "Block is invalid");
|
assertEquals("Block is invalid", Block.ValidationResult.OK, block.isValid());
|
||||||
|
|
||||||
block.process();
|
block.process();
|
||||||
repository.saveChanges();
|
repository.saveChanges();
|
||||||
@ -644,12 +628,12 @@ public class TransactionTests {
|
|||||||
// Check sender's balance
|
// Check sender's balance
|
||||||
BigDecimal expectedBalance = initialSenderBalance.subtract(fee);
|
BigDecimal expectedBalance = initialSenderBalance.subtract(fee);
|
||||||
BigDecimal actualBalance = accountRepository.getBalance(sender.getAddress(), Asset.QORA).getBalance();
|
BigDecimal actualBalance = accountRepository.getBalance(sender.getAddress(), Asset.QORA).getBalance();
|
||||||
assertTrue(expectedBalance.compareTo(actualBalance) == 0, "Sender's new balance incorrect");
|
assertTrue("Sender's new balance incorrect", expectedBalance.compareTo(actualBalance) == 0);
|
||||||
|
|
||||||
// Fee should be in generator's balance
|
// Fee should be in generator's balance
|
||||||
expectedBalance = initialGeneratorBalance.add(fee);
|
expectedBalance = initialGeneratorBalance.add(fee);
|
||||||
actualBalance = accountRepository.getBalance(generator.getAddress(), Asset.QORA).getBalance();
|
actualBalance = accountRepository.getBalance(generator.getAddress(), Asset.QORA).getBalance();
|
||||||
assertTrue(expectedBalance.compareTo(actualBalance) == 0, "Generator's new balance incorrect");
|
assertTrue("Generator's new balance incorrect", expectedBalance.compareTo(actualBalance) == 0);
|
||||||
|
|
||||||
// Check we now have an assetId
|
// Check we now have an assetId
|
||||||
Long assetId = issueAssetTransactionData.getAssetId();
|
Long assetId = issueAssetTransactionData.getAssetId();
|
||||||
@ -673,11 +657,11 @@ public class TransactionTests {
|
|||||||
|
|
||||||
// Check sender's balance
|
// Check sender's balance
|
||||||
actualBalance = accountRepository.getBalance(sender.getAddress(), Asset.QORA).getBalance();
|
actualBalance = accountRepository.getBalance(sender.getAddress(), Asset.QORA).getBalance();
|
||||||
assertTrue(initialSenderBalance.compareTo(actualBalance) == 0, "Sender's reverted balance incorrect");
|
assertTrue("Sender's reverted balance incorrect", initialSenderBalance.compareTo(actualBalance) == 0);
|
||||||
|
|
||||||
// Check generator's balance
|
// Check generator's balance
|
||||||
actualBalance = accountRepository.getBalance(generator.getAddress(), Asset.QORA).getBalance();
|
actualBalance = accountRepository.getBalance(generator.getAddress(), Asset.QORA).getBalance();
|
||||||
assertTrue(initialGeneratorBalance.compareTo(actualBalance) == 0, "Generator's reverted balance incorrect");
|
assertTrue("Generator's reverted balance incorrect", initialGeneratorBalance.compareTo(actualBalance) == 0);
|
||||||
|
|
||||||
// Check asset no longer exists
|
// Check asset no longer exists
|
||||||
assertFalse(assetRepo.assetExists(assetId));
|
assertFalse(assetRepo.assetExists(assetId));
|
||||||
@ -725,8 +709,8 @@ public class TransactionTests {
|
|||||||
block.addTransaction(transferAssetTransactionData);
|
block.addTransaction(transferAssetTransactionData);
|
||||||
block.sign();
|
block.sign();
|
||||||
|
|
||||||
assertTrue(block.isSignatureValid(), "Block signatures invalid");
|
assertTrue("Block signatures invalid", block.isSignatureValid());
|
||||||
assertEquals(Block.ValidationResult.OK, block.isValid(), "Block is invalid");
|
assertEquals("Block is invalid", Block.ValidationResult.OK, block.isValid());
|
||||||
|
|
||||||
block.process();
|
block.process();
|
||||||
repository.saveChanges();
|
repository.saveChanges();
|
||||||
@ -734,12 +718,12 @@ public class TransactionTests {
|
|||||||
// Check sender's balance
|
// Check sender's balance
|
||||||
BigDecimal expectedBalance = originalSenderBalance.subtract(fee);
|
BigDecimal expectedBalance = originalSenderBalance.subtract(fee);
|
||||||
BigDecimal actualBalance = accountRepository.getBalance(sender.getAddress(), Asset.QORA).getBalance();
|
BigDecimal actualBalance = accountRepository.getBalance(sender.getAddress(), Asset.QORA).getBalance();
|
||||||
assertTrue(expectedBalance.compareTo(actualBalance) == 0, "Sender's new balance incorrect");
|
assertTrue("Sender's new balance incorrect", expectedBalance.compareTo(actualBalance) == 0);
|
||||||
|
|
||||||
// Fee should be in generator's balance
|
// Fee should be in generator's balance
|
||||||
expectedBalance = originalGeneratorBalance.add(fee);
|
expectedBalance = originalGeneratorBalance.add(fee);
|
||||||
actualBalance = accountRepository.getBalance(generator.getAddress(), Asset.QORA).getBalance();
|
actualBalance = accountRepository.getBalance(generator.getAddress(), Asset.QORA).getBalance();
|
||||||
assertTrue(expectedBalance.compareTo(actualBalance) == 0, "Generator's new balance incorrect");
|
assertTrue("Generator's new balance incorrect", expectedBalance.compareTo(actualBalance) == 0);
|
||||||
|
|
||||||
// Check asset balances
|
// Check asset balances
|
||||||
BigDecimal actualSenderAssetBalance = sender.getConfirmedBalance(assetId);
|
BigDecimal actualSenderAssetBalance = sender.getConfirmedBalance(assetId);
|
||||||
@ -757,11 +741,11 @@ public class TransactionTests {
|
|||||||
|
|
||||||
// Check sender's balance
|
// Check sender's balance
|
||||||
actualBalance = accountRepository.getBalance(sender.getAddress(), Asset.QORA).getBalance();
|
actualBalance = accountRepository.getBalance(sender.getAddress(), Asset.QORA).getBalance();
|
||||||
assertTrue(originalSenderBalance.compareTo(actualBalance) == 0, "Sender's reverted balance incorrect");
|
assertTrue("Sender's reverted balance incorrect", originalSenderBalance.compareTo(actualBalance) == 0);
|
||||||
|
|
||||||
// Check generator's balance
|
// Check generator's balance
|
||||||
actualBalance = accountRepository.getBalance(generator.getAddress(), Asset.QORA).getBalance();
|
actualBalance = accountRepository.getBalance(generator.getAddress(), Asset.QORA).getBalance();
|
||||||
assertTrue(originalGeneratorBalance.compareTo(actualBalance) == 0, "Generator's reverted balance incorrect");
|
assertTrue("Generator's reverted balance incorrect", originalGeneratorBalance.compareTo(actualBalance) == 0);
|
||||||
|
|
||||||
// Check asset balances
|
// Check asset balances
|
||||||
actualSenderAssetBalance = sender.getConfirmedBalance(assetId);
|
actualSenderAssetBalance = sender.getConfirmedBalance(assetId);
|
||||||
@ -817,8 +801,8 @@ public class TransactionTests {
|
|||||||
BigDecimal fee = BigDecimal.ONE;
|
BigDecimal fee = BigDecimal.ONE;
|
||||||
long timestamp = parentBlockData.getTimestamp() + 1_000;
|
long timestamp = parentBlockData.getTimestamp() + 1_000;
|
||||||
|
|
||||||
CreateAssetOrderTransactionData createOrderTransactionData = new CreateAssetOrderTransactionData(timestamp, Group.NO_GROUP, buyersReference, buyer.getPublicKey(), haveAssetId,
|
CreateAssetOrderTransactionData createOrderTransactionData = new CreateAssetOrderTransactionData(timestamp, Group.NO_GROUP, buyersReference,
|
||||||
wantAssetId, amount, price, fee);
|
buyer.getPublicKey(), haveAssetId, wantAssetId, amount, price, fee);
|
||||||
Transaction createOrderTransaction = new CreateAssetOrderTransaction(this.repository, createOrderTransactionData);
|
Transaction createOrderTransaction = new CreateAssetOrderTransaction(this.repository, createOrderTransactionData);
|
||||||
createOrderTransaction.sign(buyer);
|
createOrderTransaction.sign(buyer);
|
||||||
assertTrue(createOrderTransaction.isSignatureValid());
|
assertTrue(createOrderTransaction.isSignatureValid());
|
||||||
@ -829,8 +813,8 @@ public class TransactionTests {
|
|||||||
block.addTransaction(createOrderTransactionData);
|
block.addTransaction(createOrderTransactionData);
|
||||||
block.sign();
|
block.sign();
|
||||||
|
|
||||||
assertTrue(block.isSignatureValid(), "Block signatures invalid");
|
assertTrue("Block signatures invalid", block.isSignatureValid());
|
||||||
assertEquals(Block.ValidationResult.OK, block.isValid(), "Block is invalid");
|
assertEquals("Block is invalid", Block.ValidationResult.OK, block.isValid());
|
||||||
|
|
||||||
block.process();
|
block.process();
|
||||||
repository.saveChanges();
|
repository.saveChanges();
|
||||||
@ -898,7 +882,8 @@ public class TransactionTests {
|
|||||||
BigDecimal fee = BigDecimal.ONE;
|
BigDecimal fee = BigDecimal.ONE;
|
||||||
long timestamp = parentBlockData.getTimestamp() + 1_000;
|
long timestamp = parentBlockData.getTimestamp() + 1_000;
|
||||||
byte[] buyersReference = buyer.getLastReference();
|
byte[] buyersReference = buyer.getLastReference();
|
||||||
CancelAssetOrderTransactionData cancelOrderTransactionData = new CancelAssetOrderTransactionData(timestamp, Group.NO_GROUP, buyersReference, buyer.getPublicKey(), orderId, fee);
|
CancelAssetOrderTransactionData cancelOrderTransactionData = new CancelAssetOrderTransactionData(timestamp, Group.NO_GROUP, buyersReference,
|
||||||
|
buyer.getPublicKey(), orderId, fee);
|
||||||
|
|
||||||
Transaction cancelOrderTransaction = new CancelAssetOrderTransaction(this.repository, cancelOrderTransactionData);
|
Transaction cancelOrderTransaction = new CancelAssetOrderTransaction(this.repository, cancelOrderTransactionData);
|
||||||
cancelOrderTransaction.sign(buyer);
|
cancelOrderTransaction.sign(buyer);
|
||||||
@ -910,8 +895,8 @@ public class TransactionTests {
|
|||||||
block.addTransaction(cancelOrderTransactionData);
|
block.addTransaction(cancelOrderTransactionData);
|
||||||
block.sign();
|
block.sign();
|
||||||
|
|
||||||
assertTrue(block.isSignatureValid(), "Block signatures invalid");
|
assertTrue("Block signatures invalid", block.isSignatureValid());
|
||||||
assertEquals(Block.ValidationResult.OK, block.isValid(), "Block is invalid");
|
assertEquals("Block is invalid", Block.ValidationResult.OK, block.isValid());
|
||||||
|
|
||||||
block.process();
|
block.process();
|
||||||
repository.saveChanges();
|
repository.saveChanges();
|
||||||
@ -973,8 +958,8 @@ public class TransactionTests {
|
|||||||
long timestamp = parentBlockData.getTimestamp() + 1_000;
|
long timestamp = parentBlockData.getTimestamp() + 1_000;
|
||||||
BigDecimal senderPreTradeWantBalance = sender.getConfirmedBalance(wantAssetId);
|
BigDecimal senderPreTradeWantBalance = sender.getConfirmedBalance(wantAssetId);
|
||||||
|
|
||||||
CreateAssetOrderTransactionData createOrderTransactionData = new CreateAssetOrderTransactionData(timestamp, Group.NO_GROUP, reference, sender.getPublicKey(), haveAssetId,
|
CreateAssetOrderTransactionData createOrderTransactionData = new CreateAssetOrderTransactionData(timestamp, Group.NO_GROUP, reference,
|
||||||
wantAssetId, amount, price, fee);
|
sender.getPublicKey(), haveAssetId, wantAssetId, amount, price, fee);
|
||||||
Transaction createOrderTransaction = new CreateAssetOrderTransaction(this.repository, createOrderTransactionData);
|
Transaction createOrderTransaction = new CreateAssetOrderTransaction(this.repository, createOrderTransactionData);
|
||||||
createOrderTransaction.sign(sender);
|
createOrderTransaction.sign(sender);
|
||||||
assertTrue(createOrderTransaction.isSignatureValid());
|
assertTrue(createOrderTransaction.isSignatureValid());
|
||||||
@ -985,8 +970,8 @@ public class TransactionTests {
|
|||||||
block.addTransaction(createOrderTransactionData);
|
block.addTransaction(createOrderTransactionData);
|
||||||
block.sign();
|
block.sign();
|
||||||
|
|
||||||
assertTrue(block.isSignatureValid(), "Block signatures invalid");
|
assertTrue("Block signatures invalid", block.isSignatureValid());
|
||||||
assertEquals(Block.ValidationResult.OK, block.isValid(), "Block is invalid");
|
assertEquals("Block is invalid", Block.ValidationResult.OK, block.isValid());
|
||||||
|
|
||||||
block.process();
|
block.process();
|
||||||
repository.saveChanges();
|
repository.saveChanges();
|
||||||
@ -999,7 +984,7 @@ public class TransactionTests {
|
|||||||
// Check order has trades
|
// Check order has trades
|
||||||
List<TradeData> trades = assetRepo.getOrdersTrades(orderId);
|
List<TradeData> trades = assetRepo.getOrdersTrades(orderId);
|
||||||
assertNotNull(trades);
|
assertNotNull(trades);
|
||||||
assertEquals(1, trades.size(), "Trade didn't happen");
|
assertEquals("Trade didn't happen", 1, trades.size());
|
||||||
TradeData tradeData = trades.get(0);
|
TradeData tradeData = trades.get(0);
|
||||||
|
|
||||||
// Check trade has correct values
|
// Check trade has correct values
|
||||||
@ -1082,7 +1067,8 @@ public class TransactionTests {
|
|||||||
payments.add(paymentData);
|
payments.add(paymentData);
|
||||||
}
|
}
|
||||||
|
|
||||||
MultiPaymentTransactionData multiPaymentTransactionData = new MultiPaymentTransactionData(timestamp, Group.NO_GROUP, reference, sender.getPublicKey(), payments, fee);
|
MultiPaymentTransactionData multiPaymentTransactionData = new MultiPaymentTransactionData(timestamp, Group.NO_GROUP, reference, sender.getPublicKey(),
|
||||||
|
payments, fee);
|
||||||
|
|
||||||
Transaction multiPaymentTransaction = new MultiPaymentTransaction(repository, multiPaymentTransactionData);
|
Transaction multiPaymentTransaction = new MultiPaymentTransaction(repository, multiPaymentTransactionData);
|
||||||
multiPaymentTransaction.sign(sender);
|
multiPaymentTransaction.sign(sender);
|
||||||
@ -1094,20 +1080,20 @@ public class TransactionTests {
|
|||||||
block.addTransaction(multiPaymentTransactionData);
|
block.addTransaction(multiPaymentTransactionData);
|
||||||
block.sign();
|
block.sign();
|
||||||
|
|
||||||
assertTrue(block.isSignatureValid(), "Block signatures invalid");
|
assertTrue("Block signatures invalid", block.isSignatureValid());
|
||||||
assertEquals(Block.ValidationResult.OK, block.isValid(), "Block is invalid");
|
assertEquals("Block is invalid", Block.ValidationResult.OK, block.isValid());
|
||||||
|
|
||||||
block.process();
|
block.process();
|
||||||
repository.saveChanges();
|
repository.saveChanges();
|
||||||
|
|
||||||
// Check sender's balance
|
// Check sender's balance
|
||||||
BigDecimal actualBalance = accountRepository.getBalance(sender.getAddress(), Asset.QORA).getBalance();
|
BigDecimal actualBalance = accountRepository.getBalance(sender.getAddress(), Asset.QORA).getBalance();
|
||||||
assertTrue(expectedSenderBalance.compareTo(actualBalance) == 0, "Sender's new balance incorrect");
|
assertTrue("Sender's new balance incorrect", expectedSenderBalance.compareTo(actualBalance) == 0);
|
||||||
|
|
||||||
// Fee should be in generator's balance
|
// Fee should be in generator's balance
|
||||||
BigDecimal expectedBalance = initialGeneratorBalance.add(fee);
|
BigDecimal expectedBalance = initialGeneratorBalance.add(fee);
|
||||||
actualBalance = accountRepository.getBalance(generator.getAddress(), Asset.QORA).getBalance();
|
actualBalance = accountRepository.getBalance(generator.getAddress(), Asset.QORA).getBalance();
|
||||||
assertTrue(expectedBalance.compareTo(actualBalance) == 0, "Generator's new balance incorrect");
|
assertTrue("Generator's new balance incorrect", expectedBalance.compareTo(actualBalance) == 0);
|
||||||
|
|
||||||
// Check recipients
|
// Check recipients
|
||||||
for (int i = 0; i < payments.size(); ++i) {
|
for (int i = 0; i < payments.size(); ++i) {
|
||||||
@ -1115,12 +1101,12 @@ public class TransactionTests {
|
|||||||
Account recipient = new Account(this.repository, paymentData.getRecipient());
|
Account recipient = new Account(this.repository, paymentData.getRecipient());
|
||||||
|
|
||||||
byte[] recipientsReference = recipient.getLastReference();
|
byte[] recipientsReference = recipient.getLastReference();
|
||||||
assertTrue(Arrays.equals(multiPaymentTransaction.getTransactionData().getSignature(), recipientsReference), "Recipient's new reference incorrect");
|
assertTrue("Recipient's new reference incorrect", Arrays.equals(multiPaymentTransaction.getTransactionData().getSignature(), recipientsReference));
|
||||||
|
|
||||||
// Amount should be in recipient's balance
|
// Amount should be in recipient's balance
|
||||||
expectedBalance = paymentData.getAmount();
|
expectedBalance = paymentData.getAmount();
|
||||||
actualBalance = accountRepository.getBalance(recipient.getAddress(), Asset.QORA).getBalance();
|
actualBalance = accountRepository.getBalance(recipient.getAddress(), Asset.QORA).getBalance();
|
||||||
assertTrue(expectedBalance.compareTo(actualBalance) == 0, "Recipient's new balance incorrect");
|
assertTrue("Recipient's new balance incorrect", expectedBalance.compareTo(actualBalance) == 0);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1130,11 +1116,11 @@ public class TransactionTests {
|
|||||||
|
|
||||||
// Check sender's balance
|
// Check sender's balance
|
||||||
actualBalance = accountRepository.getBalance(sender.getAddress(), Asset.QORA).getBalance();
|
actualBalance = accountRepository.getBalance(sender.getAddress(), Asset.QORA).getBalance();
|
||||||
assertTrue(initialSenderBalance.compareTo(actualBalance) == 0, "Sender's reverted balance incorrect");
|
assertTrue("Sender's reverted balance incorrect", initialSenderBalance.compareTo(actualBalance) == 0);
|
||||||
|
|
||||||
// Check generator's balance
|
// Check generator's balance
|
||||||
actualBalance = accountRepository.getBalance(generator.getAddress(), Asset.QORA).getBalance();
|
actualBalance = accountRepository.getBalance(generator.getAddress(), Asset.QORA).getBalance();
|
||||||
assertTrue(initialGeneratorBalance.compareTo(actualBalance) == 0, "Generator's new balance incorrect");
|
assertTrue("Generator's new balance incorrect", initialGeneratorBalance.compareTo(actualBalance) == 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -1164,8 +1150,8 @@ public class TransactionTests {
|
|||||||
block.addTransaction(messageTransactionData);
|
block.addTransaction(messageTransactionData);
|
||||||
block.sign();
|
block.sign();
|
||||||
|
|
||||||
assertTrue(block.isSignatureValid(), "Block signatures invalid");
|
assertTrue("Block signatures invalid", block.isSignatureValid());
|
||||||
assertEquals(Block.ValidationResult.OK, block.isValid(), "Block is invalid");
|
assertEquals("Block is invalid", Block.ValidationResult.OK, block.isValid());
|
||||||
|
|
||||||
block.process();
|
block.process();
|
||||||
repository.saveChanges();
|
repository.saveChanges();
|
||||||
@ -1173,17 +1159,17 @@ public class TransactionTests {
|
|||||||
// Check sender's balance
|
// Check sender's balance
|
||||||
BigDecimal expectedBalance = initialSenderBalance.subtract(amount).subtract(fee);
|
BigDecimal expectedBalance = initialSenderBalance.subtract(amount).subtract(fee);
|
||||||
BigDecimal actualBalance = accountRepository.getBalance(sender.getAddress(), Asset.QORA).getBalance();
|
BigDecimal actualBalance = accountRepository.getBalance(sender.getAddress(), Asset.QORA).getBalance();
|
||||||
assertTrue(expectedBalance.compareTo(actualBalance) == 0, "Sender's new balance incorrect");
|
assertTrue("Sender's new balance incorrect", expectedBalance.compareTo(actualBalance) == 0);
|
||||||
|
|
||||||
// Fee should be in generator's balance
|
// Fee should be in generator's balance
|
||||||
expectedBalance = initialGeneratorBalance.add(fee);
|
expectedBalance = initialGeneratorBalance.add(fee);
|
||||||
actualBalance = accountRepository.getBalance(generator.getAddress(), Asset.QORA).getBalance();
|
actualBalance = accountRepository.getBalance(generator.getAddress(), Asset.QORA).getBalance();
|
||||||
assertTrue(expectedBalance.compareTo(actualBalance) == 0, "Generator's new balance incorrect");
|
assertTrue("Generator's new balance incorrect", expectedBalance.compareTo(actualBalance) == 0);
|
||||||
|
|
||||||
// Amount should be in recipient's balance
|
// Amount should be in recipient's balance
|
||||||
expectedBalance = amount;
|
expectedBalance = amount;
|
||||||
actualBalance = accountRepository.getBalance(recipient.getAddress(), Asset.QORA).getBalance();
|
actualBalance = accountRepository.getBalance(recipient.getAddress(), Asset.QORA).getBalance();
|
||||||
assertTrue(expectedBalance.compareTo(actualBalance) == 0, "Recipient's new balance incorrect");
|
assertTrue("Recipient's new balance incorrect", expectedBalance.compareTo(actualBalance) == 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -1,18 +0,0 @@
|
|||||||
package org.qora.test.utils;
|
|
||||||
|
|
||||||
import java.util.Collection;
|
|
||||||
|
|
||||||
import static org.hamcrest.collection.IsIterableContainingInAnyOrder.containsInAnyOrder;
|
|
||||||
import static org.hamcrest.MatcherAssert.assertThat;
|
|
||||||
|
|
||||||
public class AssertExtensions {
|
|
||||||
|
|
||||||
public static <T> void assertItemsEqual(Collection<T> expected, Iterable<T> actual) {
|
|
||||||
assertItemsEqual(expected, actual, (String) null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static <T> void assertItemsEqual(Collection<T> expected, Iterable<T> actual, String message) {
|
|
||||||
assertThat(message, actual, containsInAnyOrder(expected.toArray()));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
6
src/test/resources/test-settings.json
Normal file
6
src/test/resources/test-settings.json
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"restrictedApi": false,
|
||||||
|
"blockchainConfig": "src/test/resources/test-v2qorachain.json",
|
||||||
|
"wipeUnconfirmedOnStart": false,
|
||||||
|
"minPeers": 0
|
||||||
|
}
|
31
src/test/resources/test-v2qorachain.json
Normal file
31
src/test/resources/test-v2qorachain.json
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
{
|
||||||
|
"isTestNet": true,
|
||||||
|
"maxBalance": "10000000000",
|
||||||
|
"blockDifficultyInterval": 10,
|
||||||
|
"minBlockTime": 30000,
|
||||||
|
"maxBlockTime": 60000,
|
||||||
|
"blockTimestampMargin": 500,
|
||||||
|
"maxBytesPerUnitFee": 1024,
|
||||||
|
"unitFee": "0.1",
|
||||||
|
"requireGroupForApproval": true,
|
||||||
|
"defaultGroupId": 2,
|
||||||
|
"genesisInfo": {
|
||||||
|
"version": 4,
|
||||||
|
"timestamp": 0,
|
||||||
|
"generatingBalance": "10000000",
|
||||||
|
"transactions": [
|
||||||
|
{ "type": "ISSUE_ASSET", "owner": "QcFmNxSArv5tWEzCtTKb2Lqc5QkKuQ7RNs", "assetName": "QORA", "description": "QORA native coin", "quantity": 10000000000, "isDivisible": true, "fee": 0, "reference": "3Verk6ZKBJc3WTTVfxFC9icSjKdM8b92eeJEpJP8qNizG4ZszNFq8wdDYdSjJXq2iogDFR1njyhsBdVpbvDfjzU7" },
|
||||||
|
{ "type": "GENESIS", "recipient": "QgV4s3xnzLhVBEJxcYui4u4q11yhUHsd9v", "amount": "9876543210.12345678", "fee": 0 },
|
||||||
|
{ "type": "CREATE_GROUP", "creatorPublicKey": "2tiMr5LTpaWCgbRvkPK8TFd7k63DyHJMMFFsz9uBf1ZP", "owner": "QgV4s3xnzLhVBEJxcYui4u4q11yhUHsd9v", "groupName": "dev-group", "description": "developer group", "isOpen": false, "approvalThreshold": "PCT100", "minimumBlockDelay": 0, "maximumBlockDelay": 1440 }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"featureTriggers": {
|
||||||
|
"messageHeight": 0,
|
||||||
|
"atHeight": 0,
|
||||||
|
"assetsTimestamp": 0,
|
||||||
|
"votingTimestamp": 0,
|
||||||
|
"arbitraryTimestamp": 0,
|
||||||
|
"powfixTimestamp": 0,
|
||||||
|
"v2Timestamp": 0
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user