diff --git a/block-explorer.html b/block-explorer.html index f073e047..32ef6caa 100644 --- a/block-explorer.html +++ b/block-explorer.html @@ -88,7 +88,7 @@ document.body.innerHTML = html; XHR({ - url: "/transactions/address/" + address, + url: "/transactions/search?address=" + address, onload: renderAddressTransactions, responseType: "json" }); @@ -137,7 +137,7 @@ } function renderBlockInfo(e) { - var blockData = e.target.response; + 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" ]; @@ -196,7 +196,7 @@ } function listBlock(e) { - var blockData = e.target.response; + var blockData = e.target.response.block; var ourHeight = blockData.height; var blockTimestamp = new Date(blockData.timestamp).toUTCString(); diff --git a/src/api/AddressesResource.java b/src/api/AddressesResource.java index f14d5480..7fdc768f 100644 --- a/src/api/AddressesResource.java +++ b/src/api/AddressesResource.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; @@ -45,16 +44,6 @@ public class AddressesResource { @Context HttpServletRequest request; - private ApiErrorFactory apiErrorFactory; - - public AddressesResource() { - this(new ApiErrorFactory(Translator.getInstance())); - } - - public AddressesResource(ApiErrorFactory apiErrorFactory) { - this.apiErrorFactory = apiErrorFactory; - } - @GET @Path("/lastreference/{address}") @Operation( @@ -72,7 +61,7 @@ public class AddressesResource { responses = { @ApiResponse( description = "the base64-encoded transaction signature or \"false\"", - content = @Content(schema = @Schema(implementation = String.class)), + content = @Content(mediaType = MediaType.TEXT_PLAIN, schema = @Schema(type = "string")), extensions = { @Extension(name = "translation", properties = { @ExtensionProperty(name="description.key", value="success_response:description") @@ -83,7 +72,7 @@ public class AddressesResource { ) public String getLastReference(@Parameter(ref = "address") @PathParam("address") String address) { if (!Crypto.isValidAddress(address)) - throw this.apiErrorFactory.createError(ApiError.INVALID_ADDRESS); + throw ApiErrorFactory.getInstance().createError(ApiError.INVALID_ADDRESS); byte[] lastReference = null; try (final Repository repository = RepositoryManager.getRepository()) { @@ -92,7 +81,7 @@ public class AddressesResource { } catch (ApiException e) { throw e; } catch (DataException e) { - throw this.apiErrorFactory.createError(ApiError.REPOSITORY_ISSUE, e); + throw ApiErrorFactory.getInstance().createError(ApiError.REPOSITORY_ISSUE, e); } if(lastReference == null || lastReference.length == 0) { @@ -119,7 +108,7 @@ public class AddressesResource { responses = { @ApiResponse( description = "the base64-encoded transaction signature", - content = @Content(schema = @Schema(implementation = String.class)), + content = @Content(mediaType = MediaType.TEXT_PLAIN, schema = @Schema(type = "string")), extensions = { @Extension(name = "translation", properties = { @ExtensionProperty(name="description.key", value="success_response:description") @@ -130,7 +119,7 @@ public class AddressesResource { ) public String getLastReferenceUnconfirmed(@PathParam("address") String address) { if (!Crypto.isValidAddress(address)) - throw this.apiErrorFactory.createError(ApiError.INVALID_ADDRESS); + throw ApiErrorFactory.getInstance().createError(ApiError.INVALID_ADDRESS); byte[] lastReference = null; try (final Repository repository = RepositoryManager.getRepository()) { @@ -139,7 +128,7 @@ public class AddressesResource { } catch (ApiException e) { throw e; } catch (DataException e) { - throw this.apiErrorFactory.createError(ApiError.REPOSITORY_ISSUE, e); + throw ApiErrorFactory.getInstance().createError(ApiError.REPOSITORY_ISSUE, e); } if(lastReference == null || lastReference.length == 0) { @@ -163,8 +152,7 @@ public class AddressesResource { }, responses = { @ApiResponse( - //description = "", - content = @Content(schema = @Schema(implementation = Boolean.class)), + content = @Content(mediaType = MediaType.TEXT_PLAIN, schema = @Schema(type = "boolean")), extensions = { @Extension(name = "translation", properties = { @ExtensionProperty(name="description.key", value="success_response:description") @@ -194,7 +182,7 @@ public class AddressesResource { responses = { @ApiResponse( description = "the generating balance", - content = @Content(schema = @Schema(implementation = BigDecimal.class)), + content = @Content(mediaType = MediaType.TEXT_PLAIN, schema = @Schema(type = "string", format = "number")), extensions = { @Extension(name = "translation", properties = { @ExtensionProperty(name="description.key", value="success_response:description") @@ -205,7 +193,7 @@ public class AddressesResource { ) public BigDecimal getGeneratingBalanceOfAddress(@PathParam("address") String address) { if (!Crypto.isValidAddress(address)) - throw this.apiErrorFactory.createError(ApiError.INVALID_ADDRESS); + throw ApiErrorFactory.getInstance().createError(ApiError.INVALID_ADDRESS); try (final Repository repository = RepositoryManager.getRepository()) { Account account = new Account(repository, address); @@ -213,7 +201,7 @@ public class AddressesResource { } catch (ApiException e) { throw e; } catch (DataException e) { - throw this.apiErrorFactory.createError(ApiError.REPOSITORY_ISSUE, e); + throw ApiErrorFactory.getInstance().createError(ApiError.REPOSITORY_ISSUE, e); } } @@ -233,7 +221,7 @@ public class AddressesResource { responses = { @ApiResponse( description = "the balance", - content = @Content(schema = @Schema(name = "balance", type = "number")), + content = @Content(mediaType = MediaType.TEXT_PLAIN, schema = @Schema(type = "string", format = "number")), extensions = { @Extension(name = "translation", properties = { @ExtensionProperty(name="description.key", value="success_response:description") @@ -244,7 +232,7 @@ public class AddressesResource { ) public BigDecimal getGeneratingBalance(@PathParam("address") String address) { if (!Crypto.isValidAddress(address)) - throw this.apiErrorFactory.createError(ApiError.INVALID_ADDRESS); + throw ApiErrorFactory.getInstance().createError(ApiError.INVALID_ADDRESS); try (final Repository repository = RepositoryManager.getRepository()) { Account account = new Account(repository, address); @@ -252,7 +240,7 @@ public class AddressesResource { } catch (ApiException e) { throw e; } catch (DataException e) { - throw this.apiErrorFactory.createError(ApiError.REPOSITORY_ISSUE, e); + throw ApiErrorFactory.getInstance().createError(ApiError.REPOSITORY_ISSUE, e); } } @@ -273,7 +261,7 @@ public class AddressesResource { responses = { @ApiResponse( description = "the balance", - content = @Content(schema = @Schema(implementation = BigDecimal.class)), + content = @Content(mediaType = MediaType.TEXT_PLAIN, schema = @Schema(type = "string", format = "number")), extensions = { @Extension(name = "translation", properties = { @ExtensionProperty(name="description.key", value="success_response:description") @@ -284,7 +272,7 @@ public class AddressesResource { ) public BigDecimal getAssetBalance(@PathParam("assetid") long assetid, @PathParam("address") String address) { if (!Crypto.isValidAddress(address)) - throw this.apiErrorFactory.createError(ApiError.INVALID_ADDRESS); + throw ApiErrorFactory.getInstance().createError(ApiError.INVALID_ADDRESS); try (final Repository repository = RepositoryManager.getRepository()) { Account account = new Account(repository, address); @@ -292,7 +280,7 @@ public class AddressesResource { } catch (ApiException e) { throw e; } catch (DataException e) { - throw this.apiErrorFactory.createError(ApiError.REPOSITORY_ISSUE, e); + throw ApiErrorFactory.getInstance().createError(ApiError.REPOSITORY_ISSUE, e); } } @@ -324,14 +312,14 @@ public class AddressesResource { ) public List getAssets(@PathParam("address") String address) { if (!Crypto.isValidAddress(address)) - throw this.apiErrorFactory.createError(ApiError.INVALID_ADDRESS); + throw ApiErrorFactory.getInstance().createError(ApiError.INVALID_ADDRESS); try (final Repository repository = RepositoryManager.getRepository()) { return repository.getAccountRepository().getAllBalances(address); } catch (ApiException e) { throw e; } catch (DataException e) { - throw this.apiErrorFactory.createError(ApiError.REPOSITORY_ISSUE, e); + throw ApiErrorFactory.getInstance().createError(ApiError.REPOSITORY_ISSUE, e); } } @@ -381,7 +369,7 @@ public class AddressesResource { responses = { @ApiResponse( description = "the public key", - content = @Content(schema = @Schema(implementation = String.class)), + content = @Content(mediaType = MediaType.TEXT_PLAIN, schema = @Schema(type = "string")), extensions = { @Extension(name = "translation", properties = { @ExtensionProperty(name="description.key", value="success_response:description") @@ -392,7 +380,7 @@ public class AddressesResource { ) public String getPublicKey(@PathParam("address") String address) { if (!Crypto.isValidAddress(address)) - throw this.apiErrorFactory.createError(ApiError.INVALID_ADDRESS); + throw ApiErrorFactory.getInstance().createError(ApiError.INVALID_ADDRESS); try (final Repository repository = RepositoryManager.getRepository()) { AccountData accountData = repository.getAccountRepository().getAccount(address); @@ -408,13 +396,12 @@ public class AddressesResource { } 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("/convert/{publickey}") - @Produces(MediaType.TEXT_PLAIN) @Operation( summary = "Convert public key into address", description = "Returns account address based on supplied public key. Expects base64-encoded, 32-byte public key.", @@ -430,7 +417,7 @@ public class AddressesResource { responses = { @ApiResponse( description = "the address", - content = @Content(schema = @Schema(implementation = String.class)), + content = @Content(mediaType = MediaType.TEXT_PLAIN, schema = @Schema(type = "string")), extensions = { @Extension(name = "translation", properties = { @ExtensionProperty(name="description.key", value="success_response:description") @@ -445,19 +432,19 @@ public class AddressesResource { try { publicKeyBytes = Base64.getDecoder().decode(publicKey); } catch (NumberFormatException e) { - throw this.apiErrorFactory.createError(ApiError.INVALID_PUBLIC_KEY, e); + throw ApiErrorFactory.getInstance().createError(ApiError.INVALID_PUBLIC_KEY, e); } // Correct size for public key? if (publicKeyBytes.length != Transformer.PUBLIC_KEY_LENGTH) - throw this.apiErrorFactory.createError(ApiError.INVALID_PUBLIC_KEY); + throw ApiErrorFactory.getInstance().createError(ApiError.INVALID_PUBLIC_KEY); try (final Repository repository = RepositoryManager.getRepository()) { return Crypto.toAddress(publicKeyBytes); } 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/ApiError.java b/src/api/ApiError.java index b29fe66d..a2b5ae2d 100644 --- a/src/api/ApiError.java +++ b/src/api/ApiError.java @@ -6,7 +6,7 @@ public enum ApiError { JSON(1, 400), NO_BALANCE(2, 422), NOT_YET_RELEASED(3, 422), - UNAUTHORIZED(4, 401), + UNAUTHORIZED(4, 403), REPOSITORY_ISSUE(5, 500), //VALIDATION @@ -67,6 +67,8 @@ public enum ApiError { //ASSET INVALID_ASSET_ID(601, 400), + INVALID_ORDER_ID(602, 400), + ORDER_NO_EXISTS(603, 404), //NAME PAYMENTS NAME_NOT_REGISTERED(701, 422), diff --git a/src/api/ApiErrorMessage.java b/src/api/ApiErrorMessage.java index 0c84fe4a..161e1836 100644 --- a/src/api/ApiErrorMessage.java +++ b/src/api/ApiErrorMessage.java @@ -1,15 +1,14 @@ package api; -import javax.xml.bind.annotation.XmlElement; -import javax.xml.bind.annotation.XmlRootElement; +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; -@XmlRootElement +// All properties to be converted to JSON via JAX-RS +@XmlAccessorType(XmlAccessType.FIELD) public class ApiErrorMessage { - @XmlElement(name = "error") public int error; - @XmlElement(name = "message") public String message; ApiErrorMessage() { diff --git a/src/api/AssetsResource.java b/src/api/AssetsResource.java index 9f7cf111..1e0a2552 100644 --- a/src/api/AssetsResource.java +++ b/src/api/AssetsResource.java @@ -13,6 +13,7 @@ import repository.Repository; import repository.RepositoryManager; import java.util.ArrayList; +import java.util.Base64; import java.util.List; import javax.servlet.http.HttpServletRequest; @@ -27,7 +28,9 @@ import javax.ws.rs.core.MediaType; import api.models.AssetWithHolders; import api.models.IssueAssetRequest; +import api.models.OrderWithTrades; import api.models.TradeWithOrderInfo; +import data.account.AccountBalanceData; import data.assets.AssetData; import data.assets.OrderData; import data.assets.TradeData; @@ -78,7 +81,7 @@ public class AssetsResource { ) } ) - public AssetWithHolders getAssetInfo(@QueryParam("assetId") Integer assetId, @QueryParam("assetName") String assetName, @Parameter(ref = "includeHolders") @QueryParam("withHolders") boolean includeHolders) { + public AssetWithHolders getAssetInfo(@QueryParam("assetId") Integer assetId, @QueryParam("assetName") String assetName, @Parameter(ref = "includeHolders") @QueryParam("includeHolders") boolean includeHolders) { if (assetId == null && (assetName == null || assetName.isEmpty())) throw ApiErrorFactory.getInstance().createError(ApiError.INVALID_CRITERIA); @@ -93,7 +96,11 @@ public class AssetsResource { if (assetData == null) throw ApiErrorFactory.getInstance().createError(ApiError.INVALID_ASSET_ID); - return new AssetWithHolders(repository, assetData, includeHolders); + List holders = null; + if (includeHolders) + holders = repository.getAccountRepository().getAssetBalances(assetData.getAssetId()); + + return new AssetWithHolders(assetData, holders); } catch (DataException e) { throw ApiErrorFactory.getInstance().createError(ApiError.REPOSITORY_ISSUE, e); } @@ -165,8 +172,11 @@ public class AssetsResource { // Expanding remaining entries List fullTrades = new ArrayList<>(); - for (TradeData trade : trades) - fullTrades.add(new TradeWithOrderInfo(repository, trade)); + for (TradeData tradeData : trades) { + OrderData initiatingOrderData = repository.getAssetRepository().fromOrderId(tradeData.getInitiator()); + OrderData targetOrderData = repository.getAssetRepository().fromOrderId(tradeData.getTarget()); + fullTrades.add(new TradeWithOrderInfo(tradeData, initiatingOrderData, targetOrderData)); + } return fullTrades; } catch (DataException e) { @@ -174,6 +184,40 @@ public class AssetsResource { } } + @GET + @Path("/order/{orderId}") + @Operation( + summary = "Fetch asset order", + description = "Returns asset order info.", + responses = { + @ApiResponse( + description = "asset order", + content = @Content(schema = @Schema(implementation = OrderData.class)) + ) + } + ) + public OrderWithTrades getAssetOrder(@PathParam("orderId") String orderId64) { + // Decode orderID + byte[] orderId; + try { + orderId = Base64.getDecoder().decode(orderId64); + } catch (NumberFormatException e) { + throw ApiErrorFactory.getInstance().createError(ApiError.INVALID_ORDER_ID, e); + } + + try (final Repository repository = RepositoryManager.getRepository()) { + OrderData orderData = repository.getAssetRepository().fromOrderId(orderId); + if (orderData == null) + throw ApiErrorFactory.getInstance().createError(ApiError.ORDER_NO_EXISTS); + + List trades = repository.getAssetRepository().getOrdersTrades(orderId); + + return new OrderWithTrades(orderData, trades); + } catch (DataException e) { + throw ApiErrorFactory.getInstance().createError(ApiError.REPOSITORY_ISSUE, e); + } + } + @POST @Path("/issue") @Operation( diff --git a/src/api/BlocksResource.java b/src/api/BlocksResource.java index 3b1d33cb..450adb1e 100644 --- a/src/api/BlocksResource.java +++ b/src/api/BlocksResource.java @@ -1,6 +1,7 @@ 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; @@ -15,6 +16,7 @@ import java.math.BigDecimal; import java.util.ArrayList; import java.util.Base64; import java.util.List; +import java.util.stream.Collectors; import javax.servlet.http.HttpServletRequest; @@ -33,11 +35,8 @@ import repository.Repository; import repository.RepositoryManager; @Path("blocks") -@Produces({MediaType.APPLICATION_JSON, MediaType.TEXT_PLAIN}) -@Extension(name = "translation", properties = { - @ExtensionProperty(name="path", value="/Api/BlocksResource") - } -) +@Produces({ MediaType.APPLICATION_JSON, MediaType.TEXT_PLAIN }) +@Extension(name = "translation", properties = { @ExtensionProperty(name = "path", value = "/Api/BlocksResource") }) @Tag(name = "Blocks") public class BlocksResource { @@ -59,28 +58,23 @@ public class BlocksResource { @Operation( summary = "Fetch block using base64 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 signature, + @Parameter(ref = "includeTransactions") @QueryParam("includeTransactions") boolean includeTransactions) { // Decode signature byte[] signatureBytes; try { @@ -91,7 +85,7 @@ public class BlocksResource { try (final Repository repository = RepositoryManager.getRepository()) { BlockData blockData = repository.getBlockRepository().fromSignature(signatureBytes); - return new BlockWithTransactions(repository, blockData, includeTransactions); + return packageBlockData(repository, blockData, includeTransactions); } catch (ApiException e) { throw e; } catch (DataException e) { @@ -104,26 +98,23 @@ public class BlocksResource { @Operation( summary = "Fetch genesis block", description = "Returns the genesis block", - extensions = @Extension(name = "translation", 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") - }) - } - ) - } + extensions = @Extension( + name = "translation", + 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") }) } + ) } ) public BlockWithTransactions getFirstBlock(@Parameter(ref = "includeTransactions") @QueryParam("includeTransactions") boolean includeTransactions) { try (final Repository repository = RepositoryManager.getRepository()) { BlockData blockData = repository.getBlockRepository().fromHeight(1); - return new BlockWithTransactions(repository, blockData, includeTransactions); + return packageBlockData(repository, blockData, includeTransactions); } catch (ApiException e) { throw e; } catch (DataException e) { @@ -136,26 +127,23 @@ public class BlocksResource { @Operation( summary = "Fetch last/newest block in blockchain", description = "Returns the last valid block", - extensions = @Extension(name = "translation", 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") - }) - } - ) - } + extensions = @Extension( + name = "translation", + 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") }) } + ) } ) public BlockWithTransactions getLastBlock(@Parameter(ref = "includeTransactions") @QueryParam("includeTransactions") boolean includeTransactions) { try (final Repository repository = RepositoryManager.getRepository()) { BlockData blockData = repository.getBlockRepository().getLastBlock(); - return new BlockWithTransactions(repository, blockData, includeTransactions); + return packageBlockData(repository, blockData, includeTransactions); } catch (ApiException e) { throw e; } catch (DataException e) { @@ -168,28 +156,23 @@ public class BlocksResource { @Operation( summary = "Fetch child block using base64 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 signature, + @Parameter(ref = "includeTransactions") @QueryParam("includeTransactions") boolean includeTransactions) { // Decode signature byte[] signatureBytes; try { @@ -202,16 +185,13 @@ public class BlocksResource { BlockData blockData = repository.getBlockRepository().fromSignature(signatureBytes); // Check block exists - if(blockData == null) + if (blockData == null) throw this.apiErrorFactory.createError(ApiError.BLOCK_NO_EXISTS); BlockData childBlockData = repository.getBlockRepository().fromReference(signatureBytes); - // Check child exists - if(childBlockData == null) - throw this.apiErrorFactory.createError(ApiError.BLOCK_NO_EXISTS); - - return new BlockWithTransactions(repository, childBlockData, includeTransactions); + // Checking child exists is handled by packageBlockData() + return packageBlockData(repository, childBlockData, includeTransactions); } catch (ApiException e) { throw e; } catch (DataException e) { @@ -224,21 +204,18 @@ public class BlocksResource { @Operation( summary = "Generating balance of next block", 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") - }), - 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") - }) - } - ) - } + extensions = @Extension( + name = "translation", + 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") }) } + ) } ) public BigDecimal getGeneratingBalance() { try (final Repository repository = RepositoryManager.getRepository()) { @@ -257,26 +234,21 @@ 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(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) { // Decode signature @@ -308,21 +280,18 @@ public class BlocksResource { @Operation( summary = "Estimated time to forge next block", 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") - }), - 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") - }) - } - ) - } + extensions = @Extension( + name = "translation", + 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") }) } + ) } ) public long getTimePerBlock() { try (final Repository repository = RepositoryManager.getRepository()) { @@ -340,21 +309,18 @@ public class BlocksResource { @Operation( summary = "Estimated time to forge block given generating balance", 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") - }), - 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") - }) - } - ) - } + extensions = @Extension( + name = "translation", + 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") }) } + ) } ) public long getTimePerBlock(@PathParam("generating") BigDecimal generatingbalance) { return Block.calcForgingDelay(generatingbalance); @@ -365,21 +331,18 @@ public class BlocksResource { @Operation( summary = "Current blockchain height", 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") - }), - 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"), @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") }) } + ) } ) public int getHeight() { try (final Repository repository = RepositoryManager.getRepository()) { @@ -396,26 +359,20 @@ 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(schema = @Schema(implementation = int.class)), + extensions = + { @Extension(name = "translation", properties = { @ExtensionProperty(name = "description.key", value = "success_response:description") }) } + ) } ) public int getHeight(@PathParam("signature") String signature) { // Decode signature @@ -446,31 +403,26 @@ 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 new BlockWithTransactions(repository, blockData, includeTransactions); + return packageBlockData(repository, blockData, includeTransactions); } catch (ApiException e) { throw e; } catch (DataException e) { @@ -483,26 +435,20 @@ 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) { boolean includeTransactions = false; @@ -516,7 +462,7 @@ public class BlocksResource { // Run out of blocks! break; - blocks.add(new BlockWithTransactions(repository, blockData, includeTransactions)); + blocks.add(packageBlockData(repository, blockData, includeTransactions)); } return blocks; @@ -527,4 +473,17 @@ public class BlocksResource { } } + private BlockWithTransactions packageBlockData(Repository repository, BlockData blockData, boolean includeTransactions) throws DataException { + if (blockData == null) + throw this.apiErrorFactory.createError(ApiError.BLOCK_NO_EXISTS); + + List transactions = null; + if (includeTransactions) { + Block block = new Block(repository, blockData); + transactions = block.getTransactions().stream().map(transaction -> transaction.getTransactionData()).collect(Collectors.toList()); + } + + return new BlockWithTransactions(blockData, transactions); + } + } diff --git a/src/api/TransactionsResource.java b/src/api/TransactionsResource.java index 1b022b1c..39505032 100644 --- a/src/api/TransactionsResource.java +++ b/src/api/TransactionsResource.java @@ -209,7 +209,7 @@ 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") Integer limit, @Parameter(ref = "offset") @QueryParam("offset") int offset) { + @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); diff --git a/src/api/models/AssetWithHolders.java b/src/api/models/AssetWithHolders.java index d3e0978f..b499f605 100644 --- a/src/api/models/AssetWithHolders.java +++ b/src/api/models/AssetWithHolders.java @@ -2,17 +2,17 @@ package api.models; import java.util.List; +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlElement; -import api.ApiError; -import api.ApiErrorFactory; import data.account.AccountBalanceData; import data.assets.AssetData; import io.swagger.v3.oas.annotations.media.Schema; -import repository.DataException; -import repository.Repository; -@Schema(description = "Asset with (optional) asset holders") +@Schema(description = "Asset info, maybe including asset holders") +// All properties to be converted to JSON via JAX-RS +@XmlAccessorType(XmlAccessType.FIELD) public class AssetWithHolders { @Schema(implementation = AssetData.class, name = "asset", title = "asset data") @@ -22,18 +22,12 @@ public class AssetWithHolders { public List holders; // For JAX-RS - @SuppressWarnings("unused") - private AssetWithHolders() { + protected AssetWithHolders() { } - public AssetWithHolders(Repository repository, AssetData assetData, boolean includeHolders) throws DataException { - if (assetData == null) - throw ApiErrorFactory.getInstance().createError(ApiError.INVALID_ASSET_ID); - + public AssetWithHolders(AssetData assetData, List holders) { this.assetData = assetData; - - if (includeHolders) - this.holders = repository.getAccountRepository().getAssetBalances(assetData.getAssetId()); + this.holders = holders; } } diff --git a/src/api/models/BlockWithTransactions.java b/src/api/models/BlockWithTransactions.java index 0844a589..3a6f7854 100644 --- a/src/api/models/BlockWithTransactions.java +++ b/src/api/models/BlockWithTransactions.java @@ -1,22 +1,17 @@ package api.models; import java.util.List; -import java.util.stream.Collectors; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlElement; -import api.ApiError; -import api.ApiErrorFactory; import data.block.BlockData; import data.transaction.TransactionData; import io.swagger.v3.oas.annotations.media.Schema; -import qora.block.Block; -import repository.DataException; -import repository.Repository; -@Schema(description = "Block with (optional) transactions") +@Schema(description = "Block info, maybe including transactions") +// All properties to be converted to JSON via JAX-RS @XmlAccessorType(XmlAccessType.FIELD) public class BlockWithTransactions { @@ -30,16 +25,9 @@ public class BlockWithTransactions { protected BlockWithTransactions() { } - public BlockWithTransactions(Repository repository, BlockData blockData, boolean includeTransactions) throws DataException { - if (blockData == null) - throw ApiErrorFactory.getInstance().createError(ApiError.BLOCK_NO_EXISTS); - + public BlockWithTransactions(BlockData blockData, List transactions) { this.blockData = blockData; - - if (includeTransactions) { - Block block = new Block(repository, blockData); - this.transactions = block.getTransactions().stream().map(transaction -> transaction.getTransactionData()).collect(Collectors.toList()); - } + this.transactions = transactions; } } diff --git a/src/api/models/OrderWithTrades.java b/src/api/models/OrderWithTrades.java new file mode 100644 index 00000000..e8c5731b --- /dev/null +++ b/src/api/models/OrderWithTrades.java @@ -0,0 +1,33 @@ +package api.models; + +import java.util.List; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlElement; + +import data.assets.OrderData; +import data.assets.TradeData; +import io.swagger.v3.oas.annotations.media.Schema; + +@Schema(description = "Asset order info, maybe including trades") +// All properties to be converted to JSON via JAX-RS +@XmlAccessorType(XmlAccessType.FIELD) +public class OrderWithTrades { + + @Schema(implementation = OrderData.class, name = "order", title = "order data") + @XmlElement(name = "order") + public OrderData orderData; + + List trades; + + // For JAX-RS + protected OrderWithTrades() { + } + + public OrderWithTrades(OrderData orderData, List trades) { + this.orderData = orderData; + this.trades = trades; + } + +} diff --git a/src/api/models/TradeWithOrderInfo.java b/src/api/models/TradeWithOrderInfo.java index 51484352..c6c3d62b 100644 --- a/src/api/models/TradeWithOrderInfo.java +++ b/src/api/models/TradeWithOrderInfo.java @@ -1,14 +1,16 @@ package api.models; +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlElement; import data.assets.OrderData; import data.assets.TradeData; import io.swagger.v3.oas.annotations.media.Schema; -import repository.DataException; -import repository.Repository; -@Schema(description = "Asset trade, with order info") +@Schema(description = "Asset trade, including order info") +// All properties to be converted to JSON via JAX-RS +@XmlAccessorType(XmlAccessType.FIELD) public class TradeWithOrderInfo { @Schema(implementation = TradeData.class, name = "trade", title = "trade data") @@ -27,11 +29,10 @@ public class TradeWithOrderInfo { protected TradeWithOrderInfo() { } - public TradeWithOrderInfo(Repository repository, TradeData tradeData) throws DataException { + public TradeWithOrderInfo(TradeData tradeData, OrderData initiatingOrderData, OrderData targetOrderData) { this.tradeData = tradeData; - - this.initiatingOrderData = repository.getAssetRepository().fromOrderId(tradeData.getInitiator()); - this.targetOrderData = repository.getAssetRepository().fromOrderId(tradeData.getTarget()); + this.initiatingOrderData = initiatingOrderData; + this.targetOrderData = targetOrderData; } } diff --git a/src/api/models/TransactionClassExtractor.java b/src/api/models/TransactionClassExtractor.java index d215c219..250caf3b 100644 --- a/src/api/models/TransactionClassExtractor.java +++ b/src/api/models/TransactionClassExtractor.java @@ -6,6 +6,7 @@ import org.eclipse.persistence.sessions.Session; public class TransactionClassExtractor extends ClassExtractor { + @SuppressWarnings("rawtypes") @Override public Class extractClassFromRow(Record record, Session session) { // Never called anyway? diff --git a/src/data/PaymentData.java b/src/data/PaymentData.java index 96b3eafd..04f25966 100644 --- a/src/data/PaymentData.java +++ b/src/data/PaymentData.java @@ -5,7 +5,7 @@ import java.math.BigDecimal; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; -//All properties to be converted to JSON via JAX-RS +// All properties to be converted to JSON via JAX-RS @XmlAccessorType(XmlAccessType.FIELD) public class PaymentData { diff --git a/src/qora/assets/Order.java b/src/qora/assets/Order.java index e164b53c..da45badd 100644 --- a/src/qora/assets/Order.java +++ b/src/qora/assets/Order.java @@ -3,6 +3,7 @@ package qora.assets; import java.math.BigDecimal; import java.math.BigInteger; import java.math.RoundingMode; +import java.util.Arrays; import java.util.List; import org.apache.logging.log4j.LogManager; @@ -209,10 +210,11 @@ public class Order { public void orphan() throws DataException { // Orphan trades that occurred as a result of this order - for (TradeData tradeData : getTrades()) { - Trade trade = new Trade(this.repository, tradeData); - trade.orphan(); - } + for (TradeData tradeData : getTrades()) + if (Arrays.equals(this.orderData.getOrderId(), tradeData.getInitiator())) { + Trade trade = new Trade(this.repository, tradeData); + trade.orphan(); + } // Delete this order from repository this.repository.getAssetRepository().delete(this.orderData.getOrderId()); diff --git a/src/repository/AssetRepository.java b/src/repository/AssetRepository.java index 224c3484..f09ecdfa 100644 --- a/src/repository/AssetRepository.java +++ b/src/repository/AssetRepository.java @@ -40,6 +40,7 @@ public interface AssetRepository { public List getTrades(long haveAssetId, long wantAssetId) throws DataException; + /** Returns TradeData for trades where orderId was involved, i.e. either initiating OR target order */ public List getOrdersTrades(byte[] orderId) throws DataException; public void save(TradeData tradeData) throws DataException; diff --git a/src/repository/hsqldb/HSQLDBAssetRepository.java b/src/repository/hsqldb/HSQLDBAssetRepository.java index e1cd9279..cd63a09d 100644 --- a/src/repository/hsqldb/HSQLDBAssetRepository.java +++ b/src/repository/hsqldb/HSQLDBAssetRepository.java @@ -257,19 +257,21 @@ public class HSQLDBAssetRepository implements AssetRepository { } @Override - public List getOrdersTrades(byte[] initiatingOrderId) throws DataException { + public List getOrdersTrades(byte[] orderId) throws DataException { List trades = new ArrayList(); - try (ResultSet resultSet = this.repository - .checkedExecute("SELECT target_order_id, amount, price, traded FROM AssetTrades WHERE initiating_order_id = ?", initiatingOrderId)) { + try (ResultSet resultSet = this.repository.checkedExecute( + "SELECT initiating_order_id, target_order_id, amount, price, traded FROM AssetTrades WHERE initiating_order_id = ? OR target_order_id = ?", + orderId, orderId)) { if (resultSet == null) return trades; do { - byte[] targetOrderId = resultSet.getBytes(1); - BigDecimal amount = resultSet.getBigDecimal(2); - BigDecimal price = resultSet.getBigDecimal(3); - long timestamp = resultSet.getTimestamp(4, Calendar.getInstance(HSQLDBRepository.UTC)).getTime(); + byte[] initiatingOrderId = resultSet.getBytes(1); + byte[] targetOrderId = resultSet.getBytes(2); + BigDecimal amount = resultSet.getBigDecimal(3); + BigDecimal price = resultSet.getBigDecimal(4); + long timestamp = resultSet.getTimestamp(5, Calendar.getInstance(HSQLDBRepository.UTC)).getTime(); TradeData trade = new TradeData(initiatingOrderId, targetOrderId, amount, price, timestamp); trades.add(trade); diff --git a/src/transform/block/BlockTransformer.java b/src/transform/block/BlockTransformer.java index f73471ac..2ed501b5 100644 --- a/src/transform/block/BlockTransformer.java +++ b/src/transform/block/BlockTransformer.java @@ -310,11 +310,7 @@ public class BlockTransformer extends Transformer { Order order = orderTransaction.getOrder(); List trades = order.getTrades(); - // Filter out trades with initiatingOrderId that don't match this order - trades.removeIf((TradeData tradeData) -> !Arrays.equals(tradeData.getInitiator(), order.getOrderData().getOrderId())); - - // Any trades left? - if (!trades.isEmpty()) { + if (trades.stream().anyMatch(tradeData -> Arrays.equals(tradeData.getInitiator(), order.getOrderData().getOrderId()))) { tradesHappened = true; // No need to check any further break;