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;
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();

View File

@ -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<AccountBalanceData> 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);
}
}

View File

@ -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),

View File

@ -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() {

View File

@ -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<AccountBalanceData> 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<TradeWithOrderInfo> 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<TradeData> 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(

View File

@ -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<BlockWithTransactions> 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<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,
@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);

View File

@ -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<AccountBalanceData> 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<AccountBalanceData> holders) {
this.assetData = assetData;
if (includeHolders)
this.holders = repository.getAccountRepository().getAssetBalances(assetData.getAssetId());
this.holders = holders;
}
}

View File

@ -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<TransactionData> 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;
}
}

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;
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;
}
}

View File

@ -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?

View File

@ -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 {

View File

@ -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());

View File

@ -40,6 +40,7 @@ public interface AssetRepository {
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 void save(TradeData tradeData) throws DataException;

View File

@ -257,19 +257,21 @@ public class HSQLDBAssetRepository implements AssetRepository {
}
@Override
public List<TradeData> getOrdersTrades(byte[] initiatingOrderId) throws DataException {
public List<TradeData> getOrdersTrades(byte[] orderId) throws DataException {
List<TradeData> trades = new ArrayList<TradeData>();
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);

View File

@ -310,11 +310,7 @@ public class BlockTransformer extends Transformer {
Order order = orderTransaction.getOrder();
List<TradeData> 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;