From 3ec3c69447cc76df830da2f35622b0f19ec4e71f Mon Sep 17 00:00:00 2001 From: catbref Date: Thu, 13 Dec 2018 17:25:47 +0000 Subject: [PATCH] API: wholesale conversion back to Base58 XmlJavaTypeAdapter api.Base58TypeAdapter converts byte[] to Base58. This XmlAdapter is applied at package-level to all packages inside data and api.models. So no need to annotate every byte[] property! Added package-info-maven-plugin to pom.xml to do this. block-explorer.html fixed to show/use base58 again Some data objects (e.g. TransactionData) have added XmlElements that convert public keys to addresses, for convenience. Several API calls updated to return specifically text/plain instead of ambiguous application/json and/or text/plain. (Typically API calls that return a single value, e.g. an integer, like /blocks/height). --- .classpath | 5 +- .settings/org.eclipse.jdt.core.prefs | 16 +- block-explorer.html | 31 +- pom.xml | 40 +- src/api/AddressesResource.java | 30 +- src/api/ApiDefinition.java | 2 +- src/api/AssetsResource.java | 6 +- src/api/Base58TypeAdapter.java | 25 + src/api/BlocksResource.java | 716 +++++++++++++----- .../TransactionClassExtractor.java | 2 +- src/api/TransactionsResource.java | 58 +- src/api/UtilsResource.java | 122 ++- src/data/block/BlockData.java | 16 + src/data/package-info.java | 6 + src/data/transaction/TransactionData.java | 13 +- 15 files changed, 790 insertions(+), 298 deletions(-) create mode 100644 src/api/Base58TypeAdapter.java rename src/api/{models => }/TransactionClassExtractor.java (95%) create mode 100644 src/data/package-info.java diff --git a/.classpath b/.classpath index 6b0e4082..e9b18115 100644 --- a/.classpath +++ b/.classpath @@ -6,6 +6,7 @@ + @@ -24,17 +25,17 @@ + - + - diff --git a/.settings/org.eclipse.jdt.core.prefs b/.settings/org.eclipse.jdt.core.prefs index cd2ffaf7..61cb7d11 100644 --- a/.settings/org.eclipse.jdt.core.prefs +++ b/.settings/org.eclipse.jdt.core.prefs @@ -14,7 +14,7 @@ org.eclipse.jdt.core.compiler.source=1.8 org.eclipse.jdt.core.formatter.align_fields_grouping_blank_lines=2147483647 org.eclipse.jdt.core.formatter.align_type_members_on_columns=false org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16 -org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation=0 +org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation=53 org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant=16 org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call=16 org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation=16 @@ -82,7 +82,7 @@ org.eclipse.jdt.core.formatter.comment.new_lines_at_javadoc_boundaries=true org.eclipse.jdt.core.formatter.comment.preserve_white_space_between_code_and_line_comments=false org.eclipse.jdt.core.formatter.compact_else_if=true org.eclipse.jdt.core.formatter.continuation_indentation=2 -org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer=2 +org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer=1 org.eclipse.jdt.core.formatter.disabling_tag=@formatter\:off org.eclipse.jdt.core.formatter.enabling_tag=@formatter\:on org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line=false @@ -106,16 +106,16 @@ org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_package=inser org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter=do not insert org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_type=insert org.eclipse.jdt.core.formatter.insert_new_line_after_label=do not insert -org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer=do not insert +org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer=insert org.eclipse.jdt.core.formatter.insert_new_line_after_type_annotation=do not insert org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing=do not insert org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement=do not insert -org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer=do not insert +org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer=insert org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement=do not insert org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement=do not insert org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement=do not insert org.eclipse.jdt.core.formatter.insert_new_line_in_empty_annotation_declaration=insert -org.eclipse.jdt.core.formatter.insert_new_line_in_empty_anonymous_type_declaration=insert +org.eclipse.jdt.core.formatter.insert_new_line_in_empty_anonymous_type_declaration=do not insert org.eclipse.jdt.core.formatter.insert_new_line_in_empty_block=insert org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_constant=insert org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_declaration=insert @@ -282,10 +282,10 @@ org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_ org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant=do not insert org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration=do not insert org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation=do not insert -org.eclipse.jdt.core.formatter.join_lines_in_comments=true +org.eclipse.jdt.core.formatter.join_lines_in_comments=false org.eclipse.jdt.core.formatter.join_wrapped_lines=true org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line=false -org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line=false +org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line=true org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line=false org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line=false org.eclipse.jdt.core.formatter.lineSplit=160 @@ -293,7 +293,7 @@ org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column=false org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column=false org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body=0 org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve=1 -org.eclipse.jdt.core.formatter.parentheses_positions_in_annotation=common_lines +org.eclipse.jdt.core.formatter.parentheses_positions_in_annotation=separate_lines_if_wrapped org.eclipse.jdt.core.formatter.parentheses_positions_in_catch_clause=common_lines org.eclipse.jdt.core.formatter.parentheses_positions_in_enum_constant_declaration=common_lines org.eclipse.jdt.core.formatter.parentheses_positions_in_for_statment=common_lines diff --git a/block-explorer.html b/block-explorer.html index 32ef6caa..db180ed1 100644 --- a/block-explorer.html +++ b/block-explorer.html @@ -51,14 +51,13 @@ for (var i=0; i' + '' + txTimestamp + '' + - '' + addressAsLink(txCreatorAddress) + '' + + '' + addressAsLink(tx.creatorAddress) + '' + '' + tx.fee + ' QORA' + - '' + Base58.encode(base64ToArray(tx.signature)) + '' + - '' + Base58.encode(base64ToArray(tx.reference)) + ''; + '' + tx.signature + '' + + '' + tx.reference + ''; html += row; } @@ -119,14 +118,13 @@ for (var i=0; i' + '' + txTimestamp + '' + - '' + addressAsLink(txCreatorAddress) + '' + + '' + addressAsLink(tx.creatorAddress) + '' + '' + tx.fee + ' QORA' + - '' + Base58.encode(base64ToArray(tx.signature)) + '' + - '' + Base58.encode(base64ToArray(tx.reference)) + ''; + '' + tx.signature + '' + + '' + tx.reference + ''; html += row; } @@ -138,17 +136,7 @@ function renderBlockInfo(e) { var blockData = e.target.response.block; - - // These properties are currently emitted as base64 by API but likely to be base58 in the future, so convert them - var props = [ "signature", "reference", "transactionsSignature", "generatorPublicKey", "generatorSignature" ]; - for (var i=0; i'; html += ''; @@ -163,7 +151,7 @@ html += '' + '' + - '' + + '' + '' + '' + ''; diff --git a/pom.xml b/pom.xml index dcbdfc8a..8418f9b4 100644 --- a/pom.xml +++ b/pom.xml @@ -80,7 +80,8 @@ deepLinking: true, tagsSorter: "alpha", - operationsSorter: "alpha", + operationsSorter: + "alpha", @@ -108,6 +109,34 @@ + + + com.github.bohnman + package-info-maven-plugin + 1.0.1 + + + + data.** + + + + api.models** + + + + ${project.basedir}/src + ${project.build.directory}/generated-sources/package-info + + + + + generate + + + + org.apache.maven.plugins maven-jar-plugin @@ -128,7 +157,8 @@ false - + org.webjars:swagger-ui @@ -175,6 +205,12 @@ + + + com.github.bohnman + package-info-maven-plugin + 1.0.1 + org.hsqldb diff --git a/src/api/AddressesResource.java b/src/api/AddressesResource.java index 7fdc768f..f3dd6454 100644 --- a/src/api/AddressesResource.java +++ b/src/api/AddressesResource.java @@ -11,7 +11,6 @@ import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.tags.Tag; import java.math.BigDecimal; -import java.util.Base64; import java.util.List; import javax.servlet.http.HttpServletRequest; @@ -31,6 +30,7 @@ import repository.DataException; import repository.Repository; import repository.RepositoryManager; import transform.Transformer; +import utils.Base58; @Path("addresses") @Produces({MediaType.APPLICATION_JSON, MediaType.TEXT_PLAIN}) @@ -48,7 +48,7 @@ public class AddressesResource { @Path("/lastreference/{address}") @Operation( summary = "Fetch reference for next transaction to be created by address", - description = "Returns the base64-encoded signature of the last confirmed transaction created by address, failing that: the first incoming transaction to address. Returns \"false\" if there is no transactions.", + description = "Returns the base58-encoded signature of the last confirmed transaction created by address, failing that: the first incoming transaction to address. Returns \"false\" if there is no transactions.", extensions = { @Extension(name = "translation", properties = { @ExtensionProperty(name="path", value="GET lastreference:address"), @@ -60,7 +60,7 @@ public class AddressesResource { }, responses = { @ApiResponse( - description = "the base64-encoded transaction signature or \"false\"", + description = "the base58-encoded transaction signature or \"false\"", content = @Content(mediaType = MediaType.TEXT_PLAIN, schema = @Schema(type = "string")), extensions = { @Extension(name = "translation", properties = { @@ -87,7 +87,7 @@ public class AddressesResource { if(lastReference == null || lastReference.length == 0) { return "false"; } else { - return Base64.getEncoder().encodeToString(lastReference); + return Base58.encode(lastReference); } } @@ -95,7 +95,7 @@ public class AddressesResource { @Path("/lastreference/{address}/unconfirmed") @Operation( summary = "Fetch reference for next transaction to be created by address, considering unconfirmed transactions", - description = "Returns the base64-encoded signature of the last confirmed/unconfirmed transaction created by address, failing that: the first incoming transaction. Returns \\\"false\\\" if there is no transactions.", + description = "Returns the base58-encoded signature of the last confirmed/unconfirmed transaction created by address, failing that: the first incoming transaction. Returns \\\"false\\\" if there is no transactions.", extensions = { @Extension(name = "translation", properties = { @ExtensionProperty(name="path", value="GET lastreference:address:unconfirmed"), @@ -107,7 +107,7 @@ public class AddressesResource { }, responses = { @ApiResponse( - description = "the base64-encoded transaction signature", + description = "the base58-encoded transaction signature", content = @Content(mediaType = MediaType.TEXT_PLAIN, schema = @Schema(type = "string")), extensions = { @Extension(name = "translation", properties = { @@ -134,7 +134,7 @@ public class AddressesResource { if(lastReference == null || lastReference.length == 0) { return "false"; } else { - return Base64.getEncoder().encodeToString(lastReference); + return Base58.encode(lastReference); } } @@ -356,7 +356,7 @@ public class AddressesResource { @Path("/publickey/{address}") @Operation( summary = "Get public key of address", - description = "Returns the base64-encoded account public key of the given address, or \"false\" if address not known or has no public key.", + description = "Returns the base58-encoded account public key of the given address, or \"false\" if address not known or has no public key.", extensions = { @Extension(name = "translation", properties = { @ExtensionProperty(name="path", value="GET publickey:address"), @@ -392,7 +392,7 @@ public class AddressesResource { if (publicKey == null) return "false"; - return Base64.getEncoder().encodeToString(publicKey); + return Base58.encode(publicKey); } catch (ApiException e) { throw e; } catch (DataException e) { @@ -404,7 +404,7 @@ public class AddressesResource { @Path("/convert/{publickey}") @Operation( summary = "Convert public key into address", - description = "Returns account address based on supplied public key. Expects base64-encoded, 32-byte public key.", + description = "Returns account address based on supplied public key. Expects base58-encoded, 32-byte public key.", extensions = { @Extension(name = "translation", properties = { @ExtensionProperty(name="path", value="GET publickey:address"), @@ -426,21 +426,21 @@ public class AddressesResource { ) } ) - public String fromPublicKey(@PathParam("publickey") String publicKey) { + public String fromPublicKey(@PathParam("publickey") String publicKey58) { // Decode public key - byte[] publicKeyBytes; + byte[] publicKey; try { - publicKeyBytes = Base64.getDecoder().decode(publicKey); + publicKey = Base58.decode(publicKey58); } catch (NumberFormatException e) { throw ApiErrorFactory.getInstance().createError(ApiError.INVALID_PUBLIC_KEY, e); } // Correct size for public key? - if (publicKeyBytes.length != Transformer.PUBLIC_KEY_LENGTH) + if (publicKey.length != Transformer.PUBLIC_KEY_LENGTH) throw ApiErrorFactory.getInstance().createError(ApiError.INVALID_PUBLIC_KEY); try (final Repository repository = RepositoryManager.getRepository()) { - return Crypto.toAddress(publicKeyBytes); + return Crypto.toAddress(publicKey); } catch (ApiException e) { throw e; } catch (DataException e) { diff --git a/src/api/ApiDefinition.java b/src/api/ApiDefinition.java index b91d1b63..fbd8e3a2 100644 --- a/src/api/ApiDefinition.java +++ b/src/api/ApiDefinition.java @@ -7,7 +7,7 @@ import io.swagger.v3.oas.annotations.info.Info; import io.swagger.v3.oas.annotations.tags.Tag; @OpenAPIDefinition( - info = @Info( title = "Qora API", description = "NOTE: byte-arrays are encoded in Base64" ), + info = @Info( title = "Qora API", description = "NOTE: byte-arrays are encoded in Base58" ), tags = { @Tag(name = "Addresses"), @Tag(name = "Admin"), diff --git a/src/api/AssetsResource.java b/src/api/AssetsResource.java index 1e0a2552..b5202646 100644 --- a/src/api/AssetsResource.java +++ b/src/api/AssetsResource.java @@ -11,9 +11,9 @@ import io.swagger.v3.oas.annotations.tags.Tag; import repository.DataException; import repository.Repository; import repository.RepositoryManager; +import utils.Base58; import java.util.ArrayList; -import java.util.Base64; import java.util.List; import javax.servlet.http.HttpServletRequest; @@ -196,11 +196,11 @@ public class AssetsResource { ) } ) - public OrderWithTrades getAssetOrder(@PathParam("orderId") String orderId64) { + public OrderWithTrades getAssetOrder(@PathParam("orderId") String orderId58) { // Decode orderID byte[] orderId; try { - orderId = Base64.getDecoder().decode(orderId64); + orderId = Base58.decode(orderId58); } catch (NumberFormatException e) { throw ApiErrorFactory.getInstance().createError(ApiError.INVALID_ORDER_ID, e); } diff --git a/src/api/Base58TypeAdapter.java b/src/api/Base58TypeAdapter.java new file mode 100644 index 00000000..4a55565b --- /dev/null +++ b/src/api/Base58TypeAdapter.java @@ -0,0 +1,25 @@ +package api; + +import javax.xml.bind.annotation.adapters.XmlAdapter; + +import org.bitcoinj.core.Base58; + +public class Base58TypeAdapter extends XmlAdapter { + + @Override + public byte[] unmarshal(String input) throws Exception { + if (input == null) + return null; + + return Base58.decode(input); + } + + @Override + public String marshal(byte[] input) throws Exception { + if (input == null) + return null; + + return Base58.encode(input); + } + +} diff --git a/src/api/BlocksResource.java b/src/api/BlocksResource.java index 450adb1e..cae882e5 100644 --- a/src/api/BlocksResource.java +++ b/src/api/BlocksResource.java @@ -2,7 +2,6 @@ package api; import data.block.BlockData; import data.transaction.TransactionData; -import globalization.Translator; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.extensions.Extension; @@ -14,7 +13,6 @@ import io.swagger.v3.oas.annotations.tags.Tag; import java.math.BigDecimal; import java.util.ArrayList; -import java.util.Base64; import java.util.List; import java.util.stream.Collectors; @@ -33,63 +31,96 @@ import qora.block.Block; import repository.DataException; import repository.Repository; import repository.RepositoryManager; +import utils.Base58; @Path("blocks") -@Produces({ MediaType.APPLICATION_JSON, MediaType.TEXT_PLAIN }) -@Extension(name = "translation", properties = { @ExtensionProperty(name = "path", value = "/Api/BlocksResource") }) -@Tag(name = "Blocks") +@Produces({ + MediaType.APPLICATION_JSON, MediaType.TEXT_PLAIN +}) +@Extension( + name = "translation", + properties = { + @ExtensionProperty( + name = "path", + value = "/Api/BlocksResource" + ) + } +) +@Tag( + name = "Blocks" +) public class BlocksResource { @Context HttpServletRequest request; - private ApiErrorFactory apiErrorFactory; - - public BlocksResource() { - this(new ApiErrorFactory(Translator.getInstance())); - } - - public BlocksResource(ApiErrorFactory apiErrorFactory) { - this.apiErrorFactory = apiErrorFactory; - } - @GET @Path("/signature/{signature}") @Operation( - summary = "Fetch block using base64 signature", + summary = "Fetch block using base58 signature", description = "Returns the block that matches the given signature", - extensions = - { @Extension( - name = "translation", - properties = - { @ExtensionProperty(name = "path", value = "GET signature"), @ExtensionProperty(name = "description.key", value = "operation:description") } - ), @Extension(properties = - { @ExtensionProperty(name = "apiErrors", value = "[\"INVALID_SIGNATURE\", \"BLOCK_NO_EXISTS\"]", parseValue = true), }) }, - responses = - { @ApiResponse( - description = "the block", - content = @Content(schema = @Schema(implementation = BlockWithTransactions.class)), - extensions = - { @Extension(name = "translation", properties = { @ExtensionProperty(name = "description.key", value = "success_response:description") }) } - ) } + extensions = { + @Extension( + name = "translation", + properties = { + @ExtensionProperty( + name = "path", + value = "GET signature" + ), @ExtensionProperty( + name = "description.key", + value = "operation:description" + ) + } + ), @Extension( + properties = { + @ExtensionProperty( + name = "apiErrors", + value = "[\"INVALID_SIGNATURE\", \"BLOCK_NO_EXISTS\"]", + parseValue = true + ), + } + ) + }, + responses = { + @ApiResponse( + description = "the block", + content = @Content( + schema = @Schema( + implementation = BlockWithTransactions.class + ) + ), + extensions = { + @Extension( + name = "translation", + properties = { + @ExtensionProperty( + name = "description.key", + value = "success_response:description" + ) + } + ) + } + ) + } ) - public BlockWithTransactions getBlock(@PathParam("signature") String signature, - @Parameter(ref = "includeTransactions") @QueryParam("includeTransactions") boolean includeTransactions) { + public BlockWithTransactions getBlock(@PathParam("signature") String signature58, @Parameter( + ref = "includeTransactions" + ) @QueryParam("includeTransactions") boolean includeTransactions) { // Decode signature - byte[] signatureBytes; + byte[] signature; try { - signatureBytes = Base64.getDecoder().decode(signature); + signature = Base58.decode(signature58); } catch (NumberFormatException e) { - throw this.apiErrorFactory.createError(ApiError.INVALID_SIGNATURE, e); + throw ApiErrorFactory.getInstance().createError(ApiError.INVALID_SIGNATURE, e); } try (final Repository repository = RepositoryManager.getRepository()) { - BlockData blockData = repository.getBlockRepository().fromSignature(signatureBytes); + BlockData blockData = repository.getBlockRepository().fromSignature(signature); return packageBlockData(repository, blockData, includeTransactions); } catch (ApiException e) { throw e; } catch (DataException e) { - throw this.apiErrorFactory.createError(ApiError.REPOSITORY_ISSUE, e); + throw ApiErrorFactory.getInstance().createError(ApiError.REPOSITORY_ISSUE, e); } } @@ -100,25 +131,48 @@ public class BlocksResource { description = "Returns the genesis block", extensions = @Extension( name = "translation", - properties = - { @ExtensionProperty(name = "path", value = "GET first"), @ExtensionProperty(name = "description.key", value = "operation:description") } + properties = { + @ExtensionProperty( + name = "path", + value = "GET first" + ), @ExtensionProperty( + name = "description.key", + value = "operation:description" + ) + } ), - responses = - { @ApiResponse( - description = "the block", - content = @Content(schema = @Schema(implementation = BlockWithTransactions.class)), - extensions = - { @Extension(name = "translation", properties = { @ExtensionProperty(name = "description.key", value = "success_response:description") }) } - ) } + responses = { + @ApiResponse( + description = "the block", + content = @Content( + schema = @Schema( + implementation = BlockWithTransactions.class + ) + ), + extensions = { + @Extension( + name = "translation", + properties = { + @ExtensionProperty( + name = "description.key", + value = "success_response:description" + ) + } + ) + } + ) + } ) - public BlockWithTransactions getFirstBlock(@Parameter(ref = "includeTransactions") @QueryParam("includeTransactions") boolean includeTransactions) { + public BlockWithTransactions getFirstBlock(@Parameter( + ref = "includeTransactions" + ) @QueryParam("includeTransactions") boolean includeTransactions) { try (final Repository repository = RepositoryManager.getRepository()) { BlockData blockData = repository.getBlockRepository().fromHeight(1); return packageBlockData(repository, blockData, includeTransactions); } catch (ApiException e) { throw e; } catch (DataException e) { - throw this.apiErrorFactory.createError(ApiError.REPOSITORY_ISSUE, e); + throw ApiErrorFactory.getInstance().createError(ApiError.REPOSITORY_ISSUE, e); } } @@ -129,73 +183,126 @@ public class BlocksResource { description = "Returns the last valid block", extensions = @Extension( name = "translation", - properties = - { @ExtensionProperty(name = "path", value = "GET last"), @ExtensionProperty(name = "description.key", value = "operation:description") } + properties = { + @ExtensionProperty( + name = "path", + value = "GET last" + ), @ExtensionProperty( + name = "description.key", + value = "operation:description" + ) + } ), - responses = - { @ApiResponse( - description = "the block", - content = @Content(schema = @Schema(implementation = BlockWithTransactions.class)), - extensions = - { @Extension(name = "translation", properties = { @ExtensionProperty(name = "description.key", value = "success_response:description") }) } - ) } + responses = { + @ApiResponse( + description = "the block", + content = @Content( + schema = @Schema( + implementation = BlockWithTransactions.class + ) + ), + extensions = { + @Extension( + name = "translation", + properties = { + @ExtensionProperty( + name = "description.key", + value = "success_response:description" + ) + } + ) + } + ) + } ) - public BlockWithTransactions getLastBlock(@Parameter(ref = "includeTransactions") @QueryParam("includeTransactions") boolean includeTransactions) { + public BlockWithTransactions getLastBlock(@Parameter( + ref = "includeTransactions" + ) @QueryParam("includeTransactions") boolean includeTransactions) { try (final Repository repository = RepositoryManager.getRepository()) { BlockData blockData = repository.getBlockRepository().getLastBlock(); return packageBlockData(repository, blockData, includeTransactions); } catch (ApiException e) { throw e; } catch (DataException e) { - throw this.apiErrorFactory.createError(ApiError.REPOSITORY_ISSUE, e); + throw ApiErrorFactory.getInstance().createError(ApiError.REPOSITORY_ISSUE, e); } } @GET @Path("/child/{signature}") @Operation( - summary = "Fetch child block using base64 signature of parent block", + summary = "Fetch child block using base58 signature of parent block", description = "Returns the child block of the block that matches the given signature", - extensions = - { @Extension( - name = "translation", - properties = - { @ExtensionProperty(name = "path", value = "GET child:signature"), @ExtensionProperty(name = "description.key", value = "operation:description") } - ), @Extension(properties = - { @ExtensionProperty(name = "apiErrors", value = "[\"INVALID_SIGNATURE\", \"BLOCK_NO_EXISTS\"]", parseValue = true), }) }, - responses = - { @ApiResponse( - description = "the block", - content = @Content(schema = @Schema(implementation = BlockWithTransactions.class)), - extensions = - { @Extension(name = "translation", properties = { @ExtensionProperty(name = "description.key", value = "success_response:description") }) } - ) } + extensions = { + @Extension( + name = "translation", + properties = { + @ExtensionProperty( + name = "path", + value = "GET child:signature" + ), @ExtensionProperty( + name = "description.key", + value = "operation:description" + ) + } + ), @Extension( + properties = { + @ExtensionProperty( + name = "apiErrors", + value = "[\"INVALID_SIGNATURE\", \"BLOCK_NO_EXISTS\"]", + parseValue = true + ), + } + ) + }, + responses = { + @ApiResponse( + description = "the block", + content = @Content( + schema = @Schema( + implementation = BlockWithTransactions.class + ) + ), + extensions = { + @Extension( + name = "translation", + properties = { + @ExtensionProperty( + name = "description.key", + value = "success_response:description" + ) + } + ) + } + ) + } ) - public BlockWithTransactions getChild(@PathParam("signature") String signature, - @Parameter(ref = "includeTransactions") @QueryParam("includeTransactions") boolean includeTransactions) { + public BlockWithTransactions getChild(@PathParam("signature") String signature58, @Parameter( + ref = "includeTransactions" + ) @QueryParam("includeTransactions") boolean includeTransactions) { // Decode signature - byte[] signatureBytes; + byte[] signature; try { - signatureBytes = Base64.getDecoder().decode(signature); + signature = Base58.decode(signature58); } catch (NumberFormatException e) { - throw this.apiErrorFactory.createError(ApiError.INVALID_SIGNATURE, e); + throw ApiErrorFactory.getInstance().createError(ApiError.INVALID_SIGNATURE, e); } try (final Repository repository = RepositoryManager.getRepository()) { - BlockData blockData = repository.getBlockRepository().fromSignature(signatureBytes); + BlockData blockData = repository.getBlockRepository().fromSignature(signature); // Check block exists if (blockData == null) - throw this.apiErrorFactory.createError(ApiError.BLOCK_NO_EXISTS); + throw ApiErrorFactory.getInstance().createError(ApiError.BLOCK_NO_EXISTS); - BlockData childBlockData = repository.getBlockRepository().fromReference(signatureBytes); + BlockData childBlockData = repository.getBlockRepository().fromReference(signature); // Checking child exists is handled by packageBlockData() return packageBlockData(repository, childBlockData, includeTransactions); } catch (ApiException e) { throw e; } catch (DataException e) { - throw this.apiErrorFactory.createError(ApiError.REPOSITORY_ISSUE, e); + throw ApiErrorFactory.getInstance().createError(ApiError.REPOSITORY_ISSUE, e); } } @@ -206,16 +313,38 @@ public class BlocksResource { description = "Calculates the generating balance of the block that will follow the last block", extensions = @Extension( name = "translation", - properties = - { @ExtensionProperty(name = "path", value = "GET generatingbalance"), @ExtensionProperty(name = "description.key", value = "operation:description") } + properties = { + @ExtensionProperty( + name = "path", + value = "GET generatingbalance" + ), @ExtensionProperty( + name = "description.key", + value = "operation:description" + ) + } ), - responses = - { @ApiResponse( - description = "the generating balance", - content = @Content(schema = @Schema(implementation = BigDecimal.class)), - extensions = - { @Extension(name = "translation", properties = { @ExtensionProperty(name = "description.key", value = "success_response:description") }) } - ) } + responses = { + @ApiResponse( + description = "the generating balance", + content = @Content( + mediaType = MediaType.TEXT_PLAIN, + schema = @Schema( + implementation = BigDecimal.class + ) + ), + extensions = { + @Extension( + name = "translation", + properties = { + @ExtensionProperty( + name = "description.key", + value = "success_response:description" + ) + } + ) + } + ) + } ) public BigDecimal getGeneratingBalance() { try (final Repository repository = RepositoryManager.getRepository()) { @@ -225,7 +354,7 @@ public class BlocksResource { } catch (ApiException e) { throw e; } catch (DataException e) { - throw this.apiErrorFactory.createError(ApiError.REPOSITORY_ISSUE, e); + throw ApiErrorFactory.getInstance().createError(ApiError.REPOSITORY_ISSUE, e); } } @@ -234,44 +363,73 @@ public class BlocksResource { @Operation( summary = "Generating balance of block after specific block", description = "Calculates the generating balance of the block that will follow the block that matches the signature", - extensions = - { @Extension( - name = "translation", - properties = - { @ExtensionProperty(name = "path", value = "GET generatingbalance:signature"), - @ExtensionProperty(name = "description.key", value = "operation:description") } - ), @Extension(properties = - { @ExtensionProperty(name = "apiErrors", value = "[\"INVALID_SIGNATURE\", \"BLOCK_NO_EXISTS\"]", parseValue = true), }) }, - responses = - { @ApiResponse( - description = "the block", - content = @Content(schema = @Schema(implementation = BigDecimal.class)), - extensions = - { @Extension(name = "translation", properties = { @ExtensionProperty(name = "description.key", value = "success_response:description") }) } - ) } + extensions = { + @Extension( + name = "translation", + properties = { + @ExtensionProperty( + name = "path", + value = "GET generatingbalance:signature" + ), @ExtensionProperty( + name = "description.key", + value = "operation:description" + ) + } + ), @Extension( + properties = { + @ExtensionProperty( + name = "apiErrors", + value = "[\"INVALID_SIGNATURE\", \"BLOCK_NO_EXISTS\"]", + parseValue = true + ), + } + ) + }, + responses = { + @ApiResponse( + description = "the block", + content = @Content( + mediaType = MediaType.TEXT_PLAIN, + schema = @Schema( + implementation = BigDecimal.class + ) + ), + extensions = { + @Extension( + name = "translation", + properties = { + @ExtensionProperty( + name = "description.key", + value = "success_response:description" + ) + } + ) + } + ) + } ) - public BigDecimal getGeneratingBalance(@PathParam("signature") String signature) { + public BigDecimal getGeneratingBalance(@PathParam("signature") String signature58) { // Decode signature - byte[] signatureBytes; + byte[] signature; try { - signatureBytes = Base64.getDecoder().decode(signature); + signature = Base58.decode(signature58); } catch (NumberFormatException e) { - throw this.apiErrorFactory.createError(ApiError.INVALID_SIGNATURE, e); + throw ApiErrorFactory.getInstance().createError(ApiError.INVALID_SIGNATURE, e); } try (final Repository repository = RepositoryManager.getRepository()) { - BlockData blockData = repository.getBlockRepository().fromSignature(signatureBytes); + BlockData blockData = repository.getBlockRepository().fromSignature(signature); // Check block exists if (blockData == null) - throw this.apiErrorFactory.createError(ApiError.BLOCK_NO_EXISTS); + throw ApiErrorFactory.getInstance().createError(ApiError.BLOCK_NO_EXISTS); Block block = new Block(repository, blockData); return block.calcNextBlockGeneratingBalance(); } catch (ApiException e) { throw e; } catch (DataException e) { - throw this.apiErrorFactory.createError(ApiError.REPOSITORY_ISSUE, e); + throw ApiErrorFactory.getInstance().createError(ApiError.REPOSITORY_ISSUE, e); } } @@ -282,16 +440,39 @@ public class BlocksResource { description = "Calculates the time it should take for the network to generate the next block", extensions = @Extension( name = "translation", - properties = - { @ExtensionProperty(name = "path", value = "GET time"), @ExtensionProperty(name = "description.key", value = "operation:description") } + properties = { + @ExtensionProperty( + name = "path", + value = "GET time" + ), @ExtensionProperty( + name = "description.key", + value = "operation:description" + ) + } ), - responses = - { @ApiResponse( - description = "the time in seconds", // in seconds? - content = @Content(schema = @Schema(implementation = long.class)), - extensions = - { @Extension(name = "translation", properties = { @ExtensionProperty(name = "description.key", value = "success_response:description") }) } - ) } + responses = { + @ApiResponse( + description = "the time in seconds", // in + // seconds? + content = @Content( + mediaType = MediaType.TEXT_PLAIN, + schema = @Schema( + type = "number" + ) + ), + extensions = { + @Extension( + name = "translation", + properties = { + @ExtensionProperty( + name = "description.key", + value = "success_response:description" + ) + } + ) + } + ) + } ) public long getTimePerBlock() { try (final Repository repository = RepositoryManager.getRepository()) { @@ -300,7 +481,7 @@ public class BlocksResource { } catch (ApiException e) { throw e; } catch (DataException e) { - throw this.apiErrorFactory.createError(ApiError.REPOSITORY_ISSUE, e); + throw ApiErrorFactory.getInstance().createError(ApiError.REPOSITORY_ISSUE, e); } } @@ -311,18 +492,40 @@ public class BlocksResource { description = "Calculates the time it should take for the network to generate blocks based on specified generating balance", extensions = @Extension( name = "translation", - properties = - { @ExtensionProperty(name = "path", value = "GET time:generatingbalance"), @ExtensionProperty(name = "description.key", value = "operation:description") } + properties = { + @ExtensionProperty( + name = "path", + value = "GET time:generatingbalance" + ), @ExtensionProperty( + name = "description.key", + value = "operation:description" + ) + } ), - responses = - { @ApiResponse( - description = "the time", // in seconds? - content = @Content(schema = @Schema(implementation = long.class)), - extensions = - { @Extension(name = "translation", properties = { @ExtensionProperty(name = "description.key", value = "success_response:description") }) } - ) } + responses = { + @ApiResponse( + description = "the time", // in seconds? + content = @Content( + mediaType = MediaType.TEXT_PLAIN, + schema = @Schema( + type = "number" + ) + ), + extensions = { + @Extension( + name = "translation", + properties = { + @ExtensionProperty( + name = "description.key", + value = "success_response:description" + ) + } + ) + } + ) + } ) - public long getTimePerBlock(@PathParam("generating") BigDecimal generatingbalance) { + public long getTimePerBlock(@PathParam("generatingbalance") BigDecimal generatingbalance) { return Block.calcForgingDelay(generatingbalance); } @@ -333,16 +536,38 @@ public class BlocksResource { description = "Returns the block height of the last block.", extensions = @Extension( name = "translation", - properties = - { @ExtensionProperty(name = "path", value = "GET height"), @ExtensionProperty(name = "description.key", value = "operation:description") } + properties = { + @ExtensionProperty( + name = "path", + value = "GET height" + ), @ExtensionProperty( + name = "description.key", + value = "operation:description" + ) + } ), - responses = - { @ApiResponse( - description = "the height", - content = @Content(schema = @Schema(implementation = int.class)), - extensions = - { @Extension(name = "translation", properties = { @ExtensionProperty(name = "description.key", value = "success_response:description") }) } - ) } + responses = { + @ApiResponse( + description = "the height", + content = @Content( + mediaType = MediaType.TEXT_PLAIN, + schema = @Schema( + type = "number" + ) + ), + extensions = { + @Extension( + name = "translation", + properties = { + @ExtensionProperty( + name = "description.key", + value = "success_response:description" + ) + } + ) + } + ) + } ) public int getHeight() { try (final Repository repository = RepositoryManager.getRepository()) { @@ -350,7 +575,7 @@ public class BlocksResource { } catch (ApiException e) { throw e; } catch (DataException e) { - throw this.apiErrorFactory.createError(ApiError.REPOSITORY_ISSUE, e); + throw ApiErrorFactory.getInstance().createError(ApiError.REPOSITORY_ISSUE, e); } } @@ -359,42 +584,72 @@ public class BlocksResource { @Operation( summary = "Height of specific block", description = "Returns the block height of the block that matches the given signature", - extensions = - { @Extension( - name = "translation", - properties = - { @ExtensionProperty(name = "path", value = "GET height:signature"), @ExtensionProperty(name = "description.key", value = "operation:description") } - ), @Extension(properties = - { @ExtensionProperty(name = "apiErrors", value = "[\"INVALID_SIGNATURE\", \"BLOCK_NO_EXISTS\"]", parseValue = true), }) }, - responses = - { @ApiResponse( - description = "the height", - content = @Content(schema = @Schema(implementation = int.class)), - extensions = - { @Extension(name = "translation", properties = { @ExtensionProperty(name = "description.key", value = "success_response:description") }) } - ) } + extensions = { + @Extension( + name = "translation", + properties = { + @ExtensionProperty( + name = "path", + value = "GET height:signature" + ), @ExtensionProperty( + name = "description.key", + value = "operation:description" + ) + } + ), @Extension( + properties = { + @ExtensionProperty( + name = "apiErrors", + value = "[\"INVALID_SIGNATURE\", \"BLOCK_NO_EXISTS\"]", + parseValue = true + ), + } + ) + }, + responses = { + @ApiResponse( + description = "the height", + content = @Content( + mediaType = MediaType.TEXT_PLAIN, + schema = @Schema( + type = "number" + ) + ), + extensions = { + @Extension( + name = "translation", + properties = { + @ExtensionProperty( + name = "description.key", + value = "success_response:description" + ) + } + ) + } + ) + } ) - public int getHeight(@PathParam("signature") String signature) { + public int getHeight(@PathParam("signature") String signature58) { // Decode signature - byte[] signatureBytes; + byte[] signature; try { - signatureBytes = Base64.getDecoder().decode(signature); + signature = Base58.decode(signature58); } catch (NumberFormatException e) { - throw this.apiErrorFactory.createError(ApiError.INVALID_SIGNATURE, e); + throw ApiErrorFactory.getInstance().createError(ApiError.INVALID_SIGNATURE, e); } try (final Repository repository = RepositoryManager.getRepository()) { - BlockData blockData = repository.getBlockRepository().fromSignature(signatureBytes); + BlockData blockData = repository.getBlockRepository().fromSignature(signature); // Check block exists if (blockData == null) - throw this.apiErrorFactory.createError(ApiError.BLOCK_NO_EXISTS); + throw ApiErrorFactory.getInstance().createError(ApiError.BLOCK_NO_EXISTS); return blockData.getHeight(); } catch (ApiException e) { throw e; } catch (DataException e) { - throw this.apiErrorFactory.createError(ApiError.REPOSITORY_ISSUE, e); + throw ApiErrorFactory.getInstance().createError(ApiError.REPOSITORY_ISSUE, e); } } @@ -403,30 +658,60 @@ public class BlocksResource { @Operation( summary = "Fetch block using block height", description = "Returns the block with given height", - extensions = - { @Extension( - name = "translation", - properties = - { @ExtensionProperty(name = "path", value = "GET byheight:height"), @ExtensionProperty(name = "description.key", value = "operation:description") } - ), @Extension(properties = - { @ExtensionProperty(name = "apiErrors", value = "[\"BLOCK_NO_EXISTS\"]", parseValue = true), }) }, - responses = - { @ApiResponse( - description = "the block", - content = @Content(schema = @Schema(implementation = BlockWithTransactions.class)), - extensions = - { @Extension(name = "translation", properties = { @ExtensionProperty(name = "description.key", value = "success_response:description") }) } - ) } + extensions = { + @Extension( + name = "translation", + properties = { + @ExtensionProperty( + name = "path", + value = "GET byheight:height" + ), @ExtensionProperty( + name = "description.key", + value = "operation:description" + ) + } + ), @Extension( + properties = { + @ExtensionProperty( + name = "apiErrors", + value = "[\"BLOCK_NO_EXISTS\"]", + parseValue = true + ), + } + ) + }, + responses = { + @ApiResponse( + description = "the block", + content = @Content( + schema = @Schema( + implementation = BlockWithTransactions.class + ) + ), + extensions = { + @Extension( + name = "translation", + properties = { + @ExtensionProperty( + name = "description.key", + value = "success_response:description" + ) + } + ) + } + ) + } ) - public BlockWithTransactions getByHeight(@PathParam("height") int height, - @Parameter(ref = "includeTransactions") @QueryParam("includeTransactions") boolean includeTransactions) { + public BlockWithTransactions getByHeight(@PathParam("height") int height, @Parameter( + ref = "includeTransactions" + ) @QueryParam("includeTransactions") boolean includeTransactions) { try (final Repository repository = RepositoryManager.getRepository()) { BlockData blockData = repository.getBlockRepository().fromHeight(height); return packageBlockData(repository, blockData, includeTransactions); } catch (ApiException e) { throw e; } catch (DataException e) { - throw this.apiErrorFactory.createError(ApiError.REPOSITORY_ISSUE, e); + throw ApiErrorFactory.getInstance().createError(ApiError.REPOSITORY_ISSUE, e); } } @@ -435,22 +720,53 @@ public class BlocksResource { @Operation( summary = "Fetch blocks starting with given height", description = "Returns blocks starting with given height.", - extensions = - { @Extension( - name = "translation", - properties = - { @ExtensionProperty(name = "path", value = "GET byheight:height"), @ExtensionProperty(name = "description.key", value = "operation:description") } - ), @Extension(properties = - { @ExtensionProperty(name = "apiErrors", value = "[\"BLOCK_NO_EXISTS\"]", parseValue = true), }) }, - responses = - { @ApiResponse( - description = "blocks", - content = @Content(schema = @Schema(implementation = BlockWithTransactions.class)), - extensions = - { @Extension(name = "translation", properties = { @ExtensionProperty(name = "description.key", value = "success_response:description") }) } - ) } + extensions = { + @Extension( + name = "translation", + properties = { + @ExtensionProperty( + name = "path", + value = "GET byheight:height" + ), @ExtensionProperty( + name = "description.key", + value = "operation:description" + ) + } + ), @Extension( + properties = { + @ExtensionProperty( + name = "apiErrors", + value = "[\"BLOCK_NO_EXISTS\"]", + parseValue = true + ), + } + ) + }, + responses = { + @ApiResponse( + description = "blocks", + content = @Content( + schema = @Schema( + implementation = BlockWithTransactions.class + ) + ), + extensions = { + @Extension( + name = "translation", + properties = { + @ExtensionProperty( + name = "description.key", + value = "success_response:description" + ) + } + ) + } + ) + } ) - public List getBlockRange(@PathParam("height") int height, @Parameter(ref = "count") @QueryParam("count") int count) { + public List getBlockRange(@PathParam("height") int height, @Parameter( + ref = "count" + ) @QueryParam("count") int count) { boolean includeTransactions = false; try (final Repository repository = RepositoryManager.getRepository()) { @@ -469,13 +785,13 @@ public class BlocksResource { } catch (ApiException e) { throw e; } catch (DataException e) { - throw this.apiErrorFactory.createError(ApiError.REPOSITORY_ISSUE, e); + throw ApiErrorFactory.getInstance().createError(ApiError.REPOSITORY_ISSUE, e); } } private BlockWithTransactions packageBlockData(Repository repository, BlockData blockData, boolean includeTransactions) throws DataException { if (blockData == null) - throw this.apiErrorFactory.createError(ApiError.BLOCK_NO_EXISTS); + throw ApiErrorFactory.getInstance().createError(ApiError.BLOCK_NO_EXISTS); List transactions = null; if (includeTransactions) { diff --git a/src/api/models/TransactionClassExtractor.java b/src/api/TransactionClassExtractor.java similarity index 95% rename from src/api/models/TransactionClassExtractor.java rename to src/api/TransactionClassExtractor.java index 250caf3b..3c58343e 100644 --- a/src/api/models/TransactionClassExtractor.java +++ b/src/api/TransactionClassExtractor.java @@ -1,4 +1,4 @@ -package api.models; +package api; import org.eclipse.persistence.descriptors.ClassExtractor; import org.eclipse.persistence.sessions.Record; diff --git a/src/api/TransactionsResource.java b/src/api/TransactionsResource.java index 39505032..06370c3c 100644 --- a/src/api/TransactionsResource.java +++ b/src/api/TransactionsResource.java @@ -1,6 +1,5 @@ package api; -import globalization.Translator; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.extensions.Extension; @@ -13,7 +12,6 @@ import io.swagger.v3.oas.annotations.tags.Tag; import qora.transaction.Transaction.TransactionType; import java.util.ArrayList; -import java.util.Base64; import java.util.List; import javax.servlet.http.HttpServletRequest; @@ -45,26 +43,16 @@ public class TransactionsResource { @Context HttpServletRequest request; - private ApiErrorFactory apiErrorFactory; - - public TransactionsResource() { - this(new ApiErrorFactory(Translator.getInstance())); - } - - public TransactionsResource(ApiErrorFactory apiErrorFactory) { - this.apiErrorFactory = apiErrorFactory; - } - @GET @Path("/signature/{signature}") @Operation( summary = "Fetch transaction using transaction signature", description = "Returns transaction", extensions = { - @Extension(properties = { - @ExtensionProperty(name="apiErrors", value="[\"INVALID_SIGNATURE\", \"TRANSACTION_NO_EXISTS\"]", parseValue = true), - }) - }, + @Extension(properties = { + @ExtensionProperty(name="apiErrors", value="[\"INVALID_SIGNATURE\", \"TRANSACTION_NO_EXISTS\"]", parseValue = true), + }) + }, responses = { @ApiResponse( description = "a transaction", @@ -77,25 +65,24 @@ public class TransactionsResource { ) } ) - public TransactionData getTransactions(@PathParam("signature") String signature) { - // Decode signature - byte[] signatureBytes; + public TransactionData getTransactions(@PathParam("signature") String signature58) { + byte[] signature; try { - signatureBytes = Base64.getDecoder().decode(signature); + signature = Base58.decode(signature58); } catch (NumberFormatException e) { - throw this.apiErrorFactory.createError(ApiError.INVALID_SIGNATURE, e); + throw ApiErrorFactory.getInstance().createError(ApiError.INVALID_SIGNATURE, e); } try (final Repository repository = RepositoryManager.getRepository()) { - TransactionData transactionData = repository.getTransactionRepository().fromSignature(signatureBytes); + TransactionData transactionData = repository.getTransactionRepository().fromSignature(signature); if (transactionData == null) - throw this.apiErrorFactory.createError(ApiError.TRANSACTION_NO_EXISTS); + throw ApiErrorFactory.getInstance().createError(ApiError.TRANSACTION_NO_EXISTS); return transactionData; } catch (ApiException e) { throw e; } catch (DataException e) { - throw this.apiErrorFactory.createError(ApiError.REPOSITORY_ISSUE, e); + throw ApiErrorFactory.getInstance().createError(ApiError.REPOSITORY_ISSUE, e); } } @@ -127,21 +114,20 @@ public class TransactionsResource { ) } ) - public List getBlockTransactions(@PathParam("signature") String signature, @Parameter(ref = "limit") @QueryParam("limit") int limit, @Parameter(ref = "offset") @QueryParam("offset") int offset) { - // decode signature - byte[] signatureBytes; + public List getBlockTransactions(@PathParam("signature") String signature58, @Parameter(ref = "limit") @QueryParam("limit") int limit, @Parameter(ref = "offset") @QueryParam("offset") int offset) { + byte[] signature; try { - signatureBytes = Base58.decode(signature); + signature = Base58.decode(signature58); } catch (NumberFormatException e) { - throw this.apiErrorFactory.createError(ApiError.INVALID_SIGNATURE, e); + throw ApiErrorFactory.getInstance().createError(ApiError.INVALID_SIGNATURE, e); } try (final Repository repository = RepositoryManager.getRepository()) { - List transactions = repository.getBlockRepository().getTransactionsFromSignature(signatureBytes); + List transactions = repository.getBlockRepository().getTransactionsFromSignature(signature); // check if block exists if(transactions == null) - throw this.apiErrorFactory.createError(ApiError.BLOCK_NO_EXISTS); + throw ApiErrorFactory.getInstance().createError(ApiError.BLOCK_NO_EXISTS); // Pagination would take effect here (or as part of the repository access) int fromIndex = Integer.min(offset, transactions.size()); @@ -152,7 +138,7 @@ public class TransactionsResource { } catch (ApiException e) { throw e; } catch (DataException e) { - throw this.apiErrorFactory.createError(ApiError.REPOSITORY_ISSUE, e); + throw ApiErrorFactory.getInstance().createError(ApiError.REPOSITORY_ISSUE, e); } } @@ -179,7 +165,7 @@ public class TransactionsResource { } catch (ApiException e) { throw e; } catch (DataException e) { - throw this.apiErrorFactory.createError(ApiError.REPOSITORY_ISSUE, e); + throw ApiErrorFactory.getInstance().createError(ApiError.REPOSITORY_ISSUE, e); } } @@ -211,13 +197,13 @@ public class TransactionsResource { public List searchTransactions(@QueryParam("startBlock") Integer startBlock, @QueryParam("blockLimit") Integer blockLimit, @QueryParam("txType") Integer txTypeNum, @QueryParam("address") String address, @Parameter(ref = "limit") @QueryParam("limit") int limit, @Parameter(ref = "offset") @QueryParam("offset") int offset) { if ((txTypeNum == null || txTypeNum == 0) && (address == null || address.isEmpty())) - throw this.apiErrorFactory.createError(ApiError.INVALID_CRITERIA); + throw ApiErrorFactory.getInstance().createError(ApiError.INVALID_CRITERIA); TransactionType txType = null; if (txTypeNum != null) { txType = TransactionType.valueOf(txTypeNum); if (txType == null) - throw this.apiErrorFactory.createError(ApiError.INVALID_CRITERIA); + throw ApiErrorFactory.getInstance().createError(ApiError.INVALID_CRITERIA); } try (final Repository repository = RepositoryManager.getRepository()) { @@ -237,7 +223,7 @@ public class TransactionsResource { } catch (ApiException e) { throw e; } catch (DataException e) { - throw this.apiErrorFactory.createError(ApiError.REPOSITORY_ISSUE, e); + throw ApiErrorFactory.getInstance().createError(ApiError.REPOSITORY_ISSUE, e); } } diff --git a/src/api/UtilsResource.java b/src/api/UtilsResource.java index 86602bc1..c935a64c 100644 --- a/src/api/UtilsResource.java +++ b/src/api/UtilsResource.java @@ -3,24 +3,36 @@ package api; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.media.Content; import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.parameters.RequestBody; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.tags.Tag; +import qora.crypto.Crypto; import utils.Base58; +import java.math.BigInteger; import java.security.SecureRandom; import java.util.Base64; +import java.util.UUID; import javax.servlet.http.HttpServletRequest; import javax.ws.rs.GET; +import javax.ws.rs.POST; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.core.Context; import javax.ws.rs.core.MediaType; +import com.google.common.primitives.Bytes; +import com.google.common.primitives.Longs; + @Path("/utils") -@Produces({MediaType.TEXT_PLAIN}) -@Tag(name = "Utilities") +@Produces({ + MediaType.TEXT_PLAIN +}) +@Tag( + name = "Utilities" +) public class UtilsResource { @Context @@ -33,7 +45,11 @@ public class UtilsResource { responses = { @ApiResponse( description = "base58 data", - content = @Content(schema = @Schema(implementation = String.class)) + content = @Content( + schema = @Schema( + implementation = String.class + ) + ) ) } ) @@ -52,7 +68,11 @@ public class UtilsResource { responses = { @ApiResponse( description = "base64 data", - content = @Content(schema = @Schema(implementation = String.class)) + content = @Content( + schema = @Schema( + implementation = String.class + ) + ) ) } ) @@ -70,15 +90,103 @@ public class UtilsResource { summary = "Generate random 32-byte seed", responses = { @ApiResponse( - description = "base64 data", - content = @Content(schema = @Schema(implementation = String.class)) + description = "base58 data", + content = @Content( + mediaType = MediaType.TEXT_PLAIN, + schema = @Schema( + type = "string" + ) + ) ) } ) public String seed() { byte[] seed = new byte[32]; new SecureRandom().nextBytes(seed); - return Base64.getEncoder().encodeToString(seed); + return Base58.encode(seed); + } + + @GET + @Path("/seedPhrase") + @Operation( + summary = "Generate random 12-word BIP39 seed phrase", + responses = { + @ApiResponse( + description = "seed phrase", + content = @Content( + mediaType = MediaType.TEXT_PLAIN, + schema = @Schema( + type = "string" + ) + ) + ) + } + ) + public String seedPhrase() { + /* + * BIP39 word lists have 2048 entries so can be represented by 11 bits. + * UUID (128bits) and another 4 bits gives 132 bits. + * 132 bits, divided by 11, gives 12 words. + */ + final int WORD_MASK = 2048 - 1; + + UUID uuid = UUID.randomUUID(); + + System.out.println("UUID: " + uuid.toString()); + + byte[] uuidMSB = Longs.toByteArray(uuid.getMostSignificantBits()); + byte[] uuidLSB = Longs.toByteArray(uuid.getLeastSignificantBits()); + byte[] message = Bytes.concat(uuidMSB, uuidLSB); + + // Use SHA256 to generate more bits + byte[] hash = Crypto.digest(message); + + // Append last 4 bits from hash to end. (Actually 8 bits but we only use 4). + message = Bytes.concat(message, new byte[] { + hash[hash.length - 1] + }); + + BigInteger wordBits = new BigInteger(message); + + String[] phraseWords = new String[12]; + for (int i = phraseWords.length; i >= 0; --i) { + int wordListIndex = wordBits.intValue() & WORD_MASK; + wordBits = wordBits.shiftRight(11); + // phraseWords[i] = wordList.get(wordListIndex); + } + + return String.join(" ", phraseWords); + } + + @POST + @Path("/privateKey") + @Operation( + summary = "Calculate private key from 12-word BIP39 seed phrase", + description = "Returns the base58-encoded private key, or \"false\" if phrase is invalid.", + requestBody = @RequestBody( + required = true, + content = @Content( + mediaType = MediaType.TEXT_PLAIN, + schema = @Schema( + type = "string" + ) + ) + ), + responses = { + @ApiResponse( + description = "the private key", + content = @Content( + mediaType = MediaType.TEXT_PLAIN, + schema = @Schema( + type = "string" + ) + ) + ) + } + ) + public String getPublicKey(String seedPhrase) { + // TODO: convert BIP39 seed phrase to private key + return seedPhrase; } } diff --git a/src/data/block/BlockData.java b/src/data/block/BlockData.java index 4b794426..15a40c0b 100644 --- a/src/data/block/BlockData.java +++ b/src/data/block/BlockData.java @@ -2,11 +2,14 @@ package data.block; import com.google.common.primitives.Bytes; +import qora.crypto.Crypto; + import java.io.Serializable; import java.math.BigDecimal; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlElement; // All properties to be converted to JSON via JAX-RS @XmlAccessorType(XmlAccessType.FIELD) @@ -14,6 +17,8 @@ public class BlockData implements Serializable { private static final long serialVersionUID = -7678329659124664620L; + // Properties + private byte[] signature; private int version; private byte[] reference; @@ -28,6 +33,8 @@ public class BlockData implements Serializable { private int atCount; private BigDecimal atFees; + // Constructors + // necessary for JAX-RS serialization protected BlockData() { } @@ -53,6 +60,8 @@ public class BlockData implements Serializable { this.signature = null; } + // Getters/setters + public byte[] getSignature() { return this.signature; } @@ -141,4 +150,11 @@ public class BlockData implements Serializable { this.atFees = atFees; } + // JAXB special + + @XmlElement(name = "generatorAddress") + protected String getGeneratorAddress() { + return Crypto.toAddress(this.generatorPublicKey); + } + } diff --git a/src/data/package-info.java b/src/data/package-info.java new file mode 100644 index 00000000..27ea8739 --- /dev/null +++ b/src/data/package-info.java @@ -0,0 +1,6 @@ +// This file (data/package-info.java) is used as a template! + +@XmlJavaTypeAdapter(type = byte[].class, value = api.Base58TypeAdapter.class) +package data; + +import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; diff --git a/src/data/transaction/TransactionData.java b/src/data/transaction/TransactionData.java index 0eb423e3..2b0b2cc4 100644 --- a/src/data/transaction/TransactionData.java +++ b/src/data/transaction/TransactionData.java @@ -6,11 +6,14 @@ import java.util.Arrays; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlSeeAlso; +import javax.xml.bind.annotation.XmlTransient; import org.eclipse.persistence.oxm.annotations.XmlClassExtractor; -import api.models.TransactionClassExtractor; +import api.TransactionClassExtractor; +import qora.crypto.Crypto; import qora.transaction.Transaction.TransactionType; /* @@ -32,6 +35,7 @@ public abstract class TransactionData { // Properties shared with all transaction types protected TransactionType type; + @XmlTransient // represented in transaction-specific properties protected byte[] creatorPublicKey; protected long timestamp; protected byte[] reference; @@ -87,6 +91,13 @@ public abstract class TransactionData { this.signature = signature; } + // JAXB special + + @XmlElement(name = "creatorAddress") + protected String getCreatorAddress() { + return Crypto.toAddress(this.creatorPublicKey); + } + // Comparison @Override
PropertyValue
'; } - if (p == "generator") { + if (p == "generatorAddress") { html += addressAsLink(blockData[p]); } else { html += blockData[p]; @@ -200,12 +188,11 @@ var ourHeight = blockData.height; var blockTimestamp = new Date(blockData.timestamp).toUTCString(); - var blockGeneratorAddress = publicKeyToAddress(base64ToArray(blockData.generatorPublicKey)); // currently base64 but likely to be base58 in the future var ourRow = document.createElement('TR'); ourRow.innerHTML = '' + ourHeight + '' + blockTimestamp + '' + addressAsLink(blockGeneratorAddress) + '' + addressAsLink(blockData.generatorAddress) + '' + blockData.generatingBalance + '' + blockData.transactionCount + '' + blockData.totalFees + ' QORA