forked from Qortal/qortal
API, HSQLDB
Added more global parameters to /admin/unused API endpoint (formally /admin/dud) and also managed to remove /admin/unused from API documentation UI. Added results slicing to /assets/all Added /assets/orderbook API call that returns open asset orders Added /assets/trades that returns successful asset trades Added POST /assets/issue stub Unified HSQLDB connectionUrl to public variable inside Controller class. Can't deploy v1 ATs with isFinished=true flag as that prevents later transactions sending messages (during import of v1 chain). Some future hard-fork code will need to set all v1 ATs to "finished". Changed DB's "TransactionRecipients" to "TransactionParticipants" to properly support API call to find all transactions 'involving' a specific address. Support code needed in Block and Transaction with some transaction-specific overrides for Genesis and AT transactions. Removed old, deprecated calls from Transaction/TransactionRepository Moved HSQLDB database properties from connection URL to explicit SQL statements in HSQLDBDatabaseUpdates. They didn't work in connection URL during DB creation anyway. Retrofitted HSQLDB Accounts table with public_key column instead of rebuilding it later. Fixed incorrect comments in IssueAssetTransactionTransformer regarding v1 serialization for signing. Re-imported v1 chain to test latest changes.
This commit is contained in:
parent
2aaa199c86
commit
cfd8b53fc1
@ -26,7 +26,6 @@ import javax.ws.rs.core.MediaType;
|
||||
import data.account.AccountBalanceData;
|
||||
import data.account.AccountData;
|
||||
import qora.account.Account;
|
||||
import qora.account.PublicKeyAccount;
|
||||
import qora.assets.Asset;
|
||||
import qora.crypto.Crypto;
|
||||
import repository.DataException;
|
||||
@ -82,9 +81,7 @@ public class AddressesResource {
|
||||
)
|
||||
}
|
||||
)
|
||||
public String getLastReference(
|
||||
@Parameter(description = "a base64-encoded address", required = true) @PathParam("address") String address
|
||||
) {
|
||||
public String getLastReference(@Parameter(ref = "address") @PathParam("address") String address) {
|
||||
if (!Crypto.isValidAddress(address))
|
||||
throw this.apiErrorFactory.createError(ApiError.INVALID_ADDRESS);
|
||||
|
||||
|
@ -32,13 +32,17 @@ public class AdminResource {
|
||||
HttpServletRequest request;
|
||||
|
||||
@GET
|
||||
@Path("/dud")
|
||||
@Parameter(name = "blockSignature", description = "Block signature", schema = @Schema(type = "string", format = "byte", minLength = 84, maxLength=88))
|
||||
@Parameter(in = ParameterIn.QUERY, name = "count", description = "Maximum number of entries to return", schema = @Schema(type = "integer", defaultValue = "10"))
|
||||
@Parameter(in = ParameterIn.QUERY, name = "limit", description = "Maximum number of entries to return, 0 means unlimited", schema = @Schema(type = "integer", defaultValue = "10"))
|
||||
@Parameter(in = ParameterIn.QUERY, name = "offset", description = "Starting entry in results", schema = @Schema(type = "integer"))
|
||||
@Path("/unused")
|
||||
@Parameter(in = ParameterIn.PATH, name = "blockSignature", description = "Block signature", schema = @Schema(type = "string", format = "byte"), example = "ZZZZ==")
|
||||
@Parameter(in = ParameterIn.PATH, name = "assetId", description = "Asset ID, 0 is native coin", schema = @Schema(type = "string", format = "byte"))
|
||||
@Parameter(in = ParameterIn.PATH, name = "otherAssetId", description = "Asset ID, 0 is native coin", schema = @Schema(type = "string", format = "byte"))
|
||||
@Parameter(in = ParameterIn.PATH, name = "address", description = "an account address", example = "QRHDHASWAXarqTvB2X4SNtJCWbxGf68M2o")
|
||||
@Parameter(in = ParameterIn.QUERY, name = "count", description = "Maximum number of entries to return, 0 means none", schema = @Schema(type = "integer", defaultValue = "20"))
|
||||
@Parameter(in = ParameterIn.QUERY, name = "limit", description = "Maximum number of entries to return, 0 means unlimited", schema = @Schema(type = "integer", defaultValue = "20"))
|
||||
@Parameter(in = ParameterIn.QUERY, name = "offset", description = "Starting entry in results, 0 is first entry", schema = @Schema(type = "integer"))
|
||||
@Parameter(in = ParameterIn.QUERY, name = "includeTransactions", description = "Include associated transactions in results", schema = @Schema(type = "boolean"))
|
||||
@Parameter(in = ParameterIn.QUERY, name = "includeHolders", description = "Include asset holders in results", schema = @Schema(type = "boolean"))
|
||||
@Parameter(in = ParameterIn.QUERY, name = "queryAssetId", description = "Asset ID, 0 is native coin", schema = @Schema(type = "string", format = "byte"))
|
||||
public String globalParameters() {
|
||||
return "";
|
||||
}
|
||||
|
@ -55,12 +55,14 @@ public class AnnotationPostProcessor implements ReaderListener {
|
||||
@Override
|
||||
public void afterScan(Reader reader, OpenAPI openAPI) {
|
||||
// Populate Components section with reusable parameters, like "limit" and "offset"
|
||||
// We take the reusable parameters from AdminResource.globalParameters path "/admin/dud"
|
||||
// We take the reusable parameters from AdminResource.globalParameters path "/admin/unused"
|
||||
Components components = openAPI.getComponents();
|
||||
PathItem globalParametersPathItem = openAPI.getPaths().get("/admin/dud");
|
||||
if (globalParametersPathItem != null)
|
||||
PathItem globalParametersPathItem = openAPI.getPaths().get("/admin/unused");
|
||||
if (globalParametersPathItem != null) {
|
||||
for (Parameter parameter : globalParametersPathItem.getGet().getParameters())
|
||||
components.addParameters(parameter.getName(), parameter);
|
||||
openAPI.getPaths().remove("/admin/unused");
|
||||
}
|
||||
|
||||
// use context path and keys from "x-translation" extension annotations
|
||||
// to translate supported annotations and finally remove "x-translation" extensions
|
||||
|
@ -5,24 +5,32 @@ import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.media.ArraySchema;
|
||||
import io.swagger.v3.oas.annotations.media.Content;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import io.swagger.v3.oas.annotations.parameters.RequestBody;
|
||||
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import repository.DataException;
|
||||
import repository.Repository;
|
||||
import repository.RepositoryManager;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.ws.rs.GET;
|
||||
import javax.ws.rs.POST;
|
||||
import javax.ws.rs.Path;
|
||||
import javax.ws.rs.PathParam;
|
||||
import javax.ws.rs.Produces;
|
||||
import javax.ws.rs.QueryParam;
|
||||
import javax.ws.rs.core.Context;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
|
||||
import api.models.AssetWithHolders;
|
||||
import api.models.IssueAssetRequest;
|
||||
import api.models.TradeWithOrderInfo;
|
||||
import data.assets.AssetData;
|
||||
import data.assets.OrderData;
|
||||
import data.assets.TradeData;
|
||||
|
||||
@Path("/assets")
|
||||
@Produces({MediaType.APPLICATION_JSON, MediaType.TEXT_PLAIN})
|
||||
@ -43,9 +51,16 @@ public class AssetsResource {
|
||||
)
|
||||
}
|
||||
)
|
||||
public List<AssetData> getAllAssets() {
|
||||
public List<AssetData> getAllAssets(@Parameter(ref = "limit") @QueryParam("limit") int limit, @Parameter(ref = "offset") @QueryParam("offset") int offset) {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
return repository.getAssetRepository().getAllAssets();
|
||||
List<AssetData> assets = repository.getAssetRepository().getAllAssets();
|
||||
|
||||
// Pagination would take effect here (or as part of the repository access)
|
||||
int fromIndex = Integer.min(offset, assets.size());
|
||||
int toIndex = limit == 0 ? assets.size() : Integer.min(fromIndex + limit, assets.size());
|
||||
assets = assets.subList(fromIndex, toIndex);
|
||||
|
||||
return assets;
|
||||
} catch (DataException e) {
|
||||
throw ApiErrorFactory.getInstance().createError(ApiError.REPOSITORY_ISSUE, e);
|
||||
}
|
||||
@ -55,24 +70,25 @@ public class AssetsResource {
|
||||
@Path("/info")
|
||||
@Operation(
|
||||
summary = "Info on specific asset",
|
||||
description = "Supply either assetId OR assetName. (If both supplied, assetId takes priority).",
|
||||
responses = {
|
||||
@ApiResponse(
|
||||
description = "asset info",
|
||||
content = @Content(array = @ArraySchema(schema = @Schema(implementation = AssetData.class)))
|
||||
content = @Content(array = @ArraySchema(schema = @Schema(implementation = AssetWithHolders.class)))
|
||||
)
|
||||
}
|
||||
)
|
||||
public AssetWithHolders getAssetInfo(@QueryParam("key") Integer key, @QueryParam("name") String name, @Parameter(ref = "includeHolders") @QueryParam("withHolders") boolean includeHolders) {
|
||||
if (key == null && (name == null || name.isEmpty()))
|
||||
public AssetWithHolders getAssetInfo(@QueryParam("assetId") Integer assetId, @QueryParam("assetName") String assetName, @Parameter(ref = "includeHolders") @QueryParam("withHolders") boolean includeHolders) {
|
||||
if (assetId == null && (assetName == null || assetName.isEmpty()))
|
||||
throw ApiErrorFactory.getInstance().createError(ApiError.INVALID_CRITERIA);
|
||||
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
AssetData assetData = null;
|
||||
|
||||
if (key != null)
|
||||
assetData = repository.getAssetRepository().fromAssetId(key);
|
||||
if (assetId != null)
|
||||
assetData = repository.getAssetRepository().fromAssetId(assetId);
|
||||
else
|
||||
assetData = repository.getAssetRepository().fromAssetName(name);
|
||||
assetData = repository.getAssetRepository().fromAssetName(assetName);
|
||||
|
||||
if (assetData == null)
|
||||
throw ApiErrorFactory.getInstance().createError(ApiError.INVALID_ASSET_ID);
|
||||
@ -83,4 +99,98 @@ public class AssetsResource {
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/orderbook/{assetId}/{otherAssetId}")
|
||||
@Operation(
|
||||
summary = "Asset order book",
|
||||
description = "Returns open orders, offering {assetId} for {otherAssetId} in return.",
|
||||
responses = {
|
||||
@ApiResponse(
|
||||
description = "asset orders",
|
||||
content = @Content(array = @ArraySchema(schema = @Schema(implementation = OrderData.class)))
|
||||
)
|
||||
}
|
||||
)
|
||||
public List<OrderData> getAssetOrders(@Parameter(ref = "assetId") @PathParam("assetId") int assetId, @Parameter(ref = "otherAssetId") @PathParam("otherAssetId") int otherAssetId,
|
||||
@Parameter(ref = "limit") @QueryParam("limit") int limit, @Parameter(ref = "offset") @QueryParam("offset") int offset) {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
if (!repository.getAssetRepository().assetExists(assetId))
|
||||
throw ApiErrorFactory.getInstance().createError(ApiError.INVALID_ASSET_ID);
|
||||
|
||||
if (!repository.getAssetRepository().assetExists(otherAssetId))
|
||||
throw ApiErrorFactory.getInstance().createError(ApiError.INVALID_ASSET_ID);
|
||||
|
||||
List<OrderData> orders = repository.getAssetRepository().getOpenOrders(assetId, otherAssetId);
|
||||
|
||||
// Pagination would take effect here (or as part of the repository access)
|
||||
int fromIndex = Integer.min(offset, orders.size());
|
||||
int toIndex = limit == 0 ? orders.size() : Integer.min(fromIndex + limit, orders.size());
|
||||
orders = orders.subList(fromIndex, toIndex);
|
||||
|
||||
return orders;
|
||||
} catch (DataException e) {
|
||||
throw ApiErrorFactory.getInstance().createError(ApiError.REPOSITORY_ISSUE, e);
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/trades/{assetId}/{otherAssetId}")
|
||||
@Operation(
|
||||
summary = "Asset trades",
|
||||
description = "Returns successful trades of {assetId} for {otherAssetId}.<br>" +
|
||||
"Does NOT include trades of {otherAssetId} for {assetId}!<br>" +
|
||||
"\"Initiating\" order is the order that caused the actual trade by matching up with the \"target\" order.",
|
||||
responses = {
|
||||
@ApiResponse(
|
||||
description = "asset trades",
|
||||
content = @Content(array = @ArraySchema(schema = @Schema(implementation = TradeWithOrderInfo.class)))
|
||||
)
|
||||
}
|
||||
)
|
||||
public List<TradeWithOrderInfo> getAssetTrades(@Parameter(ref = "assetId") @PathParam("assetId") int assetId, @Parameter(ref = "otherAssetId") @PathParam("otherAssetId") int otherAssetId,
|
||||
@Parameter(ref = "limit") @QueryParam("limit") int limit, @Parameter(ref = "offset") @QueryParam("offset") int offset) {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
if (!repository.getAssetRepository().assetExists(assetId))
|
||||
throw ApiErrorFactory.getInstance().createError(ApiError.INVALID_ASSET_ID);
|
||||
|
||||
if (!repository.getAssetRepository().assetExists(otherAssetId))
|
||||
throw ApiErrorFactory.getInstance().createError(ApiError.INVALID_ASSET_ID);
|
||||
|
||||
List<TradeData> trades = repository.getAssetRepository().getTrades(assetId, otherAssetId);
|
||||
|
||||
// Pagination would take effect here (or as part of the repository access)
|
||||
int fromIndex = Integer.min(offset, trades.size());
|
||||
int toIndex = limit == 0 ? trades.size() : Integer.min(fromIndex + limit, trades.size());
|
||||
trades = trades.subList(fromIndex, toIndex);
|
||||
|
||||
// Expanding remaining entries
|
||||
List<TradeWithOrderInfo> fullTrades = new ArrayList<>();
|
||||
for (TradeData trade : trades)
|
||||
fullTrades.add(new TradeWithOrderInfo(repository, trade));
|
||||
|
||||
return fullTrades;
|
||||
} catch (DataException e) {
|
||||
throw ApiErrorFactory.getInstance().createError(ApiError.REPOSITORY_ISSUE, e);
|
||||
}
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("/issue")
|
||||
@Operation(
|
||||
summary = "Issue new asset",
|
||||
requestBody = @RequestBody(
|
||||
required = true,
|
||||
content = @Content(
|
||||
mediaType = "application/json",
|
||||
schema = @Schema(implementation = IssueAssetRequest.class)
|
||||
)
|
||||
)
|
||||
)
|
||||
public String issueAsset(IssueAssetRequest issueAssetRequest) {
|
||||
// required: issuer (pubkey), name, description, quantity, isDivisible, fee
|
||||
// optional: reference
|
||||
// returns: raw tx
|
||||
return "";
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -3,7 +3,6 @@ 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.enums.ParameterIn;
|
||||
import io.swagger.v3.oas.annotations.extensions.Extension;
|
||||
import io.swagger.v3.oas.annotations.extensions.ExtensionProperty;
|
||||
import io.swagger.v3.oas.annotations.media.ArraySchema;
|
||||
@ -11,7 +10,6 @@ import io.swagger.v3.oas.annotations.media.Content;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import qora.crypto.Crypto;
|
||||
import qora.transaction.Transaction.TransactionType;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
@ -1,7 +1,6 @@
|
||||
package api.models;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.xml.bind.annotation.XmlElement;
|
||||
|
||||
@ -9,10 +8,7 @@ import api.ApiError;
|
||||
import api.ApiErrorFactory;
|
||||
import data.account.AccountBalanceData;
|
||||
import data.assets.AssetData;
|
||||
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;
|
||||
|
||||
|
26
src/api/models/IssueAssetRequest.java
Normal file
26
src/api/models/IssueAssetRequest.java
Normal file
@ -0,0 +1,26 @@
|
||||
package api.models;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
|
||||
public class IssueAssetRequest {
|
||||
|
||||
@Schema(description = "asset issuer's public key")
|
||||
public byte[] issuer;
|
||||
|
||||
@Schema(description = "asset name - must be lowercase", example = "my-asset123")
|
||||
public String name;
|
||||
|
||||
@Schema(description = "asset description")
|
||||
public String description;
|
||||
|
||||
public BigDecimal quantity;
|
||||
|
||||
public boolean isDivisible;
|
||||
|
||||
public BigDecimal fee;
|
||||
|
||||
public byte[] reference;
|
||||
|
||||
}
|
37
src/api/models/TradeWithOrderInfo.java
Normal file
37
src/api/models/TradeWithOrderInfo.java
Normal file
@ -0,0 +1,37 @@
|
||||
package api.models;
|
||||
|
||||
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")
|
||||
public class TradeWithOrderInfo {
|
||||
|
||||
@Schema(implementation = TradeData.class, name = "trade", title = "trade data")
|
||||
@XmlElement(name = "trade")
|
||||
public TradeData tradeData;
|
||||
|
||||
@Schema(implementation = OrderData.class, name = "order", title = "order data")
|
||||
@XmlElement(name = "initiatingOrder")
|
||||
public OrderData initiatingOrderData;
|
||||
|
||||
@Schema(implementation = OrderData.class, name = "order", title = "order data")
|
||||
@XmlElement(name = "targetOrder")
|
||||
public OrderData targetOrderData;
|
||||
|
||||
// For JAX-RS
|
||||
protected TradeWithOrderInfo() {
|
||||
}
|
||||
|
||||
public TradeWithOrderInfo(Repository repository, TradeData tradeData) throws DataException {
|
||||
this.tradeData = tradeData;
|
||||
|
||||
this.initiatingOrderData = repository.getAssetRepository().fromOrderId(tradeData.getInitiator());
|
||||
this.targetOrderData = repository.getAssetRepository().fromOrderId(tradeData.getTarget());
|
||||
}
|
||||
|
||||
}
|
@ -14,7 +14,7 @@ import utils.Base58;
|
||||
public class blockgenerator {
|
||||
|
||||
private static final Logger LOGGER = LogManager.getLogger(blockgenerator.class);
|
||||
public static final String connectionUrl = "jdbc:hsqldb:file:db/test;create=true";
|
||||
public static final String connectionUrl = "jdbc:hsqldb:file:db/blockchain;create=true";
|
||||
|
||||
public static void main(String[] args) {
|
||||
if (args.length != 1) {
|
||||
|
@ -13,7 +13,7 @@ public class Controller {
|
||||
|
||||
private static final Logger LOGGER = LogManager.getLogger(Controller.class);
|
||||
|
||||
private static final String connectionUrl = "jdbc:hsqldb:file:db/test;create=true";
|
||||
public static final String connectionUrl = "jdbc:hsqldb:file:db/blockchain;create=true";
|
||||
|
||||
public static final long startTime = System.currentTimeMillis();
|
||||
private static final Object shutdownLock = new Object();
|
||||
|
@ -3,7 +3,7 @@ package data.assets;
|
||||
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 AssetData {
|
||||
|
||||
@ -16,6 +16,8 @@ public class AssetData {
|
||||
private boolean isDivisible;
|
||||
private byte[] reference;
|
||||
|
||||
// Constructors
|
||||
|
||||
// necessary for JAX-RS serialization
|
||||
protected AssetData() {
|
||||
}
|
||||
|
@ -2,19 +2,48 @@ package data.assets;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
import javax.xml.bind.annotation.XmlAccessType;
|
||||
import javax.xml.bind.annotation.XmlAccessorType;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
|
||||
// All properties to be converted to JSON via JAX-RS
|
||||
@XmlAccessorType(XmlAccessType.FIELD)
|
||||
public class OrderData implements Comparable<OrderData> {
|
||||
|
||||
// Properties
|
||||
private byte[] orderId;
|
||||
private byte[] creatorPublicKey;
|
||||
|
||||
@Schema(description = "asset on offer to give by order creator")
|
||||
private long haveAssetId;
|
||||
|
||||
@Schema(description = "asset wanted to receive by order creator")
|
||||
private long wantAssetId;
|
||||
|
||||
@Schema(description = "amount of \"have\" asset to trade")
|
||||
private BigDecimal amount;
|
||||
private BigDecimal fulfilled;
|
||||
|
||||
@Schema(description = "amount of \"want\" asset to receive per unit of \"have\" asset traded")
|
||||
private BigDecimal price;
|
||||
|
||||
@Schema(description = "how much \"have\" asset has traded")
|
||||
private BigDecimal fulfilled;
|
||||
|
||||
private long timestamp;
|
||||
|
||||
@Schema(description = "has this order been cancelled for further trades?")
|
||||
private boolean isClosed;
|
||||
|
||||
@Schema(description = "has this order been fully traded?")
|
||||
private boolean isFulfilled;
|
||||
|
||||
// Constructors
|
||||
|
||||
// necessary for JAX-RS serialization
|
||||
protected OrderData() {
|
||||
}
|
||||
|
||||
public OrderData(byte[] orderId, byte[] creatorPublicKey, long haveAssetId, long wantAssetId, BigDecimal amount, BigDecimal fulfilled, BigDecimal price,
|
||||
long timestamp, boolean isClosed, boolean isFulfilled) {
|
||||
this.orderId = orderId;
|
||||
@ -33,6 +62,8 @@ public class OrderData implements Comparable<OrderData> {
|
||||
this(orderId, creatorPublicKey, haveAssetId, wantAssetId, amount, BigDecimal.ZERO.setScale(8), price, timestamp, false, false);
|
||||
}
|
||||
|
||||
// Getters/setters
|
||||
|
||||
public byte[] getOrderId() {
|
||||
return this.orderId;
|
||||
}
|
||||
|
@ -2,17 +2,42 @@ package data.assets;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
import javax.xml.bind.annotation.XmlAccessType;
|
||||
import javax.xml.bind.annotation.XmlAccessorType;
|
||||
import javax.xml.bind.annotation.XmlElement;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
|
||||
// All properties to be converted to JSON via JAX-RS
|
||||
@XmlAccessorType(XmlAccessType.FIELD)
|
||||
public class TradeData {
|
||||
|
||||
// Properties
|
||||
@Schema(name = "initiatingOrderId", description = "ID of order that caused trade")
|
||||
@XmlElement(name = "initiatingOrderId")
|
||||
private byte[] initiator;
|
||||
|
||||
@Schema(name = "targetOrderId", description = "ID of order that matched")
|
||||
@XmlElement(name = "targetOrderId")
|
||||
private byte[] target;
|
||||
|
||||
@Schema(name = "targetAmount", description = "amount traded from target order")
|
||||
@XmlElement(name = "targetAmount")
|
||||
private BigDecimal amount;
|
||||
|
||||
@Schema(name = "initiatorAmount", description = "amount traded from initiating order")
|
||||
@XmlElement(name = "initiatorAmount")
|
||||
private BigDecimal price;
|
||||
|
||||
@Schema(description = "when trade happened")
|
||||
private long timestamp;
|
||||
|
||||
// Constructors
|
||||
|
||||
// necessary for JAX-RS serialization
|
||||
protected TradeData() {
|
||||
}
|
||||
|
||||
public TradeData(byte[] initiator, byte[] target, BigDecimal amount, BigDecimal price, long timestamp) {
|
||||
this.initiator = initiator;
|
||||
this.target = target;
|
||||
|
@ -1,3 +1,4 @@
|
||||
import controller.Controller;
|
||||
import data.block.BlockData;
|
||||
import qora.block.Block;
|
||||
import qora.block.BlockChain;
|
||||
@ -9,8 +10,6 @@ import repository.hsqldb.HSQLDBRepositoryFactory;
|
||||
|
||||
public class orphan {
|
||||
|
||||
public static final String connectionUrl = "jdbc:hsqldb:file:db/test;create=true";
|
||||
|
||||
public static void main(String[] args) {
|
||||
if (args.length == 0) {
|
||||
System.err.println("usage: orphan <new-blockchain-tip-height>");
|
||||
@ -20,7 +19,7 @@ public class orphan {
|
||||
int targetHeight = Integer.parseInt(args[0]);
|
||||
|
||||
try {
|
||||
RepositoryFactory repositoryFactory = new HSQLDBRepositoryFactory(connectionUrl);
|
||||
RepositoryFactory repositoryFactory = new HSQLDBRepositoryFactory(Controller.connectionUrl);
|
||||
RepositoryManager.setRepositoryFactory(repositoryFactory);
|
||||
} catch (DataException e) {
|
||||
System.err.println("Couldn't connect to repository: " + e.getMessage());
|
||||
|
@ -61,7 +61,8 @@ public class AT {
|
||||
this.atStateData = new ATStateData(atAddress, height, creation, stateData, stateHash, BigDecimal.ZERO.setScale(8));
|
||||
} else {
|
||||
// Legacy v1 AT
|
||||
// We deploy these in 'dead' state as they will never be run on Qora2
|
||||
// We would deploy these in 'dead' state as they will never be run on Qora2
|
||||
// but this breaks import from Qora1 so something else will have to mark them dead at hard-fork
|
||||
|
||||
// Extract code bytes length
|
||||
ByteBuffer byteBuffer = ByteBuffer.wrap(deployATTransactionData.getCreationBytes());
|
||||
@ -89,10 +90,10 @@ public class AT {
|
||||
byte[] codeBytes = new byte[codeLen];
|
||||
byteBuffer.get(codeBytes);
|
||||
|
||||
// Create AT but in dead state
|
||||
// Create AT
|
||||
boolean isSleeping = false;
|
||||
Integer sleepUntilHeight = null;
|
||||
boolean isFinished = true;
|
||||
boolean isFinished = false;
|
||||
boolean hadFatalError = false;
|
||||
boolean isFrozen = false;
|
||||
Long frozenBalance = null;
|
||||
|
@ -9,6 +9,7 @@ import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
@ -887,6 +888,7 @@ public class Block {
|
||||
this.repository.getBlockRepository().save(this.blockData);
|
||||
|
||||
// Link transactions to this block, thus removing them from unconfirmed transactions list.
|
||||
// Also update "transaction participants" in repository for "transactions involving X" support in API
|
||||
for (int sequence = 0; sequence < transactions.size(); ++sequence) {
|
||||
Transaction transaction = transactions.get(sequence);
|
||||
|
||||
@ -897,6 +899,10 @@ public class Block {
|
||||
|
||||
// No longer unconfirmed
|
||||
this.repository.getTransactionRepository().confirmTransaction(transaction.getTransactionData().getSignature());
|
||||
|
||||
List<Account> participants = transaction.getInvolvedAccounts();
|
||||
List<String> participantAddresses = participants.stream().map(account -> account.getAddress()).collect(Collectors.toList());
|
||||
this.repository.getTransactionRepository().saveParticipants(transaction.getTransactionData(), participantAddresses);
|
||||
}
|
||||
}
|
||||
|
||||
@ -918,6 +924,8 @@ public class Block {
|
||||
BlockTransactionData blockTransactionData = new BlockTransactionData(this.getSignature(), sequence,
|
||||
transaction.getTransactionData().getSignature());
|
||||
this.repository.getBlockRepository().delete(blockTransactionData);
|
||||
|
||||
this.repository.getTransactionRepository().deleteParticipants(transaction.getTransactionData());
|
||||
}
|
||||
|
||||
// If fees are non-zero then remove fees from generator's balance
|
||||
|
@ -1,6 +1,7 @@
|
||||
package qora.transaction;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
@ -53,6 +54,14 @@ public class ATTransaction extends Transaction {
|
||||
return Collections.singletonList(new Account(this.repository, this.atTransactionData.getRecipient()));
|
||||
}
|
||||
|
||||
/** For AT-Transactions, the use the AT address instead of transaction creator (which is genesis account) */
|
||||
@Override
|
||||
public List<Account> getInvolvedAccounts() throws DataException {
|
||||
List<Account> participants = new ArrayList<Account>(getRecipientAccounts());
|
||||
participants.add(getATAccount());
|
||||
return participants;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInvolved(Account account) throws DataException {
|
||||
String address = account.getAddress();
|
||||
|
@ -41,6 +41,12 @@ public class GenesisTransaction extends Transaction {
|
||||
return Collections.singletonList(new Account(this.repository, genesisTransactionData.getRecipient()));
|
||||
}
|
||||
|
||||
/** For Genesis Transactions, do not include transaction creator (which is genesis account) */
|
||||
@Override
|
||||
public List<Account> getInvolvedAccounts() throws DataException {
|
||||
return getRecipientAccounts();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInvolved(Account account) throws DataException {
|
||||
String address = account.getAddress();
|
||||
|
@ -3,6 +3,7 @@ package qora.transaction;
|
||||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
import java.math.MathContext;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@ -10,7 +11,6 @@ import java.util.Map;
|
||||
import static java.util.Arrays.stream;
|
||||
import static java.util.stream.Collectors.toMap;
|
||||
|
||||
import data.block.BlockData;
|
||||
import data.transaction.TransactionData;
|
||||
import qora.account.Account;
|
||||
import qora.account.PrivateKeyAccount;
|
||||
@ -314,6 +314,21 @@ public abstract class Transaction {
|
||||
*/
|
||||
public abstract List<Account> getRecipientAccounts() throws DataException;
|
||||
|
||||
/**
|
||||
* Returns a list of involved accounts for this transaction.
|
||||
* <p>
|
||||
* "Involved" means sender or recipient.
|
||||
*
|
||||
* @return list of involved accounts, or empty list if none
|
||||
* @throws DataException
|
||||
*/
|
||||
public List<Account> getInvolvedAccounts() throws DataException {
|
||||
// Typically this is all the recipients plus the transaction creator/sender
|
||||
List<Account> participants = new ArrayList<Account>(getRecipientAccounts());
|
||||
participants.add(getCreator());
|
||||
return participants;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether passed account is an involved party in this transaction.
|
||||
* <p>
|
||||
@ -349,17 +364,6 @@ public abstract class Transaction {
|
||||
return new PublicKeyAccount(this.repository, this.transactionData.getCreatorPublicKey());
|
||||
}
|
||||
|
||||
/**
|
||||
* Load encapsulating block's data from repository, if any
|
||||
*
|
||||
* @return BlockData, or null if transaction is not in a Block
|
||||
* @throws DataException
|
||||
*/
|
||||
@Deprecated
|
||||
public BlockData getBlock() throws DataException {
|
||||
return this.repository.getTransactionRepository().getBlockDataFromSignature(this.transactionData.getSignature());
|
||||
}
|
||||
|
||||
/**
|
||||
* Load parent's transaction data from repository via this transaction's reference.
|
||||
*
|
||||
|
@ -2,7 +2,6 @@ package repository;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import data.account.AccountBalanceData;
|
||||
import data.assets.AssetData;
|
||||
import data.assets.OrderData;
|
||||
import data.assets.TradeData;
|
||||
@ -39,6 +38,8 @@ public interface AssetRepository {
|
||||
|
||||
// Trades
|
||||
|
||||
public List<TradeData> getTrades(long haveAssetId, long wantAssetId) throws DataException;
|
||||
|
||||
public List<TradeData> getOrdersTrades(byte[] orderId) throws DataException;
|
||||
|
||||
public void save(TradeData tradeData) throws DataException;
|
||||
|
@ -5,10 +5,10 @@ import qora.transaction.Transaction.TransactionType;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import data.block.BlockData;
|
||||
|
||||
public interface TransactionRepository {
|
||||
|
||||
// Fetching transactions / transaction height
|
||||
|
||||
public TransactionData fromSignature(byte[] signature) throws DataException;
|
||||
|
||||
public TransactionData fromReference(byte[] reference) throws DataException;
|
||||
@ -18,11 +18,16 @@ public interface TransactionRepository {
|
||||
/** Returns block height containing transaction or 0 if not in a block or transaction doesn't exist */
|
||||
public int getHeightFromSignature(byte[] signature) throws DataException;
|
||||
|
||||
@Deprecated
|
||||
public BlockData getBlockDataFromSignature(byte[] signature) throws DataException;
|
||||
// Transaction participants
|
||||
|
||||
public List<byte[]> getAllSignaturesInvolvingAddress(String address) throws DataException;
|
||||
|
||||
public void saveParticipants(TransactionData transactionData, List<String> participants) throws DataException;
|
||||
|
||||
public void deleteParticipants(TransactionData transactionData) throws DataException;
|
||||
|
||||
// Searching transactions
|
||||
|
||||
public List<byte[]> getAllSignaturesMatchingCriteria(Integer startBlock, Integer blockLimit, TransactionType txType, String address) throws DataException;
|
||||
|
||||
/**
|
||||
|
@ -228,6 +228,34 @@ public class HSQLDBAssetRepository implements AssetRepository {
|
||||
|
||||
// Trades
|
||||
|
||||
@Override
|
||||
public List<TradeData> getTrades(long haveAssetId, long wantAssetId) throws DataException {
|
||||
List<TradeData> trades = new ArrayList<TradeData>();
|
||||
|
||||
try (ResultSet resultSet = this.repository.checkedExecute(
|
||||
"SELECT initiating_order_id, target_order_id, AssetTrades.amount, AssetTrades.price, traded FROM AssetOrders JOIN AssetTrades ON initiating_order_id = asset_order_id "
|
||||
+ "WHERE have_asset_id = ? AND want_asset_id = ? ORDER BY traded ASC",
|
||||
haveAssetId, wantAssetId)) {
|
||||
if (resultSet == null)
|
||||
return trades;
|
||||
|
||||
do {
|
||||
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);
|
||||
} while (resultSet.next());
|
||||
|
||||
return trades;
|
||||
} catch (SQLException e) {
|
||||
throw new DataException("Unable to fetch asset trades from repository", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<TradeData> getOrdersTrades(byte[] initiatingOrderId) throws DataException {
|
||||
List<TradeData> trades = new ArrayList<TradeData>();
|
||||
|
@ -5,8 +5,6 @@ import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
|
||||
import qora.crypto.Crypto;
|
||||
|
||||
public class HSQLDBDatabaseUpdates {
|
||||
|
||||
/**
|
||||
@ -75,8 +73,11 @@ public class HSQLDBDatabaseUpdates {
|
||||
switch (databaseVersion) {
|
||||
case 0:
|
||||
// create from new
|
||||
stmt.execute("SET DATABASE SQL NAMES TRUE"); // SQL keywords cannot be used as DB object names, e.g. table names
|
||||
stmt.execute("SET DATABASE SQL SYNTAX MYS TRUE"); // Required for our use of INSERT ... ON DUPLICATE KEY UPDATE ... syntax
|
||||
stmt.execute("SET DATABASE SQL RESTRICT EXEC TRUE"); // No multiple-statement execute() or DDL/DML executeQuery()
|
||||
stmt.execute("SET DATABASE DEFAULT TABLE TYPE CACHED");
|
||||
stmt.execute("SET DATABASE COLLATION SQL_TEXT NO PAD");
|
||||
stmt.execute("SET DATABASE COLLATION SQL_TEXT NO PAD"); // Do not pad strings to same length before comparison
|
||||
stmt.execute("CREATE COLLATION SQL_TEXT_UCC_NO_PAD FOR SQL_TEXT FROM SQL_TEXT_UCC NO PAD");
|
||||
stmt.execute("CREATE COLLATION SQL_TEXT_NO_PAD FOR SQL_TEXT FROM SQL_TEXT NO PAD");
|
||||
stmt.execute("SET FILES SPACE TRUE"); // Enable per-table block space within .data file, useful for CACHED table types
|
||||
@ -151,13 +152,12 @@ public class HSQLDBDatabaseUpdates {
|
||||
// Index to allow quick sorting by creation-else-signature
|
||||
stmt.execute("CREATE INDEX UnconfirmedTransactionsIndex ON UnconfirmedTransactions (creation, signature)");
|
||||
|
||||
// Transaction recipients
|
||||
// XXX This should be transaction "participants" to allow lookup of all activity by an address!
|
||||
// Could add "is_recipient" boolean flag
|
||||
stmt.execute("CREATE TABLE TransactionRecipients (signature Signature, recipient QoraAddress NOT NULL, "
|
||||
// Transaction participants
|
||||
// To allow lookup of all activity by an address
|
||||
stmt.execute("CREATE TABLE TransactionParticipants (signature Signature, participant QoraAddress NOT NULL, "
|
||||
+ "FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
|
||||
// Use a separate table space as this table will be very large.
|
||||
stmt.execute("SET TABLE TransactionRecipients NEW SPACE");
|
||||
stmt.execute("SET TABLE TransactionParticipants NEW SPACE");
|
||||
break;
|
||||
|
||||
case 3:
|
||||
@ -310,9 +310,11 @@ public class HSQLDBDatabaseUpdates {
|
||||
|
||||
case 22:
|
||||
// Accounts
|
||||
stmt.execute("CREATE TABLE Accounts (account QoraAddress, reference Signature, PRIMARY KEY (account))");
|
||||
stmt.execute("CREATE TABLE Accounts (account QoraAddress, reference Signature, public_key QoraPublicKey, PRIMARY KEY (account))");
|
||||
stmt.execute("CREATE TABLE AccountBalances (account QoraAddress, asset_id AssetID, balance QoraAmount NOT NULL, "
|
||||
+ "PRIMARY KEY (account, asset_id), FOREIGN KEY (account) REFERENCES Accounts (account) ON DELETE CASCADE)");
|
||||
// For looking up an account by public key
|
||||
stmt.execute("CREATE INDEX AccountPublicKeyIndex on Accounts (public_key)");
|
||||
break;
|
||||
|
||||
case 23:
|
||||
@ -387,35 +389,6 @@ public class HSQLDBDatabaseUpdates {
|
||||
stmt.execute("CREATE INDEX ATTransactionsIndex on ATTransactions (AT_address)");
|
||||
break;
|
||||
|
||||
case 28:
|
||||
// Associate public keys with accounts
|
||||
stmt.execute("ALTER TABLE Accounts add public_key QoraPublicKey");
|
||||
// For looking up an account by public key
|
||||
stmt.execute("CREATE INDEX AccountPublicKeyIndex on Accounts (public_key)");
|
||||
|
||||
// Do not call close() on this as connection did not come from pool!
|
||||
HSQLDBRepository repository = new HSQLDBRepository(connection);
|
||||
|
||||
try (ResultSet resultSet = repository.checkedExecute("SELECT DISTINCT creator from Transactions")) {
|
||||
if (resultSet == null) {
|
||||
repository = null;
|
||||
break;
|
||||
}
|
||||
|
||||
do {
|
||||
byte[] publicKey = resultSet.getBytes(1);
|
||||
|
||||
String address = Crypto.toAddress(publicKey);
|
||||
|
||||
HSQLDBSaver saveHelper = new HSQLDBSaver("Accounts");
|
||||
saveHelper.bind("account", address).bind("public_key", publicKey);
|
||||
saveHelper.execute(repository);
|
||||
} while (resultSet.next());
|
||||
}
|
||||
|
||||
repository = null;
|
||||
break;
|
||||
|
||||
default:
|
||||
// nothing to do
|
||||
return false;
|
||||
|
@ -31,10 +31,6 @@ public class HSQLDBRepositoryFactory implements RepositoryFactory {
|
||||
|
||||
Properties properties = new Properties();
|
||||
properties.setProperty("close_result", "true"); // Auto-close old ResultSet if Statement creates new ResultSet
|
||||
properties.setProperty("sql.strict_exec", "true"); // No multi-SQL execute() or DDL/DML executeQuery()
|
||||
properties.setProperty("sql.enforce_names", "true"); // SQL keywords cannot be used as DB object names, e.g. table names
|
||||
properties.setProperty("sql.syntax_mys", "true"); // Required for our use of INSERT ... ON DUPLICATE KEY UPDATE ... syntax
|
||||
properties.setProperty("sql.pad_space", "false"); // Do not pad strings to same length before comparison
|
||||
this.connectionPool.setProperties(properties);
|
||||
|
||||
// Perform DB updates?
|
||||
|
@ -7,12 +7,8 @@ import java.sql.Timestamp;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
import java.util.List;
|
||||
import java.util.StringJoiner;
|
||||
|
||||
import com.google.common.base.Strings;
|
||||
|
||||
import data.PaymentData;
|
||||
import data.block.BlockData;
|
||||
import data.transaction.TransactionData;
|
||||
import qora.transaction.Transaction.TransactionType;
|
||||
import repository.DataException;
|
||||
@ -249,31 +245,11 @@ public class HSQLDBTransactionRepository implements TransactionRepository {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public BlockData getBlockDataFromSignature(byte[] signature) throws DataException {
|
||||
if (signature == null)
|
||||
return null;
|
||||
|
||||
// Fetch block signature (if any)
|
||||
try (ResultSet resultSet = this.repository.checkedExecute("SELECT block_signature FROM BlockTransactions WHERE transaction_signature = ? LIMIT 1",
|
||||
signature)) {
|
||||
if (resultSet == null)
|
||||
return null;
|
||||
|
||||
byte[] blockSignature = resultSet.getBytes(1);
|
||||
|
||||
return this.repository.getBlockRepository().fromSignature(blockSignature);
|
||||
} catch (SQLException | DataException e) {
|
||||
throw new DataException("Unable to fetch transaction's block from repository", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<byte[]> getAllSignaturesInvolvingAddress(String address) throws DataException {
|
||||
List<byte[]> signatures = new ArrayList<byte[]>();
|
||||
|
||||
// XXX We need a table for all parties involved in a transaction, not just recipients
|
||||
try (ResultSet resultSet = this.repository.checkedExecute("SELECT signature FROM TransactionRecipients WHERE recipient = ?", address)) {
|
||||
try (ResultSet resultSet = this.repository.checkedExecute("SELECT signature FROM TransactionRecipients WHERE participant = ?", address)) {
|
||||
if (resultSet == null)
|
||||
return signatures;
|
||||
|
||||
@ -289,6 +265,32 @@ public class HSQLDBTransactionRepository implements TransactionRepository {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveParticipants(TransactionData transactionData, List<String> participants) throws DataException {
|
||||
byte[] signature = transactionData.getSignature();
|
||||
|
||||
try {
|
||||
for (String participant : participants) {
|
||||
HSQLDBSaver saver = new HSQLDBSaver("TransactionParticipants");
|
||||
|
||||
saver.bind("signature", signature).bind("participant", participant);
|
||||
|
||||
saver.execute(this.repository);
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
throw new DataException("Unable to save transaction participant into repository", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteParticipants(TransactionData transactionData) throws DataException {
|
||||
try {
|
||||
this.repository.delete("TransactionParticipants", "signature = ?", transactionData.getSignature());
|
||||
} catch (SQLException e) {
|
||||
throw new DataException("Unable to delete transaction participants from repository", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<byte[]> getAllSignaturesMatchingCriteria(Integer startBlock, Integer blockLimit, TransactionType txType, String address) throws DataException {
|
||||
List<byte[]> signatures = new ArrayList<byte[]>();
|
||||
@ -323,13 +325,13 @@ public class HSQLDBTransactionRepository implements TransactionRepository {
|
||||
|
||||
if (hasAddress) {
|
||||
if (hasTxType)
|
||||
tableJoins.add("TransactionRecipients ON TransactionRecipients.signature = Transactions.signature");
|
||||
tableJoins.add("TransactionParticipants ON TransactionParticipants.signature = Transactions.signature");
|
||||
else if (hasHeightRange)
|
||||
tableJoins.add("TransactionRecipients ON TransactionRecipients.signature = BlockTransactions.transaction_signature");
|
||||
tableJoins.add("TransactionParticipants ON TransactionParticipants.signature = BlockTransactions.transaction_signature");
|
||||
else
|
||||
tableJoins.add("TransactionRecipients");
|
||||
tableJoins.add("TransactionParticipants");
|
||||
|
||||
signatureColumn = "TransactionRecipients.signature";
|
||||
signatureColumn = "TransactionParticipants.signature";
|
||||
}
|
||||
|
||||
// WHERE clauses next
|
||||
@ -346,14 +348,13 @@ public class HSQLDBTransactionRepository implements TransactionRepository {
|
||||
whereClauses.add("Transactions.type = " + txType.value);
|
||||
|
||||
if (hasAddress) {
|
||||
whereClauses.add("TransactionRecipients.recipient = ?");
|
||||
whereClauses.add("TransactionParticipants.participant = ?");
|
||||
bindParams.add(address);
|
||||
}
|
||||
|
||||
String sql = "SELECT " + signatureColumn + " FROM " + String.join(" JOIN ", tableJoins) + " WHERE " + String.join(" AND ", whereClauses);
|
||||
System.out.println("Transaction search SQL:\n" + sql);
|
||||
|
||||
// XXX We need a table for all parties involved in a transaction, not just recipients
|
||||
try (ResultSet resultSet = this.repository.checkedExecute(sql, bindParams.toArray())) {
|
||||
if (resultSet == null)
|
||||
return signatures;
|
||||
|
@ -120,8 +120,7 @@ public class IssueAssetTransactionTransformer extends TransactionTransformer {
|
||||
}
|
||||
|
||||
/**
|
||||
* In Qora v1, the bytes used for verification have transaction type set to REGISTER_NAME_TRANSACTION so we need to test for v1-ness and adjust the bytes
|
||||
* accordingly.
|
||||
* In Qora v1, the bytes used for verification have asset's reference zeroed so we need to test for v1-ness and adjust the bytes accordingly.
|
||||
*
|
||||
* @param transactionData
|
||||
* @return byte[]
|
||||
@ -136,8 +135,8 @@ public class IssueAssetTransactionTransformer extends TransactionTransformer {
|
||||
// Special v1 version
|
||||
|
||||
// Zero duplicate signature/reference
|
||||
int start = bytes.length - SIGNATURE_LENGTH - BIG_DECIMAL_LENGTH;
|
||||
int end = start + SIGNATURE_LENGTH;
|
||||
int start = bytes.length - ASSET_REFERENCE_LENGTH - FEE_LENGTH; // before asset reference (and fee)
|
||||
int end = start + ASSET_REFERENCE_LENGTH;
|
||||
Arrays.fill(bytes, start, end, (byte) 0);
|
||||
|
||||
return bytes;
|
||||
|
@ -1,5 +1,6 @@
|
||||
import com.google.common.hash.HashCode;
|
||||
|
||||
import controller.Controller;
|
||||
import data.transaction.TransactionData;
|
||||
import qora.block.BlockChain;
|
||||
import repository.DataException;
|
||||
@ -13,8 +14,6 @@ import utils.Base58;
|
||||
|
||||
public class txhex {
|
||||
|
||||
public static final String connectionUrl = "jdbc:hsqldb:file:db/test;create=true";
|
||||
|
||||
public static void main(String[] args) {
|
||||
if (args.length == 0) {
|
||||
System.err.println("usage: txhex <base58-tx-signature>");
|
||||
@ -24,7 +23,7 @@ public class txhex {
|
||||
byte[] signature = Base58.decode(args[0]);
|
||||
|
||||
try {
|
||||
RepositoryFactory repositoryFactory = new HSQLDBRepositoryFactory(connectionUrl);
|
||||
RepositoryFactory repositoryFactory = new HSQLDBRepositoryFactory(Controller.connectionUrl);
|
||||
RepositoryManager.setRepositoryFactory(repositoryFactory);
|
||||
} catch (DataException e) {
|
||||
System.err.println("Couldn't connect to repository: " + e.getMessage());
|
||||
|
@ -31,6 +31,7 @@ import com.google.common.hash.HashCode;
|
||||
import com.google.common.primitives.Bytes;
|
||||
import com.google.common.primitives.Ints;
|
||||
|
||||
import controller.Controller;
|
||||
import data.at.ATData;
|
||||
import data.at.ATStateData;
|
||||
import data.block.BlockData;
|
||||
@ -56,7 +57,6 @@ import utils.Triple;
|
||||
public class v1feeder extends Thread {
|
||||
|
||||
private static final Logger LOGGER = LogManager.getLogger(v1feeder.class);
|
||||
public static final String connectionUrl = "jdbc:hsqldb:file:db/test;create=true";
|
||||
|
||||
private static final int INACTIVITY_TIMEOUT = 60 * 1000; // milliseconds
|
||||
private static final int CONNECTION_TIMEOUT = 2 * 1000; // milliseconds
|
||||
@ -529,7 +529,7 @@ public class v1feeder extends Thread {
|
||||
readLegacyATs(legacyATPathname);
|
||||
|
||||
try {
|
||||
RepositoryFactory repositoryFactory = new HSQLDBRepositoryFactory(connectionUrl);
|
||||
RepositoryFactory repositoryFactory = new HSQLDBRepositoryFactory(Controller.connectionUrl);
|
||||
RepositoryManager.setRepositoryFactory(repositoryFactory);
|
||||
} catch (DataException e) {
|
||||
LOGGER.error("Couldn't connect to repository", e);
|
||||
|
@ -1,6 +1,9 @@
|
||||
package test;
|
||||
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
|
||||
import controller.Controller;
|
||||
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
|
||||
import repository.DataException;
|
||||
@ -10,12 +13,9 @@ import repository.hsqldb.HSQLDBRepositoryFactory;
|
||||
|
||||
public class Common {
|
||||
|
||||
// public static final String connectionUrl = "jdbc:hsqldb:file:db/test;create=true;close_result=true;sql.strict_exec=true;sql.enforce_names=true;sql.syntax_mys=true;sql.pad_space=false";
|
||||
public static final String connectionUrl = "jdbc:hsqldb:file:db/test;create=true";
|
||||
|
||||
@BeforeAll
|
||||
public static void setRepository() throws DataException {
|
||||
RepositoryFactory repositoryFactory = new HSQLDBRepositoryFactory(connectionUrl);
|
||||
RepositoryFactory repositoryFactory = new HSQLDBRepositoryFactory(Controller.connectionUrl);
|
||||
RepositoryManager.setRepositoryFactory(repositoryFactory);
|
||||
}
|
||||
|
||||
|
@ -23,7 +23,7 @@ import repository.hsqldb.HSQLDBRepositoryFactory;
|
||||
// Don't extend Common as we want an in-memory database
|
||||
public class GenesisTests {
|
||||
|
||||
public static final String connectionUrl = "jdbc:hsqldb:mem:db/test;create=true;close_result=true;sql.strict_exec=true;sql.enforce_names=true;sql.syntax_mys=true";
|
||||
public static final String connectionUrl = "jdbc:hsqldb:mem:db/blockchain;create=true";
|
||||
|
||||
@BeforeAll
|
||||
public static void setRepository() throws DataException {
|
||||
|
@ -74,7 +74,7 @@ import settings.Settings;
|
||||
// Don't extend Common as we want to use an in-memory database
|
||||
public class TransactionTests {
|
||||
|
||||
private static final String connectionUrl = "jdbc:hsqldb:mem:db/test;create=true;close_result=true;sql.strict_exec=true;sql.enforce_names=true;sql.syntax_mys=true";
|
||||
private static final String connectionUrl = "jdbc:hsqldb:mem:db/blockchain;create=true";
|
||||
|
||||
private static final byte[] generatorSeed = HashCode.fromString("0123456789abcdeffedcba98765432100123456789abcdeffedcba9876543210").asBytes();
|
||||
private static final byte[] senderSeed = HashCode.fromString("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef").asBytes();
|
||||
|
Loading…
x
Reference in New Issue
Block a user