API: assets & tidying

Cleaned up responses from /addresses/* endpoints
in that some return text/plain instead of application/json.

Removed need for class-local copy of ApiErrorFactory in
AddressesResource - using getInstance() instead.

Some work still needs to be done on annotating API errors.
API error examples in API UI rendered incorrectly - swagger-ui issue?

Removed repository-accessing code from api.models.*

Added /assets/order/{orderId} for fetching info on specific asset order.

NOTE: AssetRepository.getOrdersTrades() now returns trades where order is
initiating or target. (Previously was initiating order only).
qora.assets.Order.orphan() updated to reflect above change.

block-explorer.html fixed to use new API output.
This commit is contained in:
catbref 2018-12-13 12:22:46 +00:00
parent dcd19f8e42
commit 034cf5dee3
17 changed files with 344 additions and 335 deletions

View File

@ -88,7 +88,7 @@
document.body.innerHTML = html; document.body.innerHTML = html;
XHR({ XHR({
url: "/transactions/address/" + address, url: "/transactions/search?address=" + address,
onload: renderAddressTransactions, onload: renderAddressTransactions,
responseType: "json" responseType: "json"
}); });
@ -137,7 +137,7 @@
} }
function renderBlockInfo(e) { 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 // 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" ]; var props = [ "signature", "reference", "transactionsSignature", "generatorPublicKey", "generatorSignature" ];
@ -196,7 +196,7 @@
} }
function listBlock(e) { function listBlock(e) {
var blockData = e.target.response; var blockData = e.target.response.block;
var ourHeight = blockData.height; var ourHeight = blockData.height;
var blockTimestamp = new Date(blockData.timestamp).toUTCString(); var blockTimestamp = new Date(blockData.timestamp).toUTCString();

View File

@ -1,6 +1,5 @@
package api; package api;
import globalization.Translator;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.extensions.Extension; import io.swagger.v3.oas.annotations.extensions.Extension;
@ -45,16 +44,6 @@ public class AddressesResource {
@Context @Context
HttpServletRequest request; HttpServletRequest request;
private ApiErrorFactory apiErrorFactory;
public AddressesResource() {
this(new ApiErrorFactory(Translator.getInstance()));
}
public AddressesResource(ApiErrorFactory apiErrorFactory) {
this.apiErrorFactory = apiErrorFactory;
}
@GET @GET
@Path("/lastreference/{address}") @Path("/lastreference/{address}")
@Operation( @Operation(
@ -72,7 +61,7 @@ public class AddressesResource {
responses = { responses = {
@ApiResponse( @ApiResponse(
description = "the base64-encoded transaction signature or \"false\"", 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 = { extensions = {
@Extension(name = "translation", properties = { @Extension(name = "translation", properties = {
@ExtensionProperty(name="description.key", value="success_response:description") @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) { public String getLastReference(@Parameter(ref = "address") @PathParam("address") String address) {
if (!Crypto.isValidAddress(address)) if (!Crypto.isValidAddress(address))
throw this.apiErrorFactory.createError(ApiError.INVALID_ADDRESS); throw ApiErrorFactory.getInstance().createError(ApiError.INVALID_ADDRESS);
byte[] lastReference = null; byte[] lastReference = null;
try (final Repository repository = RepositoryManager.getRepository()) { try (final Repository repository = RepositoryManager.getRepository()) {
@ -92,7 +81,7 @@ public class AddressesResource {
} catch (ApiException e) { } catch (ApiException e) {
throw e; throw e;
} catch (DataException 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) { if(lastReference == null || lastReference.length == 0) {
@ -119,7 +108,7 @@ public class AddressesResource {
responses = { responses = {
@ApiResponse( @ApiResponse(
description = "the base64-encoded transaction signature", description = "the base64-encoded transaction signature",
content = @Content(schema = @Schema(implementation = String.class)), content = @Content(mediaType = MediaType.TEXT_PLAIN, schema = @Schema(type = "string")),
extensions = { extensions = {
@Extension(name = "translation", properties = { @Extension(name = "translation", properties = {
@ExtensionProperty(name="description.key", value="success_response:description") @ExtensionProperty(name="description.key", value="success_response:description")
@ -130,7 +119,7 @@ public class AddressesResource {
) )
public String getLastReferenceUnconfirmed(@PathParam("address") String address) { public String getLastReferenceUnconfirmed(@PathParam("address") String address) {
if (!Crypto.isValidAddress(address)) if (!Crypto.isValidAddress(address))
throw this.apiErrorFactory.createError(ApiError.INVALID_ADDRESS); throw ApiErrorFactory.getInstance().createError(ApiError.INVALID_ADDRESS);
byte[] lastReference = null; byte[] lastReference = null;
try (final Repository repository = RepositoryManager.getRepository()) { try (final Repository repository = RepositoryManager.getRepository()) {
@ -139,7 +128,7 @@ public class AddressesResource {
} catch (ApiException e) { } catch (ApiException e) {
throw e; throw e;
} catch (DataException 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) { if(lastReference == null || lastReference.length == 0) {
@ -163,8 +152,7 @@ public class AddressesResource {
}, },
responses = { responses = {
@ApiResponse( @ApiResponse(
//description = "", content = @Content(mediaType = MediaType.TEXT_PLAIN, schema = @Schema(type = "boolean")),
content = @Content(schema = @Schema(implementation = Boolean.class)),
extensions = { extensions = {
@Extension(name = "translation", properties = { @Extension(name = "translation", properties = {
@ExtensionProperty(name="description.key", value="success_response:description") @ExtensionProperty(name="description.key", value="success_response:description")
@ -194,7 +182,7 @@ public class AddressesResource {
responses = { responses = {
@ApiResponse( @ApiResponse(
description = "the generating balance", description = "the generating balance",
content = @Content(schema = @Schema(implementation = BigDecimal.class)), content = @Content(mediaType = MediaType.TEXT_PLAIN, schema = @Schema(type = "string", format = "number")),
extensions = { extensions = {
@Extension(name = "translation", properties = { @Extension(name = "translation", properties = {
@ExtensionProperty(name="description.key", value="success_response:description") @ExtensionProperty(name="description.key", value="success_response:description")
@ -205,7 +193,7 @@ public class AddressesResource {
) )
public BigDecimal getGeneratingBalanceOfAddress(@PathParam("address") String address) { public BigDecimal getGeneratingBalanceOfAddress(@PathParam("address") String address) {
if (!Crypto.isValidAddress(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()) { try (final Repository repository = RepositoryManager.getRepository()) {
Account account = new Account(repository, address); Account account = new Account(repository, address);
@ -213,7 +201,7 @@ public class AddressesResource {
} catch (ApiException e) { } catch (ApiException e) {
throw e; throw e;
} catch (DataException 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 = { responses = {
@ApiResponse( @ApiResponse(
description = "the balance", description = "the balance",
content = @Content(schema = @Schema(name = "balance", type = "number")), content = @Content(mediaType = MediaType.TEXT_PLAIN, schema = @Schema(type = "string", format = "number")),
extensions = { extensions = {
@Extension(name = "translation", properties = { @Extension(name = "translation", properties = {
@ExtensionProperty(name="description.key", value="success_response:description") @ExtensionProperty(name="description.key", value="success_response:description")
@ -244,7 +232,7 @@ public class AddressesResource {
) )
public BigDecimal getGeneratingBalance(@PathParam("address") String address) { public BigDecimal getGeneratingBalance(@PathParam("address") String address) {
if (!Crypto.isValidAddress(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()) { try (final Repository repository = RepositoryManager.getRepository()) {
Account account = new Account(repository, address); Account account = new Account(repository, address);
@ -252,7 +240,7 @@ public class AddressesResource {
} catch (ApiException e) { } catch (ApiException e) {
throw e; throw e;
} catch (DataException 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 = { responses = {
@ApiResponse( @ApiResponse(
description = "the balance", description = "the balance",
content = @Content(schema = @Schema(implementation = BigDecimal.class)), content = @Content(mediaType = MediaType.TEXT_PLAIN, schema = @Schema(type = "string", format = "number")),
extensions = { extensions = {
@Extension(name = "translation", properties = { @Extension(name = "translation", properties = {
@ExtensionProperty(name="description.key", value="success_response:description") @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) { public BigDecimal getAssetBalance(@PathParam("assetid") long assetid, @PathParam("address") String address) {
if (!Crypto.isValidAddress(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()) { try (final Repository repository = RepositoryManager.getRepository()) {
Account account = new Account(repository, address); Account account = new Account(repository, address);
@ -292,7 +280,7 @@ public class AddressesResource {
} catch (ApiException e) { } catch (ApiException e) {
throw e; throw e;
} catch (DataException 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<AccountBalanceData> getAssets(@PathParam("address") String address) { public List<AccountBalanceData> getAssets(@PathParam("address") String address) {
if (!Crypto.isValidAddress(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()) { try (final Repository repository = RepositoryManager.getRepository()) {
return repository.getAccountRepository().getAllBalances(address); return repository.getAccountRepository().getAllBalances(address);
} catch (ApiException e) { } catch (ApiException e) {
throw e; throw e;
} catch (DataException 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 = { responses = {
@ApiResponse( @ApiResponse(
description = "the public key", description = "the public key",
content = @Content(schema = @Schema(implementation = String.class)), content = @Content(mediaType = MediaType.TEXT_PLAIN, schema = @Schema(type = "string")),
extensions = { extensions = {
@Extension(name = "translation", properties = { @Extension(name = "translation", properties = {
@ExtensionProperty(name="description.key", value="success_response:description") @ExtensionProperty(name="description.key", value="success_response:description")
@ -392,7 +380,7 @@ public class AddressesResource {
) )
public String getPublicKey(@PathParam("address") String address) { public String getPublicKey(@PathParam("address") String address) {
if (!Crypto.isValidAddress(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()) { try (final Repository repository = RepositoryManager.getRepository()) {
AccountData accountData = repository.getAccountRepository().getAccount(address); AccountData accountData = repository.getAccountRepository().getAccount(address);
@ -408,13 +396,12 @@ public class AddressesResource {
} catch (ApiException e) { } catch (ApiException e) {
throw e; throw e;
} catch (DataException e) { } catch (DataException e) {
throw this.apiErrorFactory.createError(ApiError.REPOSITORY_ISSUE, e); throw ApiErrorFactory.getInstance().createError(ApiError.REPOSITORY_ISSUE, e);
} }
} }
@GET @GET
@Path("/convert/{publickey}") @Path("/convert/{publickey}")
@Produces(MediaType.TEXT_PLAIN)
@Operation( @Operation(
summary = "Convert public key into address", 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 base64-encoded, 32-byte public key.",
@ -430,7 +417,7 @@ public class AddressesResource {
responses = { responses = {
@ApiResponse( @ApiResponse(
description = "the address", description = "the address",
content = @Content(schema = @Schema(implementation = String.class)), content = @Content(mediaType = MediaType.TEXT_PLAIN, schema = @Schema(type = "string")),
extensions = { extensions = {
@Extension(name = "translation", properties = { @Extension(name = "translation", properties = {
@ExtensionProperty(name="description.key", value="success_response:description") @ExtensionProperty(name="description.key", value="success_response:description")
@ -445,19 +432,19 @@ public class AddressesResource {
try { try {
publicKeyBytes = Base64.getDecoder().decode(publicKey); publicKeyBytes = Base64.getDecoder().decode(publicKey);
} catch (NumberFormatException e) { } 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? // Correct size for public key?
if (publicKeyBytes.length != Transformer.PUBLIC_KEY_LENGTH) 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()) { try (final Repository repository = RepositoryManager.getRepository()) {
return Crypto.toAddress(publicKeyBytes); return Crypto.toAddress(publicKeyBytes);
} catch (ApiException e) { } catch (ApiException e) {
throw e; throw e;
} catch (DataException e) { } catch (DataException e) {
throw this.apiErrorFactory.createError(ApiError.REPOSITORY_ISSUE, e); throw ApiErrorFactory.getInstance().createError(ApiError.REPOSITORY_ISSUE, e);
} }
} }

View File

@ -6,7 +6,7 @@ public enum ApiError {
JSON(1, 400), JSON(1, 400),
NO_BALANCE(2, 422), NO_BALANCE(2, 422),
NOT_YET_RELEASED(3, 422), NOT_YET_RELEASED(3, 422),
UNAUTHORIZED(4, 401), UNAUTHORIZED(4, 403),
REPOSITORY_ISSUE(5, 500), REPOSITORY_ISSUE(5, 500),
//VALIDATION //VALIDATION
@ -67,6 +67,8 @@ public enum ApiError {
//ASSET //ASSET
INVALID_ASSET_ID(601, 400), INVALID_ASSET_ID(601, 400),
INVALID_ORDER_ID(602, 400),
ORDER_NO_EXISTS(603, 404),
//NAME PAYMENTS //NAME PAYMENTS
NAME_NOT_REGISTERED(701, 422), NAME_NOT_REGISTERED(701, 422),

View File

@ -1,15 +1,14 @@
package api; package api;
import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlRootElement; import javax.xml.bind.annotation.XmlAccessorType;
@XmlRootElement // All properties to be converted to JSON via JAX-RS
@XmlAccessorType(XmlAccessType.FIELD)
public class ApiErrorMessage { public class ApiErrorMessage {
@XmlElement(name = "error")
public int error; public int error;
@XmlElement(name = "message")
public String message; public String message;
ApiErrorMessage() { ApiErrorMessage() {

View File

@ -13,6 +13,7 @@ import repository.Repository;
import repository.RepositoryManager; import repository.RepositoryManager;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Base64;
import java.util.List; import java.util.List;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
@ -27,7 +28,9 @@ import javax.ws.rs.core.MediaType;
import api.models.AssetWithHolders; import api.models.AssetWithHolders;
import api.models.IssueAssetRequest; import api.models.IssueAssetRequest;
import api.models.OrderWithTrades;
import api.models.TradeWithOrderInfo; import api.models.TradeWithOrderInfo;
import data.account.AccountBalanceData;
import data.assets.AssetData; import data.assets.AssetData;
import data.assets.OrderData; import data.assets.OrderData;
import data.assets.TradeData; 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())) if (assetId == null && (assetName == null || assetName.isEmpty()))
throw ApiErrorFactory.getInstance().createError(ApiError.INVALID_CRITERIA); throw ApiErrorFactory.getInstance().createError(ApiError.INVALID_CRITERIA);
@ -93,7 +96,11 @@ public class AssetsResource {
if (assetData == null) if (assetData == null)
throw ApiErrorFactory.getInstance().createError(ApiError.INVALID_ASSET_ID); throw ApiErrorFactory.getInstance().createError(ApiError.INVALID_ASSET_ID);
return new AssetWithHolders(repository, assetData, includeHolders); List<AccountBalanceData> holders = null;
if (includeHolders)
holders = repository.getAccountRepository().getAssetBalances(assetData.getAssetId());
return new AssetWithHolders(assetData, holders);
} catch (DataException e) { } catch (DataException e) {
throw ApiErrorFactory.getInstance().createError(ApiError.REPOSITORY_ISSUE, e); throw ApiErrorFactory.getInstance().createError(ApiError.REPOSITORY_ISSUE, e);
} }
@ -165,8 +172,11 @@ public class AssetsResource {
// Expanding remaining entries // Expanding remaining entries
List<TradeWithOrderInfo> fullTrades = new ArrayList<>(); List<TradeWithOrderInfo> fullTrades = new ArrayList<>();
for (TradeData trade : trades) for (TradeData tradeData : trades) {
fullTrades.add(new TradeWithOrderInfo(repository, trade)); OrderData initiatingOrderData = repository.getAssetRepository().fromOrderId(tradeData.getInitiator());
OrderData targetOrderData = repository.getAssetRepository().fromOrderId(tradeData.getTarget());
fullTrades.add(new TradeWithOrderInfo(tradeData, initiatingOrderData, targetOrderData));
}
return fullTrades; return fullTrades;
} catch (DataException e) { } 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<TradeData> trades = repository.getAssetRepository().getOrdersTrades(orderId);
return new OrderWithTrades(orderData, trades);
} catch (DataException e) {
throw ApiErrorFactory.getInstance().createError(ApiError.REPOSITORY_ISSUE, e);
}
}
@POST @POST
@Path("/issue") @Path("/issue")
@Operation( @Operation(

View File

@ -1,6 +1,7 @@
package api; package api;
import data.block.BlockData; import data.block.BlockData;
import data.transaction.TransactionData;
import globalization.Translator; import globalization.Translator;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.Parameter;
@ -15,6 +16,7 @@ import java.math.BigDecimal;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Base64; import java.util.Base64;
import java.util.List; import java.util.List;
import java.util.stream.Collectors;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
@ -33,11 +35,8 @@ import repository.Repository;
import repository.RepositoryManager; import repository.RepositoryManager;
@Path("blocks") @Path("blocks")
@Produces({MediaType.APPLICATION_JSON, MediaType.TEXT_PLAIN}) @Produces({ MediaType.APPLICATION_JSON, MediaType.TEXT_PLAIN })
@Extension(name = "translation", properties = { @Extension(name = "translation", properties = { @ExtensionProperty(name = "path", value = "/Api/BlocksResource") })
@ExtensionProperty(name="path", value="/Api/BlocksResource")
}
)
@Tag(name = "Blocks") @Tag(name = "Blocks")
public class BlocksResource { public class BlocksResource {
@ -59,28 +58,23 @@ public class BlocksResource {
@Operation( @Operation(
summary = "Fetch block using base64 signature", summary = "Fetch block using base64 signature",
description = "Returns the block that matches the given signature", description = "Returns the block that matches the given signature",
extensions = { extensions =
@Extension(name = "translation", properties = { { @Extension(
@ExtensionProperty(name="path", value="GET signature"), name = "translation",
@ExtensionProperty(name="description.key", value="operation:description") properties =
}), { @ExtensionProperty(name = "path", value = "GET signature"), @ExtensionProperty(name = "description.key", value = "operation:description") }
@Extension(properties = { ), @Extension(properties =
@ExtensionProperty(name="apiErrors", value="[\"INVALID_SIGNATURE\", \"BLOCK_NO_EXISTS\"]", parseValue = true), { @ExtensionProperty(name = "apiErrors", value = "[\"INVALID_SIGNATURE\", \"BLOCK_NO_EXISTS\"]", parseValue = true), }) },
}) responses =
}, { @ApiResponse(
responses = { description = "the block",
@ApiResponse( content = @Content(schema = @Schema(implementation = BlockWithTransactions.class)),
description = "the block", extensions =
content = @Content(schema = @Schema(implementation = BlockWithTransactions.class)), { @Extension(name = "translation", properties = { @ExtensionProperty(name = "description.key", value = "success_response:description") }) }
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 // Decode signature
byte[] signatureBytes; byte[] signatureBytes;
try { try {
@ -91,7 +85,7 @@ public class BlocksResource {
try (final Repository repository = RepositoryManager.getRepository()) { try (final Repository repository = RepositoryManager.getRepository()) {
BlockData blockData = repository.getBlockRepository().fromSignature(signatureBytes); BlockData blockData = repository.getBlockRepository().fromSignature(signatureBytes);
return new BlockWithTransactions(repository, blockData, includeTransactions); return packageBlockData(repository, blockData, includeTransactions);
} catch (ApiException e) { } catch (ApiException e) {
throw e; throw e;
} catch (DataException e) { } catch (DataException e) {
@ -104,26 +98,23 @@ public class BlocksResource {
@Operation( @Operation(
summary = "Fetch genesis block", summary = "Fetch genesis block",
description = "Returns the genesis block", description = "Returns the genesis block",
extensions = @Extension(name = "translation", properties = { extensions = @Extension(
@ExtensionProperty(name="path", value="GET first"), name = "translation",
@ExtensionProperty(name="description.key", value="operation:description") properties =
}), { @ExtensionProperty(name = "path", value = "GET first"), @ExtensionProperty(name = "description.key", value = "operation:description") }
responses = { ),
@ApiResponse( responses =
description = "the block", { @ApiResponse(
content = @Content(schema = @Schema(implementation = BlockWithTransactions.class)), description = "the block",
extensions = { content = @Content(schema = @Schema(implementation = BlockWithTransactions.class)),
@Extension(name = "translation", properties = { extensions =
@ExtensionProperty(name="description.key", value="success_response:description") { @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()) { try (final Repository repository = RepositoryManager.getRepository()) {
BlockData blockData = repository.getBlockRepository().fromHeight(1); BlockData blockData = repository.getBlockRepository().fromHeight(1);
return new BlockWithTransactions(repository, blockData, includeTransactions); return packageBlockData(repository, blockData, includeTransactions);
} catch (ApiException e) { } catch (ApiException e) {
throw e; throw e;
} catch (DataException e) { } catch (DataException e) {
@ -136,26 +127,23 @@ public class BlocksResource {
@Operation( @Operation(
summary = "Fetch last/newest block in blockchain", summary = "Fetch last/newest block in blockchain",
description = "Returns the last valid block", description = "Returns the last valid block",
extensions = @Extension(name = "translation", properties = { extensions = @Extension(
@ExtensionProperty(name="path", value="GET last"), name = "translation",
@ExtensionProperty(name="description.key", value="operation:description") properties =
}), { @ExtensionProperty(name = "path", value = "GET last"), @ExtensionProperty(name = "description.key", value = "operation:description") }
responses = { ),
@ApiResponse( responses =
description = "the block", { @ApiResponse(
content = @Content(schema = @Schema(implementation = BlockWithTransactions.class)), description = "the block",
extensions = { content = @Content(schema = @Schema(implementation = BlockWithTransactions.class)),
@Extension(name = "translation", properties = { extensions =
@ExtensionProperty(name="description.key", value="success_response:description") { @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()) { try (final Repository repository = RepositoryManager.getRepository()) {
BlockData blockData = repository.getBlockRepository().getLastBlock(); BlockData blockData = repository.getBlockRepository().getLastBlock();
return new BlockWithTransactions(repository, blockData, includeTransactions); return packageBlockData(repository, blockData, includeTransactions);
} catch (ApiException e) { } catch (ApiException e) {
throw e; throw e;
} catch (DataException e) { } catch (DataException e) {
@ -168,28 +156,23 @@ public class BlocksResource {
@Operation( @Operation(
summary = "Fetch child block using base64 signature of parent block", summary = "Fetch child block using base64 signature of parent block",
description = "Returns the child block of the block that matches the given signature", description = "Returns the child block of the block that matches the given signature",
extensions = { extensions =
@Extension(name = "translation", properties = { { @Extension(
@ExtensionProperty(name="path", value="GET child:signature"), name = "translation",
@ExtensionProperty(name="description.key", value="operation:description") properties =
}), { @ExtensionProperty(name = "path", value = "GET child:signature"), @ExtensionProperty(name = "description.key", value = "operation:description") }
@Extension(properties = { ), @Extension(properties =
@ExtensionProperty(name="apiErrors", value="[\"INVALID_SIGNATURE\", \"BLOCK_NO_EXISTS\"]", parseValue = true), { @ExtensionProperty(name = "apiErrors", value = "[\"INVALID_SIGNATURE\", \"BLOCK_NO_EXISTS\"]", parseValue = true), }) },
}) responses =
}, { @ApiResponse(
responses = { description = "the block",
@ApiResponse( content = @Content(schema = @Schema(implementation = BlockWithTransactions.class)),
description = "the block", extensions =
content = @Content(schema = @Schema(implementation = BlockWithTransactions.class)), { @Extension(name = "translation", properties = { @ExtensionProperty(name = "description.key", value = "success_response:description") }) }
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 // Decode signature
byte[] signatureBytes; byte[] signatureBytes;
try { try {
@ -202,16 +185,13 @@ public class BlocksResource {
BlockData blockData = repository.getBlockRepository().fromSignature(signatureBytes); BlockData blockData = repository.getBlockRepository().fromSignature(signatureBytes);
// Check block exists // Check block exists
if(blockData == null) if (blockData == null)
throw this.apiErrorFactory.createError(ApiError.BLOCK_NO_EXISTS); throw this.apiErrorFactory.createError(ApiError.BLOCK_NO_EXISTS);
BlockData childBlockData = repository.getBlockRepository().fromReference(signatureBytes); BlockData childBlockData = repository.getBlockRepository().fromReference(signatureBytes);
// Check child exists // Checking child exists is handled by packageBlockData()
if(childBlockData == null) return packageBlockData(repository, childBlockData, includeTransactions);
throw this.apiErrorFactory.createError(ApiError.BLOCK_NO_EXISTS);
return new BlockWithTransactions(repository, childBlockData, includeTransactions);
} catch (ApiException e) { } catch (ApiException e) {
throw e; throw e;
} catch (DataException e) { } catch (DataException e) {
@ -224,21 +204,18 @@ public class BlocksResource {
@Operation( @Operation(
summary = "Generating balance of next block", summary = "Generating balance of next block",
description = "Calculates the generating balance of the block that will follow the last block", description = "Calculates the generating balance of the block that will follow the last block",
extensions = @Extension(name = "translation", properties = { extensions = @Extension(
@ExtensionProperty(name="path", value="GET generatingbalance"), name = "translation",
@ExtensionProperty(name="description.key", value="operation:description") properties =
}), { @ExtensionProperty(name = "path", value = "GET generatingbalance"), @ExtensionProperty(name = "description.key", value = "operation:description") }
responses = { ),
@ApiResponse( responses =
description = "the generating balance", { @ApiResponse(
content = @Content(schema = @Schema(implementation = BigDecimal.class)), description = "the generating balance",
extensions = { content = @Content(schema = @Schema(implementation = BigDecimal.class)),
@Extension(name = "translation", properties = { extensions =
@ExtensionProperty(name="description.key", value="success_response:description") { @Extension(name = "translation", properties = { @ExtensionProperty(name = "description.key", value = "success_response:description") }) }
}) ) }
}
)
}
) )
public BigDecimal getGeneratingBalance() { public BigDecimal getGeneratingBalance() {
try (final Repository repository = RepositoryManager.getRepository()) { try (final Repository repository = RepositoryManager.getRepository()) {
@ -257,26 +234,21 @@ public class BlocksResource {
@Operation( @Operation(
summary = "Generating balance of block after specific block", 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", description = "Calculates the generating balance of the block that will follow the block that matches the signature",
extensions = { extensions =
@Extension(name = "translation", properties = { { @Extension(
@ExtensionProperty(name="path", value="GET generatingbalance:signature"), name = "translation",
@ExtensionProperty(name="description.key", value="operation:description") properties =
}), { @ExtensionProperty(name = "path", value = "GET generatingbalance:signature"),
@Extension(properties = { @ExtensionProperty(name = "description.key", value = "operation:description") }
@ExtensionProperty(name="apiErrors", value="[\"INVALID_SIGNATURE\", \"BLOCK_NO_EXISTS\"]", parseValue = true), ), @Extension(properties =
}) { @ExtensionProperty(name = "apiErrors", value = "[\"INVALID_SIGNATURE\", \"BLOCK_NO_EXISTS\"]", parseValue = true), }) },
}, responses =
responses = { { @ApiResponse(
@ApiResponse( description = "the block",
description = "the block", content = @Content(schema = @Schema(implementation = BigDecimal.class)),
content = @Content(schema = @Schema(implementation = BigDecimal.class)), extensions =
extensions = { { @Extension(name = "translation", properties = { @ExtensionProperty(name = "description.key", value = "success_response:description") }) }
@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 signature) {
// Decode signature // Decode signature
@ -308,21 +280,18 @@ public class BlocksResource {
@Operation( @Operation(
summary = "Estimated time to forge next block", summary = "Estimated time to forge next block",
description = "Calculates the time it should take for the network to generate the next block", description = "Calculates the time it should take for the network to generate the next block",
extensions = @Extension(name = "translation", properties = { extensions = @Extension(
@ExtensionProperty(name="path", value="GET time"), name = "translation",
@ExtensionProperty(name="description.key", value="operation:description") properties =
}), { @ExtensionProperty(name = "path", value = "GET time"), @ExtensionProperty(name = "description.key", value = "operation:description") }
responses = { ),
@ApiResponse( responses =
description = "the time in seconds", // in seconds? { @ApiResponse(
content = @Content(schema = @Schema(implementation = long.class)), description = "the time in seconds", // in seconds?
extensions = { content = @Content(schema = @Schema(implementation = long.class)),
@Extension(name = "translation", properties = { extensions =
@ExtensionProperty(name="description.key", value="success_response:description") { @Extension(name = "translation", properties = { @ExtensionProperty(name = "description.key", value = "success_response:description") }) }
}) ) }
}
)
}
) )
public long getTimePerBlock() { public long getTimePerBlock() {
try (final Repository repository = RepositoryManager.getRepository()) { try (final Repository repository = RepositoryManager.getRepository()) {
@ -340,21 +309,18 @@ public class BlocksResource {
@Operation( @Operation(
summary = "Estimated time to forge block given generating balance", 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", description = "Calculates the time it should take for the network to generate blocks based on specified generating balance",
extensions = @Extension(name = "translation", properties = { extensions = @Extension(
@ExtensionProperty(name="path", value="GET time:generatingbalance"), name = "translation",
@ExtensionProperty(name="description.key", value="operation:description") properties =
}), { @ExtensionProperty(name = "path", value = "GET time:generatingbalance"), @ExtensionProperty(name = "description.key", value = "operation:description") }
responses = { ),
@ApiResponse( responses =
description = "the time", // in seconds? { @ApiResponse(
content = @Content(schema = @Schema(implementation = long.class)), description = "the time", // in seconds?
extensions = { content = @Content(schema = @Schema(implementation = long.class)),
@Extension(name = "translation", properties = { extensions =
@ExtensionProperty(name="description.key", value="success_response:description") { @Extension(name = "translation", properties = { @ExtensionProperty(name = "description.key", value = "success_response:description") }) }
}) ) }
}
)
}
) )
public long getTimePerBlock(@PathParam("generating") BigDecimal generatingbalance) { public long getTimePerBlock(@PathParam("generating") BigDecimal generatingbalance) {
return Block.calcForgingDelay(generatingbalance); return Block.calcForgingDelay(generatingbalance);
@ -365,21 +331,18 @@ public class BlocksResource {
@Operation( @Operation(
summary = "Current blockchain height", summary = "Current blockchain height",
description = "Returns the block height of the last block.", description = "Returns the block height of the last block.",
extensions = @Extension(name = "translation", properties = { extensions = @Extension(
@ExtensionProperty(name="path", value="GET height"), name = "translation",
@ExtensionProperty(name="description.key", value="operation:description") properties =
}), { @ExtensionProperty(name = "path", value = "GET height"), @ExtensionProperty(name = "description.key", value = "operation:description") }
responses = { ),
@ApiResponse( responses =
description = "the height", { @ApiResponse(
content = @Content(schema = @Schema(implementation = int.class)), description = "the height",
extensions = { content = @Content(schema = @Schema(implementation = int.class)),
@Extension(name = "translation", properties = { extensions =
@ExtensionProperty(name="description.key", value="success_response:description") { @Extension(name = "translation", properties = { @ExtensionProperty(name = "description.key", value = "success_response:description") }) }
}) ) }
}
)
}
) )
public int getHeight() { public int getHeight() {
try (final Repository repository = RepositoryManager.getRepository()) { try (final Repository repository = RepositoryManager.getRepository()) {
@ -396,26 +359,20 @@ public class BlocksResource {
@Operation( @Operation(
summary = "Height of specific block", summary = "Height of specific block",
description = "Returns the block height of the block that matches the given signature", description = "Returns the block height of the block that matches the given signature",
extensions = { extensions =
@Extension(name = "translation", properties = { { @Extension(
@ExtensionProperty(name="path", value="GET height:signature"), name = "translation",
@ExtensionProperty(name="description.key", value="operation:description") properties =
}), { @ExtensionProperty(name = "path", value = "GET height:signature"), @ExtensionProperty(name = "description.key", value = "operation:description") }
@Extension(properties = { ), @Extension(properties =
@ExtensionProperty(name="apiErrors", value="[\"INVALID_SIGNATURE\", \"BLOCK_NO_EXISTS\"]", parseValue = true), { @ExtensionProperty(name = "apiErrors", value = "[\"INVALID_SIGNATURE\", \"BLOCK_NO_EXISTS\"]", parseValue = true), }) },
}) responses =
}, { @ApiResponse(
responses = { description = "the height",
@ApiResponse( content = @Content(schema = @Schema(implementation = int.class)),
description = "the height", extensions =
content = @Content(schema = @Schema(implementation = int.class)), { @Extension(name = "translation", properties = { @ExtensionProperty(name = "description.key", value = "success_response:description") }) }
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 signature) {
// Decode signature // Decode signature
@ -446,31 +403,26 @@ public class BlocksResource {
@Operation( @Operation(
summary = "Fetch block using block height", summary = "Fetch block using block height",
description = "Returns the block with given height", description = "Returns the block with given height",
extensions = { extensions =
@Extension(name = "translation", properties = { { @Extension(
@ExtensionProperty(name="path", value="GET byheight:height"), name = "translation",
@ExtensionProperty(name="description.key", value="operation:description") properties =
}), { @ExtensionProperty(name = "path", value = "GET byheight:height"), @ExtensionProperty(name = "description.key", value = "operation:description") }
@Extension(properties = { ), @Extension(properties =
@ExtensionProperty(name="apiErrors", value="[\"BLOCK_NO_EXISTS\"]", parseValue = true), { @ExtensionProperty(name = "apiErrors", value = "[\"BLOCK_NO_EXISTS\"]", parseValue = true), }) },
}) responses =
}, { @ApiResponse(
responses = { description = "the block",
@ApiResponse( content = @Content(schema = @Schema(implementation = BlockWithTransactions.class)),
description = "the block", extensions =
content = @Content(schema = @Schema(implementation = BlockWithTransactions.class)), { @Extension(name = "translation", properties = { @ExtensionProperty(name = "description.key", value = "success_response:description") }) }
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()) { try (final Repository repository = RepositoryManager.getRepository()) {
BlockData blockData = repository.getBlockRepository().fromHeight(height); BlockData blockData = repository.getBlockRepository().fromHeight(height);
return new BlockWithTransactions(repository, blockData, includeTransactions); return packageBlockData(repository, blockData, includeTransactions);
} catch (ApiException e) { } catch (ApiException e) {
throw e; throw e;
} catch (DataException e) { } catch (DataException e) {
@ -483,26 +435,20 @@ public class BlocksResource {
@Operation( @Operation(
summary = "Fetch blocks starting with given height", summary = "Fetch blocks starting with given height",
description = "Returns blocks starting with given height.", description = "Returns blocks starting with given height.",
extensions = { extensions =
@Extension(name = "translation", properties = { { @Extension(
@ExtensionProperty(name="path", value="GET byheight:height"), name = "translation",
@ExtensionProperty(name="description.key", value="operation:description") properties =
}), { @ExtensionProperty(name = "path", value = "GET byheight:height"), @ExtensionProperty(name = "description.key", value = "operation:description") }
@Extension(properties = { ), @Extension(properties =
@ExtensionProperty(name="apiErrors", value="[\"BLOCK_NO_EXISTS\"]", parseValue = true), { @ExtensionProperty(name = "apiErrors", value = "[\"BLOCK_NO_EXISTS\"]", parseValue = true), }) },
}) responses =
}, { @ApiResponse(
responses = { description = "blocks",
@ApiResponse( content = @Content(schema = @Schema(implementation = BlockWithTransactions.class)),
description = "blocks", extensions =
content = @Content(schema = @Schema(implementation = BlockWithTransactions.class)), { @Extension(name = "translation", properties = { @ExtensionProperty(name = "description.key", value = "success_response:description") }) }
extensions = { ) }
@Extension(name = "translation", properties = {
@ExtensionProperty(name="description.key", value="success_response:description")
})
}
)
}
) )
public List<BlockWithTransactions> getBlockRange(@PathParam("height") int height, @Parameter(ref = "count") @QueryParam("count") int count) { public List<BlockWithTransactions> getBlockRange(@PathParam("height") int height, @Parameter(ref = "count") @QueryParam("count") int count) {
boolean includeTransactions = false; boolean includeTransactions = false;
@ -516,7 +462,7 @@ public class BlocksResource {
// Run out of blocks! // Run out of blocks!
break; break;
blocks.add(new BlockWithTransactions(repository, blockData, includeTransactions)); blocks.add(packageBlockData(repository, blockData, includeTransactions));
} }
return blocks; 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<TransactionData> 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);
}
} }

View File

@ -209,7 +209,7 @@ public class TransactionsResource {
} }
) )
public List<TransactionData> searchTransactions(@QueryParam("startBlock") Integer startBlock, @QueryParam("blockLimit") Integer blockLimit, public List<TransactionData> 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())) if ((txTypeNum == null || txTypeNum == 0) && (address == null || address.isEmpty()))
throw this.apiErrorFactory.createError(ApiError.INVALID_CRITERIA); throw this.apiErrorFactory.createError(ApiError.INVALID_CRITERIA);

View File

@ -2,17 +2,17 @@ package api.models;
import java.util.List; import java.util.List;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlElement;
import api.ApiError;
import api.ApiErrorFactory;
import data.account.AccountBalanceData; import data.account.AccountBalanceData;
import data.assets.AssetData; import data.assets.AssetData;
import io.swagger.v3.oas.annotations.media.Schema; 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 { public class AssetWithHolders {
@Schema(implementation = AssetData.class, name = "asset", title = "asset data") @Schema(implementation = AssetData.class, name = "asset", title = "asset data")
@ -22,18 +22,12 @@ public class AssetWithHolders {
public List<AccountBalanceData> holders; public List<AccountBalanceData> holders;
// For JAX-RS // For JAX-RS
@SuppressWarnings("unused") protected AssetWithHolders() {
private AssetWithHolders() {
} }
public AssetWithHolders(Repository repository, AssetData assetData, boolean includeHolders) throws DataException { public AssetWithHolders(AssetData assetData, List<AccountBalanceData> holders) {
if (assetData == null)
throw ApiErrorFactory.getInstance().createError(ApiError.INVALID_ASSET_ID);
this.assetData = assetData; this.assetData = assetData;
this.holders = holders;
if (includeHolders)
this.holders = repository.getAccountRepository().getAssetBalances(assetData.getAssetId());
} }
} }

View File

@ -1,22 +1,17 @@
package api.models; package api.models;
import java.util.List; import java.util.List;
import java.util.stream.Collectors;
import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlElement;
import api.ApiError;
import api.ApiErrorFactory;
import data.block.BlockData; import data.block.BlockData;
import data.transaction.TransactionData; import data.transaction.TransactionData;
import io.swagger.v3.oas.annotations.media.Schema; 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) @XmlAccessorType(XmlAccessType.FIELD)
public class BlockWithTransactions { public class BlockWithTransactions {
@ -30,16 +25,9 @@ public class BlockWithTransactions {
protected BlockWithTransactions() { protected BlockWithTransactions() {
} }
public BlockWithTransactions(Repository repository, BlockData blockData, boolean includeTransactions) throws DataException { public BlockWithTransactions(BlockData blockData, List<TransactionData> transactions) {
if (blockData == null)
throw ApiErrorFactory.getInstance().createError(ApiError.BLOCK_NO_EXISTS);
this.blockData = blockData; this.blockData = blockData;
this.transactions = transactions;
if (includeTransactions) {
Block block = new Block(repository, blockData);
this.transactions = block.getTransactions().stream().map(transaction -> transaction.getTransactionData()).collect(Collectors.toList());
}
} }
} }

View File

@ -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<TradeData> trades;
// For JAX-RS
protected OrderWithTrades() {
}
public OrderWithTrades(OrderData orderData, List<TradeData> trades) {
this.orderData = orderData;
this.trades = trades;
}
}

View File

@ -1,14 +1,16 @@
package api.models; package api.models;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlElement;
import data.assets.OrderData; import data.assets.OrderData;
import data.assets.TradeData; import data.assets.TradeData;
import io.swagger.v3.oas.annotations.media.Schema; 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 { public class TradeWithOrderInfo {
@Schema(implementation = TradeData.class, name = "trade", title = "trade data") @Schema(implementation = TradeData.class, name = "trade", title = "trade data")
@ -27,11 +29,10 @@ public class TradeWithOrderInfo {
protected TradeWithOrderInfo() { protected TradeWithOrderInfo() {
} }
public TradeWithOrderInfo(Repository repository, TradeData tradeData) throws DataException { public TradeWithOrderInfo(TradeData tradeData, OrderData initiatingOrderData, OrderData targetOrderData) {
this.tradeData = tradeData; this.tradeData = tradeData;
this.initiatingOrderData = initiatingOrderData;
this.initiatingOrderData = repository.getAssetRepository().fromOrderId(tradeData.getInitiator()); this.targetOrderData = targetOrderData;
this.targetOrderData = repository.getAssetRepository().fromOrderId(tradeData.getTarget());
} }
} }

View File

@ -6,6 +6,7 @@ import org.eclipse.persistence.sessions.Session;
public class TransactionClassExtractor extends ClassExtractor { public class TransactionClassExtractor extends ClassExtractor {
@SuppressWarnings("rawtypes")
@Override @Override
public Class extractClassFromRow(Record record, Session session) { public Class extractClassFromRow(Record record, Session session) {
// Never called anyway? // Never called anyway?

View File

@ -5,7 +5,7 @@ import java.math.BigDecimal;
import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlAccessorType;
//All properties to be converted to JSON via JAX-RS // All properties to be converted to JSON via JAX-RS
@XmlAccessorType(XmlAccessType.FIELD) @XmlAccessorType(XmlAccessType.FIELD)
public class PaymentData { public class PaymentData {

View File

@ -3,6 +3,7 @@ package qora.assets;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.math.BigInteger; import java.math.BigInteger;
import java.math.RoundingMode; import java.math.RoundingMode;
import java.util.Arrays;
import java.util.List; import java.util.List;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
@ -209,10 +210,11 @@ public class Order {
public void orphan() throws DataException { public void orphan() throws DataException {
// Orphan trades that occurred as a result of this order // Orphan trades that occurred as a result of this order
for (TradeData tradeData : getTrades()) { for (TradeData tradeData : getTrades())
Trade trade = new Trade(this.repository, tradeData); if (Arrays.equals(this.orderData.getOrderId(), tradeData.getInitiator())) {
trade.orphan(); Trade trade = new Trade(this.repository, tradeData);
} trade.orphan();
}
// Delete this order from repository // Delete this order from repository
this.repository.getAssetRepository().delete(this.orderData.getOrderId()); this.repository.getAssetRepository().delete(this.orderData.getOrderId());

View File

@ -40,6 +40,7 @@ public interface AssetRepository {
public List<TradeData> getTrades(long haveAssetId, long wantAssetId) throws DataException; public List<TradeData> getTrades(long haveAssetId, long wantAssetId) throws DataException;
/** Returns TradeData for trades where orderId was involved, i.e. either initiating OR target order */
public List<TradeData> getOrdersTrades(byte[] orderId) throws DataException; public List<TradeData> getOrdersTrades(byte[] orderId) throws DataException;
public void save(TradeData tradeData) throws DataException; public void save(TradeData tradeData) throws DataException;

View File

@ -257,19 +257,21 @@ public class HSQLDBAssetRepository implements AssetRepository {
} }
@Override @Override
public List<TradeData> getOrdersTrades(byte[] initiatingOrderId) throws DataException { public List<TradeData> getOrdersTrades(byte[] orderId) throws DataException {
List<TradeData> trades = new ArrayList<TradeData>(); List<TradeData> trades = new ArrayList<TradeData>();
try (ResultSet resultSet = this.repository try (ResultSet resultSet = this.repository.checkedExecute(
.checkedExecute("SELECT target_order_id, amount, price, traded FROM AssetTrades WHERE initiating_order_id = ?", initiatingOrderId)) { "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) if (resultSet == null)
return trades; return trades;
do { do {
byte[] targetOrderId = resultSet.getBytes(1); byte[] initiatingOrderId = resultSet.getBytes(1);
BigDecimal amount = resultSet.getBigDecimal(2); byte[] targetOrderId = resultSet.getBytes(2);
BigDecimal price = resultSet.getBigDecimal(3); BigDecimal amount = resultSet.getBigDecimal(3);
long timestamp = resultSet.getTimestamp(4, Calendar.getInstance(HSQLDBRepository.UTC)).getTime(); BigDecimal price = resultSet.getBigDecimal(4);
long timestamp = resultSet.getTimestamp(5, Calendar.getInstance(HSQLDBRepository.UTC)).getTime();
TradeData trade = new TradeData(initiatingOrderId, targetOrderId, amount, price, timestamp); TradeData trade = new TradeData(initiatingOrderId, targetOrderId, amount, price, timestamp);
trades.add(trade); trades.add(trade);

View File

@ -310,11 +310,7 @@ public class BlockTransformer extends Transformer {
Order order = orderTransaction.getOrder(); Order order = orderTransaction.getOrder();
List<TradeData> trades = order.getTrades(); List<TradeData> trades = order.getTrades();
// Filter out trades with initiatingOrderId that don't match this order if (trades.stream().anyMatch(tradeData -> Arrays.equals(tradeData.getInitiator(), order.getOrderData().getOrderId()))) {
trades.removeIf((TradeData tradeData) -> !Arrays.equals(tradeData.getInitiator(), order.getOrderData().getOrderId()));
// Any trades left?
if (!trades.isEmpty()) {
tradesHappened = true; tradesHappened = true;
// No need to check any further // No need to check any further
break; break;