HSQLDB issue, resource leak prevention, v1 differences

HSQLDB v2.4.0 had some issue with non-padded, case-insensitive string comparisons.
This is fixed in svn r5836-ish of HSQLDB but yet to be pushed out to new HSQLDB release.
So this commit includes hsqldb-r5836.jar and modified pom.xml/.classpath for now.

No need for duplicate, hidden creatorPublicKey in CancelOrderTransactionData,
CreateOrderTransactionData and CreatePollTransactionData.

Various changes to use more try-with-resources, especially with JDBC objects like
Connection, Statement, PreparedStatement, ResultSet.

Added loads of missing @Override annotations.

Fixed bug in Asset exchange order matching where the matching logic loop
would incorrectly adjust temporary amount fulfilled
by the "want" asset amount (in matchedAmount)
instead of the "have" asset amount (in tradePrice).

Disabled check for duplicate asset name in IssueAssetTransactions for old v1 transactions.

In HSQLDB repository we now use ResultSet.getTimestamp(index, UTC-calendar) to make sure we
only store/fetch UTC timestamps. The UTC-calendar is made using static final TimeZone called
HSQLDBRepository.UTC.

To keep asset IDs in line with v1, Assets.asset_id values are generated on-the-fly in HSQLDB
using a "before insert" trigger on Assets table. Corresponding code
calling HSQLDBRepository.callIdentity() replaced with SELECT statement instead.

Moved most of the HSQLDB connection properties from the connection URL to explicit code in
HSQLDBRepositoryFactory.

Fixed incorrect 'amount' lengths in PaymentTransformer, as used by MultiPayment and Arbitrary
transaction types.

Added support for mangled arbitrary transaction bytes when generating/verifying a v1 transaction signature.
In v1 Arbitrary transactions, bytes-for-signing are lost prior to final payment (but only if there are any payments).
Added corresponding code for multi-payment transactions in the same vein.
This commit is contained in:
catbref 2018-08-07 15:44:41 +01:00
parent 7da84b2b85
commit e56d8f5e02
55 changed files with 744 additions and 614 deletions

View File

@ -17,5 +17,6 @@
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/4"/>
<classpathentry kind="lib" path="lib/hsqldb-r5836.jar"/>
<classpathentry kind="output" path="target/classes"/>
</classpath>

BIN
lib/hsqldb-r5836.jar Normal file

Binary file not shown.

View File

@ -17,11 +17,6 @@
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.hsqldb</groupId>
<artifactId>hsqldb</artifactId>
<version>2.4.0</version>
</dependency>
<dependency>
<groupId>com.googlecode.json-simple</groupId>
<artifactId>json-simple</artifactId>

View File

@ -7,7 +7,6 @@ import qora.transaction.Transaction;
public class CancelOrderTransactionData extends TransactionData {
// Properties
private byte[] creatorPublicKey;
private byte[] orderId;
// Constructors
@ -15,7 +14,6 @@ public class CancelOrderTransactionData extends TransactionData {
public CancelOrderTransactionData(byte[] creatorPublicKey, byte[] orderId, BigDecimal fee, long timestamp, byte[] reference, byte[] signature) {
super(Transaction.TransactionType.CANCEL_ASSET_ORDER, fee, creatorPublicKey, timestamp, reference, signature);
this.creatorPublicKey = creatorPublicKey;
this.orderId = orderId;
}
@ -25,10 +23,6 @@ public class CancelOrderTransactionData extends TransactionData {
// Getters/Setters
public byte[] getCreatorPublicKey() {
return this.creatorPublicKey;
}
public byte[] getOrderId() {
return this.orderId;
}

View File

@ -7,7 +7,6 @@ import qora.transaction.Transaction.TransactionType;
public class CreateOrderTransactionData extends TransactionData {
// Properties
private byte[] creatorPublicKey;
private long haveAssetId;
private long wantAssetId;
private BigDecimal amount;
@ -19,7 +18,6 @@ public class CreateOrderTransactionData extends TransactionData {
long timestamp, byte[] reference, byte[] signature) {
super(TransactionType.CREATE_ASSET_ORDER, fee, creatorPublicKey, timestamp, reference, signature);
this.creatorPublicKey = creatorPublicKey;
this.haveAssetId = haveAssetId;
this.wantAssetId = wantAssetId;
this.amount = amount;
@ -33,10 +31,6 @@ public class CreateOrderTransactionData extends TransactionData {
// Getters/Setters
public byte[] getCreatorPublicKey() {
return this.creatorPublicKey;
}
public long getHaveAssetId() {
return this.haveAssetId;
}

View File

@ -9,7 +9,6 @@ import qora.transaction.Transaction;
public class CreatePollTransactionData extends TransactionData {
// Properties
private byte[] creatorPublicKey;
private String owner;
private String pollName;
private String description;
@ -21,7 +20,6 @@ public class CreatePollTransactionData extends TransactionData {
BigDecimal fee, long timestamp, byte[] reference, byte[] signature) {
super(Transaction.TransactionType.CREATE_POLL, fee, creatorPublicKey, timestamp, reference, signature);
this.creatorPublicKey = creatorPublicKey;
this.owner = owner;
this.pollName = pollName;
this.description = description;
@ -35,10 +33,6 @@ public class CreatePollTransactionData extends TransactionData {
// Getters/setters
public byte[] getCreatorPublicKey() {
return this.creatorPublicKey;
}
public String getOwner() {
return this.owner;
}

View File

@ -44,21 +44,14 @@ public class migrate {
private static Map<String, byte[]> publicKeyByAddress = new HashMap<String, byte[]>();
public static Object fetchBlockJSON(int height) throws IOException {
InputStream is;
try {
is = new URL("http://localhost:9085/blocks/byheight/" + height).openStream();
try (InputStream is = new URL("http://localhost:9085/blocks/byheight/" + height).openStream();
InputStreamReader isr = new InputStreamReader(is, Charset.forName("UTF-8"));
BufferedReader reader = new BufferedReader(isr)) {
return JSONValue.parseWithException(reader);
} catch (IOException e) {
return null;
}
try {
BufferedReader reader = new BufferedReader(new InputStreamReader(is, Charset.forName("UTF-8")));
return JSONValue.parseWithException(reader);
} catch (ParseException e) {
return null;
} finally {
is.close();
}
}
@ -69,8 +62,8 @@ public class migrate {
InputStream is = new URL("http://localhost:9085/addresses/publickey/" + address).openStream();
try {
String publicKey58 = CharStreams.toString(new InputStreamReader(is, Charset.forName("UTF-8")));
try (InputStreamReader isr = new InputStreamReader(is, Charset.forName("UTF-8"))) {
String publicKey58 = CharStreams.toString(isr);
byte[] publicKey = Base58.decode(publicKey58);
publicKeyByAddress.put(address, publicKey);
@ -81,12 +74,12 @@ public class migrate {
}
public static void savePublicKeys(Connection connection) throws SQLException {
PreparedStatement pStmt = connection.prepareStatement("INSERT IGNORE INTO Test_public_keys VALUES (?, ?)");
for (Entry<String, byte[]> entry : publicKeyByAddress.entrySet()) {
pStmt.setString(1, entry.getKey());
pStmt.setBytes(2, entry.getValue());
pStmt.execute();
try (PreparedStatement pStmt = connection.prepareStatement("INSERT IGNORE INTO Test_public_keys VALUES (?, ?)")) {
for (Entry<String, byte[]> entry : publicKeyByAddress.entrySet()) {
pStmt.setString(1, entry.getKey());
pStmt.setBytes(2, entry.getValue());
pStmt.execute();
}
}
}
@ -103,6 +96,7 @@ public class migrate {
return output.toString();
}
@SuppressWarnings("resource")
public static void main(String args[]) throws SQLException, DataException, IOException {
// Genesis public key
publicKeyByAddress.put(GENESIS_ADDRESS, GENESIS_PUBLICKEY);
@ -132,14 +126,14 @@ public class migrate {
.prepareStatement("INSERT INTO PaymentTransactions " + formatWithPlaceholders("signature", "sender", "recipient", "amount"));
PreparedStatement registerNamePStmt = c
.prepareStatement("INSERT INTO RegisterNameTransactions " + formatWithPlaceholders("signature", "registrant", "name", "owner", "data"));
PreparedStatement updateNamePStmt = c
.prepareStatement("INSERT INTO UpdateNameTransactions " + formatWithPlaceholders("signature", "owner", "name", "new_owner", "new_data", "name_reference"));
PreparedStatement updateNamePStmt = c.prepareStatement(
"INSERT INTO UpdateNameTransactions " + formatWithPlaceholders("signature", "owner", "name", "new_owner", "new_data", "name_reference"));
PreparedStatement sellNamePStmt = c
.prepareStatement("INSERT INTO SellNameTransactions " + formatWithPlaceholders("signature", "owner", "name", "amount"));
PreparedStatement cancelSellNamePStmt = c
.prepareStatement("INSERT INTO CancelSellNameTransactions " + formatWithPlaceholders("signature", "owner", "name"));
PreparedStatement buyNamePStmt = c
.prepareStatement("INSERT INTO BuyNameTransactions " + formatWithPlaceholders("signature", "buyer", "name", "seller", "amount", "name_reference"));
PreparedStatement buyNamePStmt = c.prepareStatement(
"INSERT INTO BuyNameTransactions " + formatWithPlaceholders("signature", "buyer", "name", "seller", "amount", "name_reference"));
PreparedStatement createPollPStmt = c
.prepareStatement("INSERT INTO CreatePollTransactions " + formatWithPlaceholders("signature", "creator", "owner", "poll_name", "description"));
PreparedStatement createPollOptionPStmt = c

View File

@ -163,7 +163,7 @@ public class Order {
// Trade can go ahead!
// Calculate the total cost to us based on their price
// Calculate the total cost to us, in have-asset, based on their price
BigDecimal tradePrice = matchedAmount.multiply(theirOrderData.getPrice()).setScale(8);
// Construct trade
@ -174,7 +174,7 @@ public class Order {
trade.process();
// Update our order in terms of fulfilment, etc. but do not save into repository as that's handled by Trade above
this.orderData.setFulfilled(this.orderData.getFulfilled().add(matchedAmount));
this.orderData.setFulfilled(this.orderData.getFulfilled().add(tradePrice));
// Continue on to process other open orders in case we still have amount left to match
}

View File

@ -38,9 +38,11 @@ public class BlockChain {
private static final long ASSETS_RELEASE_TIMESTAMP = 0L; // From Qora epoch
private static final long VOTING_RELEASE_TIMESTAMP = 1403715600000L; // 2014-06-25T17:00:00+00:00
private static final long ARBITRARY_RELEASE_TIMESTAMP = 1405702800000L; // 2014-07-18T17:00:00+00:00
private static final long CREATE_POLL_V2_TIMESTAMP = 1552500000000L; // 2019-03-13T18:00:00+00:00 // Future Qora v2 CREATE POLL transactions
private static final long ISSUE_ASSET_V2_TIMESTAMP = 1552500000000L; // 2019-03-13T18:00:00+00:00 // Future Qora v2 ISSUE ASSET transactions
private static final long CREATE_ORDER_V2_TIMESTAMP = 1552500000000L; // 2019-03-13T18:00:00+00:00 // Future Qora v2 CREATE ORDER transactions
private static final long ARBITRARY_TRANSACTION_V2_TIMESTAMP = 1552500000000L; // 2019-03-13T18:00:00+00:00 // Future Qora v2 ARBITRARY transactions
/**
* Some sort start-up/initialization/checking method.
@ -164,4 +166,11 @@ public class BlockChain {
return CREATE_ORDER_V2_TIMESTAMP;
}
public static long getArbitraryTransactionV2Timestamp() {
if (Settings.getInstance().isTestNet())
return 0;
return ARBITRARY_TRANSACTION_V2_TIMESTAMP;
}
}

View File

@ -44,6 +44,7 @@ public class ArbitraryTransaction extends Transaction {
// More information
@Override
public List<Account> getRecipientAccounts() throws DataException {
List<Account> recipients = new ArrayList<Account>();
@ -54,6 +55,7 @@ public class ArbitraryTransaction extends Transaction {
return recipients;
}
@Override
public boolean isInvolved(Account account) throws DataException {
String address = account.getAddress();
@ -68,6 +70,7 @@ public class ArbitraryTransaction extends Transaction {
return false;
}
@Override
public BigDecimal getAmount(Account account) throws DataException {
String address = account.getAddress();
BigDecimal amount = BigDecimal.ZERO.setScale(8);

View File

@ -32,14 +32,17 @@ public class CancelOrderTransaction extends Transaction {
// More information
@Override
public List<Account> getRecipientAccounts() {
return new ArrayList<Account>();
}
@Override
public boolean isInvolved(Account account) throws DataException {
return account.getAddress().equals(this.getCreator().getAddress());
}
@Override
public BigDecimal getAmount(Account account) throws DataException {
BigDecimal amount = BigDecimal.ZERO.setScale(8);
@ -51,6 +54,7 @@ public class CancelOrderTransaction extends Transaction {
// Navigation
@Override
public Account getCreator() throws DataException {
return new PublicKeyAccount(this.repository, cancelOrderTransactionData.getCreatorPublicKey());
}

View File

@ -33,14 +33,17 @@ public class CreateOrderTransaction extends Transaction {
// More information
@Override
public List<Account> getRecipientAccounts() {
return new ArrayList<Account>();
}
@Override
public boolean isInvolved(Account account) throws DataException {
return account.getAddress().equals(this.getCreator().getAddress());
}
@Override
public BigDecimal getAmount(Account account) throws DataException {
BigDecimal amount = BigDecimal.ZERO.setScale(8);
@ -52,6 +55,7 @@ public class CreateOrderTransaction extends Transaction {
// Navigation
@Override
public Account getCreator() throws DataException {
return new PublicKeyAccount(this.repository, createOrderTransactionData.getCreatorPublicKey());
}

View File

@ -35,10 +35,12 @@ public class CreatePollTransaction extends Transaction {
// More information
@Override
public List<Account> getRecipientAccounts() throws DataException {
return Collections.singletonList(getOwner());
}
@Override
public boolean isInvolved(Account account) throws DataException {
String address = account.getAddress();
@ -51,6 +53,7 @@ public class CreatePollTransaction extends Transaction {
return false;
}
@Override
public BigDecimal getAmount(Account account) throws DataException {
String address = account.getAddress();
BigDecimal amount = BigDecimal.ZERO.setScale(8);
@ -63,6 +66,7 @@ public class CreatePollTransaction extends Transaction {
// Navigation
@Override
public Account getCreator() throws DataException {
return new PublicKeyAccount(this.repository, this.createPollTransactionData.getCreatorPublicKey());
}

View File

@ -36,10 +36,12 @@ public class GenesisTransaction extends Transaction {
// More information
@Override
public List<Account> getRecipientAccounts() throws DataException {
return Collections.singletonList(new Account(this.repository, genesisTransactionData.getRecipient()));
}
@Override
public boolean isInvolved(Account account) throws DataException {
String address = account.getAddress();
@ -52,6 +54,7 @@ public class GenesisTransaction extends Transaction {
return false;
}
@Override
public BigDecimal getAmount(Account account) throws DataException {
String address = account.getAddress();
BigDecimal amount = BigDecimal.ZERO.setScale(8);

View File

@ -36,10 +36,12 @@ public class IssueAssetTransaction extends Transaction {
// More information
@Override
public List<Account> getRecipientAccounts() throws DataException {
return Collections.singletonList(getOwner());
}
@Override
public boolean isInvolved(Account account) throws DataException {
String address = account.getAddress();
@ -52,6 +54,7 @@ public class IssueAssetTransaction extends Transaction {
return false;
}
@Override
public BigDecimal getAmount(Account account) throws DataException {
String address = account.getAddress();
BigDecimal amount = BigDecimal.ZERO.setScale(8);
@ -117,8 +120,9 @@ public class IssueAssetTransaction extends Transaction {
return ValidationResult.NO_BALANCE;
// XXX: Surely we want to check the asset name isn't already taken? This check is not present in gen1.
if (this.repository.getAssetRepository().assetExists(issueAssetTransactionData.getAssetName()))
return ValidationResult.ASSET_ALREADY_EXISTS;
if (issueAssetTransactionData.getTimestamp() >= BlockChain.getIssueAssetV2Timestamp())
if (this.repository.getAssetRepository().assetExists(issueAssetTransactionData.getAssetName()))
return ValidationResult.ASSET_ALREADY_EXISTS;
return ValidationResult.OK;
}

View File

@ -34,10 +34,12 @@ public class MessageTransaction extends Transaction {
// More information
@Override
public List<Account> getRecipientAccounts() throws DataException {
return Collections.singletonList(new Account(this.repository, messageTransactionData.getRecipient()));
}
@Override
public boolean isInvolved(Account account) throws DataException {
String address = account.getAddress();
@ -50,6 +52,7 @@ public class MessageTransaction extends Transaction {
return false;
}
@Override
public BigDecimal getAmount(Account account) throws DataException {
String address = account.getAddress();
BigDecimal amount = BigDecimal.ZERO.setScale(8);

View File

@ -34,6 +34,7 @@ public class MultiPaymentTransaction extends Transaction {
// More information
@Override
public List<Account> getRecipientAccounts() throws DataException {
List<Account> recipients = new ArrayList<Account>();
@ -43,6 +44,7 @@ public class MultiPaymentTransaction extends Transaction {
return recipients;
}
@Override
public boolean isInvolved(Account account) throws DataException {
String address = account.getAddress();
@ -56,6 +58,7 @@ public class MultiPaymentTransaction extends Transaction {
return false;
}
@Override
public BigDecimal getAmount(Account account) throws DataException {
String address = account.getAddress();
BigDecimal amount = BigDecimal.ZERO.setScale(8);

View File

@ -30,10 +30,12 @@ public class PaymentTransaction extends Transaction {
// More information
@Override
public List<Account> getRecipientAccounts() throws DataException {
return Collections.singletonList(new Account(this.repository, paymentTransactionData.getRecipient()));
}
@Override
public boolean isInvolved(Account account) throws DataException {
String address = account.getAddress();
@ -46,6 +48,7 @@ public class PaymentTransaction extends Transaction {
return false;
}
@Override
public BigDecimal getAmount(Account account) throws DataException {
String address = account.getAddress();
BigDecimal amount = BigDecimal.ZERO.setScale(8);

View File

@ -31,10 +31,12 @@ public class TransferAssetTransaction extends Transaction {
// More information
@Override
public List<Account> getRecipientAccounts() throws DataException {
return Collections.singletonList(new Account(this.repository, transferAssetTransactionData.getRecipient()));
}
@Override
public boolean isInvolved(Account account) throws DataException {
String address = account.getAddress();
@ -47,6 +49,7 @@ public class TransferAssetTransaction extends Transaction {
return false;
}
@Override
public BigDecimal getAmount(Account account) throws DataException {
String address = account.getAddress();
BigDecimal amount = BigDecimal.ZERO.setScale(8);

View File

@ -18,6 +18,7 @@ public interface Repository extends AutoCloseable {
public void discardChanges() throws DataException;
@Override
public void close() throws DataException;
public void rebuild() throws DataException;

View File

@ -17,9 +17,9 @@ public class HSQLDBAccountRepository implements AccountRepository {
this.repository = repository;
}
@Override
public AccountData getAccount(String address) throws DataException {
try {
ResultSet resultSet = this.repository.checkedExecute("SELECT reference FROM Accounts WHERE account = ?", address);
try (ResultSet resultSet = this.repository.checkedExecute("SELECT reference FROM Accounts WHERE account = ?", address)) {
if (resultSet == null)
return null;
@ -29,6 +29,7 @@ public class HSQLDBAccountRepository implements AccountRepository {
}
}
@Override
public void save(AccountData accountData) throws DataException {
HSQLDBSaver saveHelper = new HSQLDBSaver("Accounts");
saveHelper.bind("account", accountData.getAddress()).bind("reference", accountData.getReference());
@ -40,9 +41,9 @@ public class HSQLDBAccountRepository implements AccountRepository {
}
}
@Override
public AccountBalanceData getBalance(String address, long assetId) throws DataException {
try {
ResultSet resultSet = this.repository.checkedExecute("SELECT balance FROM AccountBalances WHERE account = ? and asset_id = ?", address, assetId);
try (ResultSet resultSet = this.repository.checkedExecute("SELECT balance FROM AccountBalances WHERE account = ? and asset_id = ?", address, assetId)) {
if (resultSet == null)
return null;
@ -54,6 +55,7 @@ public class HSQLDBAccountRepository implements AccountRepository {
}
}
@Override
public void save(AccountBalanceData accountBalanceData) throws DataException {
HSQLDBSaver saveHelper = new HSQLDBSaver("AccountBalances");
saveHelper.bind("account", accountBalanceData.getAddress()).bind("asset_id", accountBalanceData.getAssetId()).bind("balance",
@ -66,6 +68,7 @@ public class HSQLDBAccountRepository implements AccountRepository {
}
}
@Override
public void delete(String address, long assetId) throws DataException {
try {
this.repository.delete("AccountBalances", "account = ? and asset_id = ?", address, assetId);

View File

@ -5,6 +5,7 @@ import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
import data.assets.AssetData;
@ -23,10 +24,10 @@ public class HSQLDBAssetRepository implements AssetRepository {
// Assets
@Override
public AssetData fromAssetId(long assetId) throws DataException {
try {
ResultSet resultSet = this.repository
.checkedExecute("SELECT owner, asset_name, description, quantity, is_divisible, reference FROM Assets WHERE asset_id = ?", assetId);
try (ResultSet resultSet = this.repository
.checkedExecute("SELECT owner, asset_name, description, quantity, is_divisible, reference FROM Assets WHERE asset_id = ?", assetId)) {
if (resultSet == null)
return null;
@ -43,10 +44,10 @@ public class HSQLDBAssetRepository implements AssetRepository {
}
}
@Override
public AssetData fromAssetName(String assetName) throws DataException {
try {
ResultSet resultSet = this.repository
.checkedExecute("SELECT owner, asset_id, description, quantity, is_divisible, reference FROM Assets WHERE asset_name = ?", assetName);
try (ResultSet resultSet = this.repository
.checkedExecute("SELECT owner, asset_id, description, quantity, is_divisible, reference FROM Assets WHERE asset_name = ?", assetName)) {
if (resultSet == null)
return null;
@ -63,6 +64,7 @@ public class HSQLDBAssetRepository implements AssetRepository {
}
}
@Override
public boolean assetExists(long assetId) throws DataException {
try {
return this.repository.exists("Assets", "asset_id = ?", assetId);
@ -71,6 +73,7 @@ public class HSQLDBAssetRepository implements AssetRepository {
}
}
@Override
public boolean assetExists(String assetName) throws DataException {
try {
return this.repository.exists("Assets", "asset_name = ?", assetName);
@ -79,6 +82,7 @@ public class HSQLDBAssetRepository implements AssetRepository {
}
}
@Override
public void save(AssetData assetData) throws DataException {
HSQLDBSaver saveHelper = new HSQLDBSaver("Assets");
@ -89,13 +93,21 @@ public class HSQLDBAssetRepository implements AssetRepository {
try {
saveHelper.execute(this.repository);
if (assetData.getAssetId() == null)
assetData.setAssetId(this.repository.callIdentity());
if (assetData.getAssetId() == null) {
// Fetch new assetId
try (ResultSet resultSet = this.repository.checkedExecute("SELECT asset_id FROM Assets WHERE reference = ?", assetData.getReference())) {
if (resultSet == null)
throw new DataException("Unable to fetch new asset ID from repository");
assetData.setAssetId(resultSet.getLong(1));
}
}
} catch (SQLException e) {
throw new DataException("Unable to save asset into repository", e);
}
}
@Override
public void delete(long assetId) throws DataException {
try {
this.repository.delete("Assets", "asset_id = ?", assetId);
@ -106,11 +118,11 @@ public class HSQLDBAssetRepository implements AssetRepository {
// Orders
@Override
public OrderData fromOrderId(byte[] orderId) throws DataException {
try {
ResultSet resultSet = this.repository.checkedExecute(
"SELECT creator, have_asset_id, want_asset_id, amount, fulfilled, price, ordered, is_closed, is_fulfilled FROM AssetOrders WHERE asset_order_id = ?",
orderId);
try (ResultSet resultSet = this.repository.checkedExecute(
"SELECT creator, have_asset_id, want_asset_id, amount, fulfilled, price, ordered, is_closed, is_fulfilled FROM AssetOrders WHERE asset_order_id = ?",
orderId)) {
if (resultSet == null)
return null;
@ -120,7 +132,7 @@ public class HSQLDBAssetRepository implements AssetRepository {
BigDecimal amount = resultSet.getBigDecimal(4);
BigDecimal fulfilled = resultSet.getBigDecimal(5);
BigDecimal price = resultSet.getBigDecimal(6);
long timestamp = resultSet.getTimestamp(7).getTime();
long timestamp = resultSet.getTimestamp(7, Calendar.getInstance(HSQLDBRepository.UTC)).getTime();
boolean isClosed = resultSet.getBoolean(8);
boolean isFulfilled = resultSet.getBoolean(9);
@ -130,14 +142,14 @@ public class HSQLDBAssetRepository implements AssetRepository {
}
}
@Override
public List<OrderData> getOpenOrders(long haveAssetId, long wantAssetId) throws DataException {
List<OrderData> orders = new ArrayList<OrderData>();
try {
ResultSet resultSet = this.repository.checkedExecute(
"SELECT creator, asset_order_id, amount, fulfilled, price, ordered FROM AssetOrders "
+ "WHERE have_asset_id = ? AND want_asset_id = ? AND is_closed = FALSE AND is_fulfilled = FALSE ORDER BY price ASC",
haveAssetId, wantAssetId);
try (ResultSet resultSet = this.repository.checkedExecute(
"SELECT creator, asset_order_id, amount, fulfilled, price, ordered FROM AssetOrders "
+ "WHERE have_asset_id = ? AND want_asset_id = ? AND is_closed = FALSE AND is_fulfilled = FALSE ORDER BY price ASC",
haveAssetId, wantAssetId)) {
if (resultSet == null)
return orders;
@ -147,7 +159,7 @@ public class HSQLDBAssetRepository implements AssetRepository {
BigDecimal amount = resultSet.getBigDecimal(3);
BigDecimal fulfilled = resultSet.getBigDecimal(4);
BigDecimal price = resultSet.getBigDecimal(5);
long timestamp = resultSet.getTimestamp(6).getTime();
long timestamp = resultSet.getTimestamp(6, Calendar.getInstance(HSQLDBRepository.UTC)).getTime();
boolean isClosed = false;
boolean isFulfilled = false;
@ -162,6 +174,7 @@ public class HSQLDBAssetRepository implements AssetRepository {
}
}
@Override
public void save(OrderData orderData) throws DataException {
HSQLDBSaver saveHelper = new HSQLDBSaver("AssetOrders");
@ -177,6 +190,7 @@ public class HSQLDBAssetRepository implements AssetRepository {
}
}
@Override
public void delete(byte[] orderId) throws DataException {
try {
this.repository.delete("AssetOrders", "asset_order_id = ?", orderId);
@ -187,12 +201,12 @@ public class HSQLDBAssetRepository implements AssetRepository {
// Trades
@Override
public List<TradeData> getOrdersTrades(byte[] initiatingOrderId) 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 target_order_id, amount, price, traded FROM AssetTrades WHERE initiating_order_id = ?", initiatingOrderId)) {
if (resultSet == null)
return trades;
@ -200,7 +214,7 @@ public class HSQLDBAssetRepository implements AssetRepository {
byte[] targetOrderId = resultSet.getBytes(1);
BigDecimal amount = resultSet.getBigDecimal(2);
BigDecimal price = resultSet.getBigDecimal(3);
long timestamp = resultSet.getTimestamp(4).getTime();
long timestamp = resultSet.getTimestamp(4, Calendar.getInstance(HSQLDBRepository.UTC)).getTime();
TradeData trade = new TradeData(initiatingOrderId, targetOrderId, amount, price, timestamp);
trades.add(trade);
@ -212,6 +226,7 @@ public class HSQLDBAssetRepository implements AssetRepository {
}
}
@Override
public void save(TradeData tradeData) throws DataException {
HSQLDBSaver saveHelper = new HSQLDBSaver("AssetTrades");
@ -225,6 +240,7 @@ public class HSQLDBAssetRepository implements AssetRepository {
}
}
@Override
public void delete(TradeData tradeData) throws DataException {
try {
this.repository.delete("AssetTrades", "initiating_order_id = ? AND target_order_id = ? AND amount = ? AND price = ?", tradeData.getInitiator(),

View File

@ -5,6 +5,7 @@ import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
import data.block.BlockData;
@ -25,23 +26,23 @@ public class HSQLDBBlockRepository implements BlockRepository {
this.repository = repository;
}
private BlockData getBlockFromResultSet(ResultSet rs) throws DataException {
if (rs == null)
private BlockData getBlockFromResultSet(ResultSet resultSet) throws DataException {
if (resultSet == null)
return null;
try {
int version = rs.getInt(1);
byte[] reference = rs.getBytes(2);
int transactionCount = rs.getInt(3);
BigDecimal totalFees = rs.getBigDecimal(4);
byte[] transactionsSignature = rs.getBytes(5);
int height = rs.getInt(6);
long timestamp = rs.getTimestamp(7).getTime();
BigDecimal generatingBalance = rs.getBigDecimal(8);
byte[] generatorPublicKey = rs.getBytes(9);
byte[] generatorSignature = rs.getBytes(10);
byte[] atBytes = rs.getBytes(11);
BigDecimal atFees = rs.getBigDecimal(12);
int version = resultSet.getInt(1);
byte[] reference = resultSet.getBytes(2);
int transactionCount = resultSet.getInt(3);
BigDecimal totalFees = resultSet.getBigDecimal(4);
byte[] transactionsSignature = resultSet.getBytes(5);
int height = resultSet.getInt(6);
long timestamp = resultSet.getTimestamp(7, Calendar.getInstance(HSQLDBRepository.UTC)).getTime();
BigDecimal generatingBalance = resultSet.getBigDecimal(8);
byte[] generatorPublicKey = resultSet.getBytes(9);
byte[] generatorSignature = resultSet.getBytes(10);
byte[] atBytes = resultSet.getBytes(11);
BigDecimal atFees = resultSet.getBigDecimal(12);
return new BlockData(version, reference, transactionCount, totalFees, transactionsSignature, height, timestamp, generatingBalance,
generatorPublicKey, generatorSignature, atBytes, atFees);
@ -50,76 +51,77 @@ public class HSQLDBBlockRepository implements BlockRepository {
}
}
@Override
public BlockData fromSignature(byte[] signature) throws DataException {
try {
ResultSet rs = this.repository.checkedExecute("SELECT " + BLOCK_DB_COLUMNS + " FROM Blocks WHERE signature = ?", signature);
return getBlockFromResultSet(rs);
try (ResultSet resultSet = this.repository.checkedExecute("SELECT " + BLOCK_DB_COLUMNS + " FROM Blocks WHERE signature = ?", signature)) {
return getBlockFromResultSet(resultSet);
} catch (SQLException e) {
throw new DataException("Error loading data from DB", e);
}
}
@Override
public BlockData fromReference(byte[] reference) throws DataException {
try {
ResultSet rs = this.repository.checkedExecute("SELECT " + BLOCK_DB_COLUMNS + " FROM Blocks WHERE height = ?", reference);
return getBlockFromResultSet(rs);
try (ResultSet resultSet = this.repository.checkedExecute("SELECT " + BLOCK_DB_COLUMNS + " FROM Blocks WHERE height = ?", reference)) {
return getBlockFromResultSet(resultSet);
} catch (SQLException e) {
throw new DataException("Error loading data from DB", e);
}
}
@Override
public BlockData fromHeight(int height) throws DataException {
try {
ResultSet rs = this.repository.checkedExecute("SELECT " + BLOCK_DB_COLUMNS + " FROM Blocks WHERE height = ?", height);
return getBlockFromResultSet(rs);
try (ResultSet resultSet = this.repository.checkedExecute("SELECT " + BLOCK_DB_COLUMNS + " FROM Blocks WHERE height = ?", height)) {
return getBlockFromResultSet(resultSet);
} catch (SQLException e) {
throw new DataException("Error loading data from DB", e);
}
}
@Override
public int getHeightFromSignature(byte[] signature) throws DataException {
try {
ResultSet rs = this.repository.checkedExecute("SELECT height FROM Blocks WHERE signature = ?", signature);
if (rs == null)
try (ResultSet resultSet = this.repository.checkedExecute("SELECT height FROM Blocks WHERE signature = ?", signature)) {
if (resultSet == null)
return 0;
return rs.getInt(1);
return resultSet.getInt(1);
} catch (SQLException e) {
throw new DataException("Error obtaining block height from repository", e);
}
}
@Override
public int getBlockchainHeight() throws DataException {
try {
ResultSet rs = this.repository.checkedExecute("SELECT MAX(height) FROM Blocks");
if (rs == null)
try (ResultSet resultSet = this.repository.checkedExecute("SELECT MAX(height) FROM Blocks")) {
if (resultSet == null)
return 0;
return rs.getInt(1);
return resultSet.getInt(1);
} catch (SQLException e) {
throw new DataException("Error obtaining blockchain height from repository", e);
}
}
@Override
public BlockData getLastBlock() throws DataException {
return fromHeight(getBlockchainHeight());
}
@Override
public List<TransactionData> getTransactionsFromSignature(byte[] signature) throws DataException {
List<TransactionData> transactions = new ArrayList<TransactionData>();
try {
ResultSet rs = this.repository.checkedExecute("SELECT transaction_signature FROM BlockTransactions WHERE block_signature = ?", signature);
if (rs == null)
try (ResultSet resultSet = this.repository.checkedExecute("SELECT transaction_signature FROM BlockTransactions WHERE block_signature = ?", signature)) {
if (resultSet == null)
return transactions; // No transactions in this block
TransactionRepository transactionRepo = this.repository.getTransactionRepository();
// NB: do-while loop because .checkedExecute() implicitly calls ResultSet.next() for us
do {
byte[] transactionSignature = rs.getBytes(1);
byte[] transactionSignature = resultSet.getBytes(1);
transactions.add(transactionRepo.fromSignature(transactionSignature));
} while (rs.next());
} while (resultSet.next());
} catch (SQLException e) {
throw new DataException(e);
}
@ -127,6 +129,7 @@ public class HSQLDBBlockRepository implements BlockRepository {
return transactions;
}
@Override
public void save(BlockData blockData) throws DataException {
HSQLDBSaver saveHelper = new HSQLDBSaver("Blocks");
@ -144,6 +147,7 @@ public class HSQLDBBlockRepository implements BlockRepository {
}
}
@Override
public void delete(BlockData blockData) throws DataException {
try {
this.repository.delete("Blocks", "signature = ?", blockData.getSignature());
@ -152,6 +156,7 @@ public class HSQLDBBlockRepository implements BlockRepository {
}
}
@Override
public void save(BlockTransactionData blockTransactionData) throws DataException {
HSQLDBSaver saveHelper = new HSQLDBSaver("BlockTransactions");
saveHelper.bind("block_signature", blockTransactionData.getBlockSignature()).bind("sequence", blockTransactionData.getSequence())
@ -164,6 +169,7 @@ public class HSQLDBBlockRepository implements BlockRepository {
}
}
@Override
public void delete(BlockTransactionData blockTransactionData) throws DataException {
try {
this.repository.delete("BlockTransactions", "block_signature = ? AND sequence = ? AND transaction_signature = ?",

View File

@ -36,14 +36,12 @@ public class HSQLDBDatabaseUpdates {
private static int fetchDatabaseVersion(Connection connection) throws SQLException {
int databaseVersion = 0;
try {
Statement stmt = connection.createStatement();
if (stmt.execute("SELECT version FROM DatabaseInfo")) {
ResultSet rs = stmt.getResultSet();
if (rs.next())
databaseVersion = rs.getInt(1);
}
try (Statement stmt = connection.createStatement()) {
if (stmt.execute("SELECT version FROM DatabaseInfo"))
try (ResultSet resultSet = stmt.getResultSet()) {
if (resultSet.next())
databaseVersion = resultSet.getInt(1);
}
} catch (SQLException e) {
// empty database
}
@ -60,292 +58,298 @@ public class HSQLDBDatabaseUpdates {
private static boolean databaseUpdating(Connection connection) throws SQLException {
int databaseVersion = fetchDatabaseVersion(connection);
Statement stmt = connection.createStatement();
try (Statement stmt = connection.createStatement()) {
/*
* Try not to add too many constraints as much of these checks will be performed during transaction validation. Also some constraints might be too harsh
* on competing unconfirmed transactions.
*
* Only really add "ON DELETE CASCADE" to sub-tables that store type-specific data. For example on sub-types of Transactions like PaymentTransactions. A
* counterexample would be adding "ON DELETE CASCADE" to Assets using Assets' "reference" as a foreign key referring to Transactions' "signature". We
* want to database to automatically delete complete transaction data (Transactions row and corresponding PaymentTransactions row), but leave deleting
* less related table rows (Assets) to the Java logic.
*/
/*
* Try not to add too many constraints as much of these checks will be performed during transaction validation. Also some constraints might be too
* harsh on competing unconfirmed transactions.
*
* Only really add "ON DELETE CASCADE" to sub-tables that store type-specific data. For example on sub-types of Transactions like
* PaymentTransactions. A counterexample would be adding "ON DELETE CASCADE" to Assets using Assets' "reference" as a foreign key referring to
* Transactions' "signature". We want to database to automatically delete complete transaction data (Transactions row and corresponding
* PaymentTransactions row), but leave deleting less related table rows (Assets) to the Java logic.
*/
switch (databaseVersion) {
case 0:
// create from new
stmt.execute("SET DATABASE DEFAULT TABLE TYPE CACHED");
stmt.execute("SET DATABASE COLLATION SQL_TEXT NO PAD");
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");
stmt.execute("CREATE TABLE DatabaseInfo ( version INTEGER NOT NULL )");
stmt.execute("INSERT INTO DatabaseInfo VALUES ( 0 )");
stmt.execute("CREATE TYPE BlockSignature AS VARBINARY(128)");
stmt.execute("CREATE TYPE Signature AS VARBINARY(64)");
stmt.execute("CREATE TYPE QoraAddress AS VARCHAR(36)");
stmt.execute("CREATE TYPE QoraPublicKey AS VARBINARY(32)");
stmt.execute("CREATE TYPE QoraAmount AS DECIMAL(19, 8)");
stmt.execute("CREATE TYPE RegisteredName AS VARCHAR(400) COLLATE SQL_TEXT_NO_PAD");
stmt.execute("CREATE TYPE NameData AS VARCHAR(4000)");
stmt.execute("CREATE TYPE PollName AS VARCHAR(400) COLLATE SQL_TEXT_NO_PAD");
stmt.execute("CREATE TYPE PollOption AS VARCHAR(400) COLLATE SQL_TEXT_UCC_NO_PAD");
stmt.execute("CREATE TYPE PollOptionIndex AS INTEGER");
stmt.execute("CREATE TYPE DataHash AS VARBINARY(32)");
stmt.execute("CREATE TYPE AssetID AS BIGINT");
stmt.execute("CREATE TYPE AssetName AS VARCHAR(400) COLLATE SQL_TEXT_NO_PAD");
stmt.execute("CREATE TYPE AssetOrderID AS VARBINARY(64)");
stmt.execute("CREATE TYPE ATName AS VARCHAR(200) COLLATE SQL_TEXT_UCC_NO_PAD");
stmt.execute("CREATE TYPE ATType AS VARCHAR(200) COLLATE SQL_TEXT_UCC_NO_PAD");
break;
switch (databaseVersion) {
case 0:
// create from new
stmt.execute("SET DATABASE DEFAULT TABLE TYPE CACHED");
stmt.execute("SET DATABASE COLLATION SQL_TEXT NO PAD");
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");
stmt.execute("CREATE TABLE DatabaseInfo ( version INTEGER NOT NULL )");
stmt.execute("INSERT INTO DatabaseInfo VALUES ( 0 )");
stmt.execute("CREATE TYPE BlockSignature AS VARBINARY(128)");
stmt.execute("CREATE TYPE Signature AS VARBINARY(64)");
stmt.execute("CREATE TYPE QoraAddress AS VARCHAR(36)");
stmt.execute("CREATE TYPE QoraPublicKey AS VARBINARY(32)");
stmt.execute("CREATE TYPE QoraAmount AS DECIMAL(19, 8)");
stmt.execute("CREATE TYPE RegisteredName AS VARCHAR(400) COLLATE SQL_TEXT_NO_PAD");
stmt.execute("CREATE TYPE NameData AS VARCHAR(4000)");
stmt.execute("CREATE TYPE PollName AS VARCHAR(400) COLLATE SQL_TEXT_NO_PAD");
stmt.execute("CREATE TYPE PollOption AS VARCHAR(400) COLLATE SQL_TEXT_UCC_NO_PAD");
stmt.execute("CREATE TYPE PollOptionIndex AS INTEGER");
stmt.execute("CREATE TYPE DataHash AS VARBINARY(32)");
stmt.execute("CREATE TYPE AssetID AS BIGINT");
stmt.execute("CREATE TYPE AssetName AS VARCHAR(400) COLLATE SQL_TEXT_NO_PAD");
stmt.execute("CREATE TYPE AssetOrderID AS VARBINARY(64)");
stmt.execute("CREATE TYPE ATName AS VARCHAR(200) COLLATE SQL_TEXT_UCC_NO_PAD");
stmt.execute("CREATE TYPE ATType AS VARCHAR(200) COLLATE SQL_TEXT_UCC_NO_PAD");
break;
case 1:
// Blocks
stmt.execute("CREATE TABLE Blocks (signature BlockSignature PRIMARY KEY, version TINYINT NOT NULL, reference BlockSignature, "
+ "transaction_count INTEGER NOT NULL, total_fees QoraAmount NOT NULL, transactions_signature Signature NOT NULL, "
+ "height INTEGER NOT NULL, generation TIMESTAMP NOT NULL, generating_balance QoraAmount NOT NULL, "
+ "generator QoraPublicKey NOT NULL, generator_signature Signature NOT NULL, AT_data VARBINARY(20000), AT_fees QoraAmount)");
// For finding blocks by height.
stmt.execute("CREATE INDEX BlockHeightIndex ON Blocks (height)");
// For finding blocks by the account that generated them.
stmt.execute("CREATE INDEX BlockGeneratorIndex ON Blocks (generator)");
// For finding blocks by reference, e.g. child blocks.
stmt.execute("CREATE INDEX BlockReferenceIndex ON Blocks (reference)");
// Use a separate table space as this table will be very large.
stmt.execute("SET TABLE Blocks NEW SPACE");
break;
case 1:
// Blocks
stmt.execute("CREATE TABLE Blocks (signature BlockSignature PRIMARY KEY, version TINYINT NOT NULL, reference BlockSignature, "
+ "transaction_count INTEGER NOT NULL, total_fees QoraAmount NOT NULL, transactions_signature Signature NOT NULL, "
+ "height INTEGER NOT NULL, generation TIMESTAMP WITH TIME ZONE NOT NULL, generating_balance QoraAmount NOT NULL, "
+ "generator QoraPublicKey NOT NULL, generator_signature Signature NOT NULL, AT_data VARBINARY(20000), AT_fees QoraAmount)");
// For finding blocks by height.
stmt.execute("CREATE INDEX BlockHeightIndex ON Blocks (height)");
// For finding blocks by the account that generated them.
stmt.execute("CREATE INDEX BlockGeneratorIndex ON Blocks (generator)");
// For finding blocks by reference, e.g. child blocks.
stmt.execute("CREATE INDEX BlockReferenceIndex ON Blocks (reference)");
// Use a separate table space as this table will be very large.
stmt.execute("SET TABLE Blocks NEW SPACE");
break;
case 2:
// Generic transactions (null reference, creator and milestone_block for genesis transactions)
stmt.execute("CREATE TABLE Transactions (signature Signature PRIMARY KEY, reference Signature, type TINYINT NOT NULL, "
+ "creator QoraPublicKey, creation TIMESTAMP NOT NULL, fee QoraAmount NOT NULL, milestone_block BlockSignature)");
// For finding transactions by transaction type.
stmt.execute("CREATE INDEX TransactionTypeIndex ON Transactions (type)");
// For finding transactions using timestamp.
stmt.execute("CREATE INDEX TransactionCreationIndex ON Transactions (creation)");
// For when a user wants to lookup ALL transactions they have created, regardless of type.
stmt.execute("CREATE INDEX TransactionCreatorIndex ON Transactions (creator)");
// For finding transactions by reference, e.g. child transactions.
stmt.execute("CREATE INDEX TransactionReferenceIndex ON Transactions (reference)");
// Use a separate table space as this table will be very large.
stmt.execute("SET TABLE Transactions NEW SPACE");
case 2:
// Generic transactions (null reference, creator and milestone_block for genesis transactions)
stmt.execute("CREATE TABLE Transactions (signature Signature PRIMARY KEY, reference Signature, type TINYINT NOT NULL, "
+ "creator QoraPublicKey, creation TIMESTAMP WITH TIME ZONE NOT NULL, fee QoraAmount NOT NULL, milestone_block BlockSignature)");
// For finding transactions by transaction type.
stmt.execute("CREATE INDEX TransactionTypeIndex ON Transactions (type)");
// For finding transactions using timestamp.
stmt.execute("CREATE INDEX TransactionCreationIndex ON Transactions (creation)");
// For when a user wants to lookup ALL transactions they have created, regardless of type.
stmt.execute("CREATE INDEX TransactionCreatorIndex ON Transactions (creator)");
// For finding transactions by reference, e.g. child transactions.
stmt.execute("CREATE INDEX TransactionReferenceIndex ON Transactions (reference)");
// Use a separate table space as this table will be very large.
stmt.execute("SET TABLE Transactions NEW SPACE");
// Transaction-Block mapping ("signature" is unique as a transaction cannot be included in more than one block)
stmt.execute("CREATE TABLE BlockTransactions (block_signature BlockSignature, sequence INTEGER, transaction_signature Signature, "
+ "PRIMARY KEY (block_signature, sequence), FOREIGN KEY (transaction_signature) REFERENCES Transactions (signature) ON DELETE CASCADE, "
+ "FOREIGN KEY (block_signature) REFERENCES Blocks (signature) ON DELETE CASCADE)");
// Use a separate table space as this table will be very large.
stmt.execute("SET TABLE BlockTransactions NEW SPACE");
// Transaction-Block mapping ("signature" is unique as a transaction cannot be included in more than one block)
stmt.execute("CREATE TABLE BlockTransactions (block_signature BlockSignature, sequence INTEGER, transaction_signature Signature, "
+ "PRIMARY KEY (block_signature, sequence), FOREIGN KEY (transaction_signature) REFERENCES Transactions (signature) ON DELETE CASCADE, "
+ "FOREIGN KEY (block_signature) REFERENCES Blocks (signature) ON DELETE CASCADE)");
// Use a separate table space as this table will be very large.
stmt.execute("SET TABLE BlockTransactions NEW SPACE");
// Unconfirmed transactions
// XXX Do we need this? If a transaction doesn't have a corresponding BlockTransactions record then it's unconfirmed?
stmt.execute("CREATE TABLE UnconfirmedTransactions (signature Signature PRIMARY KEY, expiry TIMESTAMP NOT NULL)");
stmt.execute("CREATE INDEX UnconfirmedTransactionExpiryIndex ON UnconfirmedTransactions (expiry)");
// Unconfirmed transactions
// XXX Do we need this? If a transaction doesn't have a corresponding BlockTransactions record then it's unconfirmed?
stmt.execute("CREATE TABLE UnconfirmedTransactions (signature Signature PRIMARY KEY, expiry TIMESTAMP WITH TIME ZONE NOT NULL)");
stmt.execute("CREATE INDEX UnconfirmedTransactionExpiryIndex ON UnconfirmedTransactions (expiry)");
// Transaction recipients
stmt.execute("CREATE TABLE TransactionRecipients (signature Signature, recipient 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");
break;
// Transaction recipients
stmt.execute("CREATE TABLE TransactionRecipients (signature Signature, recipient 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");
break;
case 3:
// Genesis Transactions
stmt.execute("CREATE TABLE GenesisTransactions (signature Signature, recipient QoraAddress NOT NULL, "
+ "amount QoraAmount NOT NULL, PRIMARY KEY (signature), "
+ "FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
break;
case 3:
// Genesis Transactions
stmt.execute("CREATE TABLE GenesisTransactions (signature Signature, recipient QoraAddress NOT NULL, "
+ "amount QoraAmount NOT NULL, PRIMARY KEY (signature), "
+ "FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
break;
case 4:
// Payment Transactions
stmt.execute("CREATE TABLE PaymentTransactions (signature Signature, sender QoraPublicKey NOT NULL, recipient QoraAddress NOT NULL, "
+ "amount QoraAmount NOT NULL, PRIMARY KEY (signature), "
+ "FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
break;
case 4:
// Payment Transactions
stmt.execute("CREATE TABLE PaymentTransactions (signature Signature, sender QoraPublicKey NOT NULL, recipient QoraAddress NOT NULL, "
+ "amount QoraAmount NOT NULL, PRIMARY KEY (signature), "
+ "FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
break;
case 5:
// Register Name Transactions
stmt.execute("CREATE TABLE RegisterNameTransactions (signature Signature, registrant QoraPublicKey NOT NULL, name RegisteredName NOT NULL, "
+ "owner QoraAddress NOT NULL, data NameData NOT NULL, "
+ "PRIMARY KEY (signature), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
break;
case 5:
// Register Name Transactions
stmt.execute("CREATE TABLE RegisterNameTransactions (signature Signature, registrant QoraPublicKey NOT NULL, name RegisteredName NOT NULL, "
+ "owner QoraAddress NOT NULL, data NameData NOT NULL, "
+ "PRIMARY KEY (signature), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
break;
case 6:
// Update Name Transactions
stmt.execute("CREATE TABLE UpdateNameTransactions (signature Signature, owner QoraPublicKey NOT NULL, name RegisteredName NOT NULL, "
+ "new_owner QoraAddress NOT NULL, new_data NameData NOT NULL, name_reference Signature NOT NULL, "
+ "PRIMARY KEY (signature), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
break;
case 6:
// Update Name Transactions
stmt.execute("CREATE TABLE UpdateNameTransactions (signature Signature, owner QoraPublicKey NOT NULL, name RegisteredName NOT NULL, "
+ "new_owner QoraAddress NOT NULL, new_data NameData NOT NULL, name_reference Signature NOT NULL, "
+ "PRIMARY KEY (signature), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
break;
case 7:
// Sell Name Transactions
stmt.execute("CREATE TABLE SellNameTransactions (signature Signature, owner QoraPublicKey NOT NULL, name RegisteredName NOT NULL, "
+ "amount QoraAmount NOT NULL, PRIMARY KEY (signature), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
break;
case 7:
// Sell Name Transactions
stmt.execute("CREATE TABLE SellNameTransactions (signature Signature, owner QoraPublicKey NOT NULL, name RegisteredName NOT NULL, "
+ "amount QoraAmount NOT NULL, PRIMARY KEY (signature), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
break;
case 8:
// Cancel Sell Name Transactions
stmt.execute("CREATE TABLE CancelSellNameTransactions (signature Signature, owner QoraPublicKey NOT NULL, name RegisteredName NOT NULL, "
+ "PRIMARY KEY (signature), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
break;
case 8:
// Cancel Sell Name Transactions
stmt.execute("CREATE TABLE CancelSellNameTransactions (signature Signature, owner QoraPublicKey NOT NULL, name RegisteredName NOT NULL, "
+ "PRIMARY KEY (signature), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
break;
case 9:
// Buy Name Transactions
stmt.execute("CREATE TABLE BuyNameTransactions (signature Signature, buyer QoraPublicKey NOT NULL, name RegisteredName NOT NULL, "
+ "seller QoraAddress NOT NULL, amount QoraAmount NOT NULL, name_reference Signature NOT NULL, "
+ "PRIMARY KEY (signature), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
break;
case 9:
// Buy Name Transactions
stmt.execute("CREATE TABLE BuyNameTransactions (signature Signature, buyer QoraPublicKey NOT NULL, name RegisteredName NOT NULL, "
+ "seller QoraAddress NOT NULL, amount QoraAmount NOT NULL, name_reference Signature NOT NULL, "
+ "PRIMARY KEY (signature), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
break;
case 10:
// Create Poll Transactions
stmt.execute("CREATE TABLE CreatePollTransactions (signature Signature, creator QoraPublicKey NOT NULL, owner QoraAddress NOT NULL, "
+ "poll_name PollName NOT NULL, description VARCHAR(4000) NOT NULL, "
+ "PRIMARY KEY (signature), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
// Poll options. NB: option is implicitly NON NULL and UNIQUE due to being part of compound primary key
stmt.execute("CREATE TABLE CreatePollTransactionOptions (signature Signature, option_index TINYINT NOT NULL, option_name PollOption, "
+ "PRIMARY KEY (signature, option_index), FOREIGN KEY (signature) REFERENCES CreatePollTransactions (signature) ON DELETE CASCADE)");
// For the future: add flag to polls to allow one or multiple votes per voter
break;
case 10:
// Create Poll Transactions
stmt.execute("CREATE TABLE CreatePollTransactions (signature Signature, creator QoraPublicKey NOT NULL, owner QoraAddress NOT NULL, "
+ "poll_name PollName NOT NULL, description VARCHAR(4000) NOT NULL, "
+ "PRIMARY KEY (signature), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
// Poll options. NB: option is implicitly NON NULL and UNIQUE due to being part of compound primary key
stmt.execute("CREATE TABLE CreatePollTransactionOptions (signature Signature, option_index TINYINT NOT NULL, option_name PollOption, "
+ "PRIMARY KEY (signature, option_index), FOREIGN KEY (signature) REFERENCES CreatePollTransactions (signature) ON DELETE CASCADE)");
// For the future: add flag to polls to allow one or multiple votes per voter
break;
case 11:
// Vote On Poll Transactions
stmt.execute("CREATE TABLE VoteOnPollTransactions (signature Signature, voter QoraPublicKey NOT NULL, poll_name PollName NOT NULL, "
+ "option_index PollOptionIndex NOT NULL, previous_option_index PollOptionIndex, "
+ "PRIMARY KEY (signature), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
break;
case 11:
// Vote On Poll Transactions
stmt.execute("CREATE TABLE VoteOnPollTransactions (signature Signature, voter QoraPublicKey NOT NULL, poll_name PollName NOT NULL, "
+ "option_index PollOptionIndex NOT NULL, previous_option_index PollOptionIndex, "
+ "PRIMARY KEY (signature), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
break;
case 12:
// Arbitrary/Multi-payment Transaction Payments
stmt.execute("CREATE TABLE SharedTransactionPayments (signature Signature, recipient QoraAddress NOT NULL, "
+ "amount QoraAmount NOT NULL, asset_id AssetID NOT NULL, "
+ "PRIMARY KEY (signature, recipient, asset_id), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
break;
case 12:
// Arbitrary/Multi-payment Transaction Payments
stmt.execute("CREATE TABLE SharedTransactionPayments (signature Signature, recipient QoraAddress NOT NULL, "
+ "amount QoraAmount NOT NULL, asset_id AssetID NOT NULL, "
+ "PRIMARY KEY (signature, recipient, asset_id), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
break;
case 13:
// Arbitrary Transactions
stmt.execute("CREATE TABLE ArbitraryTransactions (signature Signature, sender QoraPublicKey NOT NULL, version TINYINT NOT NULL, "
+ "service TINYINT NOT NULL, data_hash DataHash NOT NULL, "
+ "PRIMARY KEY (signature), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
// NB: Actual data payload stored elsewhere
// For the future: data payload should be encrypted, at the very least with transaction's reference as the seed for the encryption key
break;
case 13:
// Arbitrary Transactions
stmt.execute("CREATE TABLE ArbitraryTransactions (signature Signature, sender QoraPublicKey NOT NULL, version TINYINT NOT NULL, "
+ "service TINYINT NOT NULL, data_hash DataHash NOT NULL, "
+ "PRIMARY KEY (signature), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
// NB: Actual data payload stored elsewhere
// For the future: data payload should be encrypted, at the very least with transaction's reference as the seed for the encryption key
break;
case 14:
// Issue Asset Transactions
stmt.execute(
"CREATE TABLE IssueAssetTransactions (signature Signature, issuer QoraPublicKey NOT NULL, owner QoraAddress NOT NULL, asset_name AssetName NOT NULL, "
+ "description VARCHAR(4000) NOT NULL, quantity BIGINT NOT NULL, is_divisible BOOLEAN NOT NULL, asset_id AssetID, "
+ "PRIMARY KEY (signature), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
// For the future: maybe convert quantity from BIGINT to QoraAmount, regardless of divisibility
break;
case 14:
// Issue Asset Transactions
stmt.execute(
"CREATE TABLE IssueAssetTransactions (signature Signature, issuer QoraPublicKey NOT NULL, owner QoraAddress NOT NULL, asset_name AssetName NOT NULL, "
+ "description VARCHAR(4000) NOT NULL, quantity BIGINT NOT NULL, is_divisible BOOLEAN NOT NULL, asset_id AssetID, "
+ "PRIMARY KEY (signature), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
// For the future: maybe convert quantity from BIGINT to QoraAmount, regardless of divisibility
break;
case 15:
// Transfer Asset Transactions
stmt.execute("CREATE TABLE TransferAssetTransactions (signature Signature, sender QoraPublicKey NOT NULL, recipient QoraAddress NOT NULL, "
+ "asset_id AssetID NOT NULL, amount QoraAmount NOT NULL,"
+ "PRIMARY KEY (signature), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
break;
case 15:
// Transfer Asset Transactions
stmt.execute("CREATE TABLE TransferAssetTransactions (signature Signature, sender QoraPublicKey NOT NULL, recipient QoraAddress NOT NULL, "
+ "asset_id AssetID NOT NULL, amount QoraAmount NOT NULL,"
+ "PRIMARY KEY (signature), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
break;
case 16:
// Create Asset Order Transactions
stmt.execute("CREATE TABLE CreateAssetOrderTransactions (signature Signature, creator QoraPublicKey NOT NULL, "
+ "have_asset_id AssetID NOT NULL, amount QoraAmount NOT NULL, want_asset_id AssetID NOT NULL, price QoraAmount NOT NULL, "
+ "PRIMARY KEY (signature), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
break;
case 16:
// Create Asset Order Transactions
stmt.execute("CREATE TABLE CreateAssetOrderTransactions (signature Signature, creator QoraPublicKey NOT NULL, "
+ "have_asset_id AssetID NOT NULL, amount QoraAmount NOT NULL, want_asset_id AssetID NOT NULL, price QoraAmount NOT NULL, "
+ "PRIMARY KEY (signature), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
break;
case 17:
// Cancel Asset Order Transactions
stmt.execute("CREATE TABLE CancelAssetOrderTransactions (signature Signature, creator QoraPublicKey NOT NULL, "
+ "asset_order_id AssetOrderID NOT NULL, "
+ "PRIMARY KEY (signature), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
break;
case 17:
// Cancel Asset Order Transactions
stmt.execute("CREATE TABLE CancelAssetOrderTransactions (signature Signature, creator QoraPublicKey NOT NULL, "
+ "asset_order_id AssetOrderID NOT NULL, "
+ "PRIMARY KEY (signature), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
break;
case 18:
// Multi-payment Transactions
stmt.execute("CREATE TABLE MultiPaymentTransactions (signature Signature, sender QoraPublicKey NOT NULL, "
+ "PRIMARY KEY (signature), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
break;
case 18:
// Multi-payment Transactions
stmt.execute("CREATE TABLE MultiPaymentTransactions (signature Signature, sender QoraPublicKey NOT NULL, "
+ "PRIMARY KEY (signature), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
break;
case 19:
// Deploy CIYAM AT Transactions
stmt.execute("CREATE TABLE DeployATTransactions (signature Signature, creator QoraPublicKey NOT NULL, AT_name ATName NOT NULL, "
+ "description VARCHAR(2000) NOT NULL, AT_type ATType NOT NULL, AT_tags VARCHAR(200) NOT NULL, "
+ "creation_bytes VARBINARY(100000) NOT NULL, amount QoraAmount NOT NULL, "
+ "PRIMARY KEY (signature), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
break;
case 19:
// Deploy CIYAM AT Transactions
stmt.execute("CREATE TABLE DeployATTransactions (signature Signature, creator QoraPublicKey NOT NULL, AT_name ATName NOT NULL, "
+ "description VARCHAR(2000) NOT NULL, AT_type ATType NOT NULL, AT_tags VARCHAR(200) NOT NULL, "
+ "creation_bytes VARBINARY(100000) NOT NULL, amount QoraAmount NOT NULL, "
+ "PRIMARY KEY (signature), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
break;
case 20:
// Message Transactions
stmt.execute(
"CREATE TABLE MessageTransactions (signature Signature, version TINYINT NOT NULL, sender QoraPublicKey NOT NULL, recipient QoraAddress NOT NULL, "
+ "is_text BOOLEAN NOT NULL, is_encrypted BOOLEAN NOT NULL, amount QoraAmount NOT NULL, asset_id AssetID NOT NULL, data VARBINARY(4000) NOT NULL, "
+ "PRIMARY KEY (signature), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
break;
case 20:
// Message Transactions
stmt.execute(
"CREATE TABLE MessageTransactions (signature Signature, version TINYINT NOT NULL, sender QoraPublicKey NOT NULL, recipient QoraAddress NOT NULL, "
+ "is_text BOOLEAN NOT NULL, is_encrypted BOOLEAN NOT NULL, amount QoraAmount NOT NULL, asset_id AssetID NOT NULL, data VARBINARY(4000) NOT NULL, "
+ "PRIMARY KEY (signature), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
break;
case 21:
// Assets (including QORA coin itself)
stmt.execute(
"CREATE TABLE Assets (asset_id AssetID IDENTITY, owner QoraAddress NOT NULL, asset_name AssetName NOT NULL, description VARCHAR(4000) NOT NULL, "
+ "quantity BIGINT NOT NULL, is_divisible BOOLEAN NOT NULL, reference Signature NOT NULL)");
// For when a user wants to lookup an asset by name
stmt.execute("CREATE INDEX AssetNameIndex on Assets (asset_name)");
break;
case 21:
// Assets (including QORA coin itself)
stmt.execute("CREATE TABLE Assets (asset_id AssetID, owner QoraAddress NOT NULL, "
+ "asset_name AssetName NOT NULL, description VARCHAR(4000) NOT NULL, "
+ "quantity BIGINT NOT NULL, is_divisible BOOLEAN NOT NULL, reference Signature NOT NULL, PRIMARY KEY (asset_id))");
// We need a corresponding trigger to make sure new asset_id values are assigned sequentially
stmt.execute(
"CREATE TRIGGER Asset_ID_Trigger BEFORE INSERT ON Assets REFERENCING NEW ROW AS new_row FOR EACH ROW WHEN (new_row.asset_id IS NULL) "
+ "SET new_row.asset_id = (SELECT IFNULL(MAX(asset_id) + 1, 0) FROM Assets)");
// For when a user wants to lookup an asset by name
stmt.execute("CREATE INDEX AssetNameIndex on Assets (asset_name)");
break;
case 22:
// Accounts
stmt.execute("CREATE TABLE Accounts (account QoraAddress, reference Signature, PRIMARY KEY (account))");
stmt.execute(
"CREATE TABLE AccountBalances (account QoraAddress, asset_id AssetID, balance QoraAmount NOT NULL, PRIMARY KEY (account, asset_id))");
break;
case 22:
// Accounts
stmt.execute("CREATE TABLE Accounts (account QoraAddress, reference Signature, PRIMARY KEY (account))");
stmt.execute("CREATE TABLE AccountBalances (account QoraAddress, asset_id AssetID, balance QoraAmount NOT NULL, "
+ "PRIMARY KEY (account, asset_id))");
break;
case 23:
// Asset Orders
stmt.execute(
"CREATE TABLE AssetOrders (asset_order_id AssetOrderID, creator QoraPublicKey NOT NULL, have_asset_id AssetID NOT NULL, want_asset_id AssetID NOT NULL, "
+ "amount QoraAmount NOT NULL, fulfilled QoraAmount NOT NULL, price QoraAmount NOT NULL, "
+ "ordered TIMESTAMP NOT NULL, is_closed BOOLEAN NOT NULL, is_fulfilled BOOLEAN NOT NULL, " + "PRIMARY KEY (asset_order_id))");
// For quick matching of orders. is_closed are is_fulfilled included so inactive orders can be filtered out.
stmt.execute("CREATE INDEX AssetOrderMatchingIndex on AssetOrders (have_asset_id, want_asset_id, is_closed, is_fulfilled)");
// For when a user wants to look up their current/historic orders. is_closed included so user can filter by active/inactive orders.
stmt.execute("CREATE INDEX AssetOrderCreatorIndex on AssetOrders (creator, is_closed)");
break;
case 23:
// Asset Orders
stmt.execute(
"CREATE TABLE AssetOrders (asset_order_id AssetOrderID, creator QoraPublicKey NOT NULL, have_asset_id AssetID NOT NULL, want_asset_id AssetID NOT NULL, "
+ "amount QoraAmount NOT NULL, fulfilled QoraAmount NOT NULL, price QoraAmount NOT NULL, "
+ "ordered TIMESTAMP WITH TIME ZONE NOT NULL, is_closed BOOLEAN NOT NULL, is_fulfilled BOOLEAN NOT NULL, "
+ "PRIMARY KEY (asset_order_id))");
// For quick matching of orders. is_closed are is_fulfilled included so inactive orders can be filtered out.
stmt.execute("CREATE INDEX AssetOrderMatchingIndex on AssetOrders (have_asset_id, want_asset_id, is_closed, is_fulfilled)");
// For when a user wants to look up their current/historic orders. is_closed included so user can filter by active/inactive orders.
stmt.execute("CREATE INDEX AssetOrderCreatorIndex on AssetOrders (creator, is_closed)");
break;
case 24:
// Asset Trades
stmt.execute("CREATE TABLE AssetTrades (initiating_order_id AssetOrderId NOT NULL, target_order_id AssetOrderId NOT NULL, "
+ "amount QoraAmount NOT NULL, price QoraAmount NOT NULL, traded TIMESTAMP NOT NULL)");
// For looking up historic trades based on orders
stmt.execute("CREATE INDEX AssetTradeBuyOrderIndex on AssetTrades (initiating_order_id, traded)");
stmt.execute("CREATE INDEX AssetTradeSellOrderIndex on AssetTrades (target_order_id, traded)");
break;
case 24:
// Asset Trades
stmt.execute("CREATE TABLE AssetTrades (initiating_order_id AssetOrderId NOT NULL, target_order_id AssetOrderId NOT NULL, "
+ "amount QoraAmount NOT NULL, price QoraAmount NOT NULL, traded TIMESTAMP WITH TIME ZONE NOT NULL)");
// For looking up historic trades based on orders
stmt.execute("CREATE INDEX AssetTradeBuyOrderIndex on AssetTrades (initiating_order_id, traded)");
stmt.execute("CREATE INDEX AssetTradeSellOrderIndex on AssetTrades (target_order_id, traded)");
break;
case 25:
// Polls/Voting
stmt.execute(
"CREATE TABLE Polls (poll_name PollName, description VARCHAR(4000) NOT NULL, creator QoraPublicKey NOT NULL, owner QoraAddress NOT NULL, "
+ "published TIMESTAMP NOT NULL, " + "PRIMARY KEY (poll_name))");
// Various options available on a poll
stmt.execute("CREATE TABLE PollOptions (poll_name PollName, option_index TINYINT NOT NULL, option_name PollOption, "
+ "PRIMARY KEY (poll_name, option_index), FOREIGN KEY (poll_name) REFERENCES Polls (poll_name) ON DELETE CASCADE)");
// Actual votes cast on a poll by voting users. NOTE: only one vote per user supported at this time.
stmt.execute("CREATE TABLE PollVotes (poll_name PollName, voter QoraPublicKey, option_index PollOptionIndex NOT NULL, "
+ "PRIMARY KEY (poll_name, voter), FOREIGN KEY (poll_name) REFERENCES Polls (poll_name) ON DELETE CASCADE)");
// For when a user wants to lookup poll they own
stmt.execute("CREATE INDEX PollOwnerIndex on Polls (owner)");
break;
case 25:
// Polls/Voting
stmt.execute(
"CREATE TABLE Polls (poll_name PollName, description VARCHAR(4000) NOT NULL, creator QoraPublicKey NOT NULL, owner QoraAddress NOT NULL, "
+ "published TIMESTAMP WITH TIME ZONE NOT NULL, " + "PRIMARY KEY (poll_name))");
// Various options available on a poll
stmt.execute("CREATE TABLE PollOptions (poll_name PollName, option_index TINYINT NOT NULL, option_name PollOption, "
+ "PRIMARY KEY (poll_name, option_index), FOREIGN KEY (poll_name) REFERENCES Polls (poll_name) ON DELETE CASCADE)");
// Actual votes cast on a poll by voting users. NOTE: only one vote per user supported at this time.
stmt.execute("CREATE TABLE PollVotes (poll_name PollName, voter QoraPublicKey, option_index PollOptionIndex NOT NULL, "
+ "PRIMARY KEY (poll_name, voter), FOREIGN KEY (poll_name) REFERENCES Polls (poll_name) ON DELETE CASCADE)");
// For when a user wants to lookup poll they own
stmt.execute("CREATE INDEX PollOwnerIndex on Polls (owner)");
break;
case 26:
// Registered Names
stmt.execute(
"CREATE TABLE Names (name RegisteredName, data VARCHAR(4000) NOT NULL, registrant QoraPublicKey NOT NULL, owner QoraAddress NOT NULL, "
+ "registered TIMESTAMP NOT NULL, updated TIMESTAMP, reference Signature, is_for_sale BOOLEAN NOT NULL, sale_price QoraAmount, "
+ "PRIMARY KEY (name))");
break;
case 26:
// Registered Names
stmt.execute(
"CREATE TABLE Names (name RegisteredName, data VARCHAR(4000) NOT NULL, registrant QoraPublicKey NOT NULL, owner QoraAddress NOT NULL, "
+ "registered TIMESTAMP WITH TIME ZONE NOT NULL, updated TIMESTAMP WITH TIME ZONE, reference Signature, is_for_sale BOOLEAN NOT NULL, sale_price QoraAmount, "
+ "PRIMARY KEY (name))");
break;
default:
// nothing to do
return false;
default:
// nothing to do
return false;
}
}
// database was updated

View File

@ -4,6 +4,7 @@ import java.math.BigDecimal;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.Calendar;
import data.naming.NameData;
import repository.NameRepository;
@ -19,19 +20,18 @@ public class HSQLDBNameRepository implements NameRepository {
@Override
public NameData fromName(String name) throws DataException {
try {
ResultSet resultSet = this.repository
.checkedExecute("SELECT registrant, owner, data, registered, updated, reference, is_for_sale, sale_price FROM Names WHERE name = ?", name);
try (ResultSet resultSet = this.repository
.checkedExecute("SELECT registrant, owner, data, registered, updated, reference, is_for_sale, sale_price FROM Names WHERE name = ?", name)) {
if (resultSet == null)
return null;
byte[] registrantPublicKey = resultSet.getBytes(1);
String owner = resultSet.getString(2);
String data = resultSet.getString(3);
long registered = resultSet.getTimestamp(4).getTime();
long registered = resultSet.getTimestamp(4, Calendar.getInstance(HSQLDBRepository.UTC)).getTime();
// Special handling for possibly-NULL "updated" column
Timestamp updatedTimestamp = resultSet.getTimestamp(5);
Timestamp updatedTimestamp = resultSet.getTimestamp(5, Calendar.getInstance(HSQLDBRepository.UTC));
Long updated = updatedTimestamp == null ? null : updatedTimestamp.getTime();
byte[] reference = resultSet.getBytes(6);

View File

@ -6,6 +6,7 @@ import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.TimeZone;
import repository.AccountRepository;
import repository.AssetRepository;
@ -19,6 +20,8 @@ import repository.hsqldb.transaction.HSQLDBTransactionRepository;
public class HSQLDBRepository implements Repository {
public static final TimeZone UTC = TimeZone.getTimeZone("UTC");
protected Connection connection;
// NB: no visibility modifier so only callable from within same package
@ -74,24 +77,22 @@ public class HSQLDBRepository implements Repository {
}
}
// TODO prevent leaking of connections if .close() is not called before garbage collection of the repository.
// Maybe use PhantomReference to call .close() on connection after repository destruction?
@Override
public void close() throws DataException {
try {
try (Statement stmt = this.connection.createStatement()) {
// Diagnostic check for uncommitted changes
Statement stmt = this.connection.createStatement();
if (!stmt.execute("SELECT transaction, transaction_size FROM information_schema.system_sessions")) // TRANSACTION_SIZE() broken?
throw new DataException("Unable to check repository status during close");
ResultSet rs = stmt.getResultSet();
if (rs == null || !rs.next())
throw new DataException("Unable to check repository status during close");
try (ResultSet resultSet = stmt.getResultSet()) {
if (resultSet == null || !resultSet.next())
System.out.println("Unable to check repository status during close");
boolean inTransaction = rs.getBoolean(1);
int transactionCount = rs.getInt(2);
if (inTransaction && transactionCount != 0)
System.out.println("Uncommitted changes (" + transactionCount + ") during repository close");
boolean inTransaction = resultSet.getBoolean(1);
int transactionCount = resultSet.getInt(2);
if (inTransaction && transactionCount != 0)
System.out.println("Uncommitted changes (" + transactionCount + ") during repository close");
}
// give connection back to the pool
this.connection.close();
@ -115,9 +116,12 @@ public class HSQLDBRepository implements Repository {
* @return ResultSet, or null if there are no found rows
* @throws SQLException
*/
@SuppressWarnings("resource")
public ResultSet checkedExecute(String sql, Object... objects) throws SQLException {
PreparedStatement preparedStatement = this.connection.prepareStatement(sql);
// Close the PreparedStatement when the ResultSet is closed otherwise there's a potential resource leak.
// We can't use try-with-resources here as closing the PreparedStatement on return would also prematurely close the ResultSet.
preparedStatement.closeOnCompletion();
return this.checkedExecuteResultSet(preparedStatement, objects);
}
@ -198,12 +202,13 @@ public class HSQLDBRepository implements Repository {
* @throws SQLException
*/
public Long callIdentity() throws SQLException {
PreparedStatement preparedStatement = this.connection.prepareStatement("CALL IDENTITY()");
ResultSet resultSet = this.checkedExecuteResultSet(preparedStatement);
if (resultSet == null)
return null;
try (PreparedStatement preparedStatement = this.connection.prepareStatement("CALL IDENTITY()");
ResultSet resultSet = this.checkedExecuteResultSet(preparedStatement)) {
if (resultSet == null)
return null;
return resultSet.getLong(1);
return resultSet.getLong(1);
}
}
/**
@ -224,12 +229,13 @@ public class HSQLDBRepository implements Repository {
* @throws SQLException
*/
public boolean exists(String tableName, String whereClause, Object... objects) throws SQLException {
PreparedStatement preparedStatement = this.connection.prepareStatement("SELECT TRUE FROM " + tableName + " WHERE " + whereClause + " LIMIT 1");
ResultSet resultSet = this.checkedExecuteResultSet(preparedStatement, objects);
if (resultSet == null)
return false;
try (PreparedStatement preparedStatement = this.connection.prepareStatement("SELECT TRUE FROM " + tableName + " WHERE " + whereClause + " LIMIT 1");
ResultSet resultSet = this.checkedExecuteResultSet(preparedStatement, objects)) {
if (resultSet == null)
return false;
return true;
return true;
}
}
/**
@ -241,8 +247,9 @@ public class HSQLDBRepository implements Repository {
* @throws SQLException
*/
public void delete(String tableName, String whereClause, Object... objects) throws SQLException {
PreparedStatement preparedStatement = this.connection.prepareStatement("DELETE FROM " + tableName + " WHERE " + whereClause);
this.checkedExecuteUpdateCount(preparedStatement, objects);
try (PreparedStatement preparedStatement = this.connection.prepareStatement("DELETE FROM " + tableName + " WHERE " + whereClause)) {
this.checkedExecuteUpdateCount(preparedStatement, objects);
}
}
}

View File

@ -3,6 +3,7 @@ package repository.hsqldb;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Properties;
import org.hsqldb.jdbc.JDBCPool;
@ -22,6 +23,14 @@ public class HSQLDBRepositoryFactory implements RepositoryFactory {
this.connectionPool = new JDBCPool();
this.connectionPool.setUrl(this.connectionUrl);
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?
try (final Connection connection = this.connectionPool.getConnection()) {
HSQLDBDatabaseUpdates.updateDatabase(connection);
@ -30,6 +39,7 @@ public class HSQLDBRepositoryFactory implements RepositoryFactory {
}
}
@Override
public Repository getRepository() throws DataException {
try {
return new HSQLDBRepository(this.getConnection());
@ -41,22 +51,23 @@ public class HSQLDBRepositoryFactory implements RepositoryFactory {
private Connection getConnection() throws SQLException {
Connection connection = this.connectionPool.getConnection();
// start transaction
// Set transaction level
connection.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE);
connection.setAutoCommit(false);
return connection;
}
@Override
public void close() throws DataException {
try {
// Close all existing connections immediately
this.connectionPool.close(0);
// Now that all connections are closed, create a dedicated connection to shut down repository
Connection connection = DriverManager.getConnection(this.connectionUrl);
connection.createStatement().execute("SHUTDOWN");
connection.close();
try (Connection connection = DriverManager.getConnection(this.connectionUrl)) {
connection.createStatement().execute("SHUTDOWN");
}
} catch (SQLException e) {
throw new DataException("Error during repository shutdown", e);
}

View File

@ -3,8 +3,10 @@ package repository.hsqldb;
import java.math.BigDecimal;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.List;
/**
@ -56,11 +58,11 @@ public class HSQLDBSaver {
*/
public boolean execute(HSQLDBRepository repository) throws SQLException {
String sql = this.formatInsertWithPlaceholders();
PreparedStatement preparedStatement = repository.connection.prepareStatement(sql);
try (PreparedStatement preparedStatement = repository.connection.prepareStatement(sql)) {
this.bindValues(preparedStatement);
this.bindValues(preparedStatement);
return preparedStatement.execute();
return preparedStatement.execute();
}
}
/**
@ -107,11 +109,15 @@ public class HSQLDBSaver {
for (int i = 0; i < this.objects.size(); ++i) {
Object object = this.objects.get(i);
// Special treatment for BigDecimals so that they retain their "scale",
// which would otherwise be assumed as 0.
if (object instanceof BigDecimal) {
// Special treatment for BigDecimals so that they retain their "scale",
// which would otherwise be assumed as 0.
preparedStatement.setBigDecimal(i + 1, (BigDecimal) object);
preparedStatement.setBigDecimal(i + this.objects.size() + 1, (BigDecimal) object);
} else if (object instanceof Timestamp) {
// Special treatment for Timestamps so that they are stored as UTC
preparedStatement.setTimestamp(i + 1, (Timestamp) object, Calendar.getInstance(HSQLDBRepository.UTC));
preparedStatement.setTimestamp(i + this.objects.size() + 1, (Timestamp) object, Calendar.getInstance(HSQLDBRepository.UTC));
} else {
preparedStatement.setObject(i + 1, object);
preparedStatement.setObject(i + this.objects.size() + 1, object);

View File

@ -4,6 +4,7 @@ import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
import data.voting.PollData;
@ -22,36 +23,39 @@ public class HSQLDBVotingRepository implements VotingRepository {
// Polls
@Override
public PollData fromPollName(String pollName) throws DataException {
try {
ResultSet resultSet = this.repository.checkedExecute("SELECT description, creator, owner, published FROM Polls WHERE poll_name = ?", pollName);
try (ResultSet resultSet = this.repository.checkedExecute("SELECT description, creator, owner, published FROM Polls WHERE poll_name = ?", pollName)) {
if (resultSet == null)
return null;
String description = resultSet.getString(1);
byte[] creatorPublicKey = resultSet.getBytes(2);
String owner = resultSet.getString(3);
long published = resultSet.getTimestamp(4).getTime();
long published = resultSet.getTimestamp(4, Calendar.getInstance(HSQLDBRepository.UTC)).getTime();
resultSet = this.repository.checkedExecute("SELECT option_name FROM PollOptions where poll_name = ? ORDER BY option_index ASC", pollName);
if (resultSet == null)
return null;
try (ResultSet optionsResultSet = this.repository
.checkedExecute("SELECT option_name FROM PollOptions where poll_name = ? ORDER BY option_index ASC", pollName)) {
if (optionsResultSet == null)
return null;
List<PollOptionData> pollOptions = new ArrayList<PollOptionData>();
List<PollOptionData> pollOptions = new ArrayList<PollOptionData>();
// NOTE: do-while because checkedExecute() above has already called rs.next() for us
do {
String optionName = resultSet.getString(1);
// NOTE: do-while because checkedExecute() above has already called rs.next() for us
do {
String optionName = optionsResultSet.getString(1);
pollOptions.add(new PollOptionData(optionName));
} while (resultSet.next());
pollOptions.add(new PollOptionData(optionName));
} while (optionsResultSet.next());
return new PollData(creatorPublicKey, owner, pollName, description, pollOptions, published);
return new PollData(creatorPublicKey, owner, pollName, description, pollOptions, published);
}
} catch (SQLException e) {
throw new DataException("Unable to fetch poll from repository", e);
}
}
@Override
public boolean pollExists(String pollName) throws DataException {
try {
return this.repository.exists("Polls", "poll_name = ?", pollName);
@ -60,6 +64,7 @@ public class HSQLDBVotingRepository implements VotingRepository {
}
}
@Override
public void save(PollData pollData) throws DataException {
HSQLDBSaver saveHelper = new HSQLDBSaver("Polls");
@ -89,6 +94,7 @@ public class HSQLDBVotingRepository implements VotingRepository {
}
}
@Override
public void delete(String pollName) throws DataException {
// NOTE: The corresponding rows in PollOptions are deleted automatically by the database thanks to "ON DELETE CASCADE" in the PollOptions' FOREIGN KEY
// definition.
@ -101,11 +107,11 @@ public class HSQLDBVotingRepository implements VotingRepository {
// Votes
@Override
public List<VoteOnPollData> getVotes(String pollName) throws DataException {
List<VoteOnPollData> votes = new ArrayList<VoteOnPollData>();
try {
ResultSet resultSet = this.repository.checkedExecute("SELECT voter, option_index FROM PollVotes WHERE poll_name = ?", pollName);
try (ResultSet resultSet = this.repository.checkedExecute("SELECT voter, option_index FROM PollVotes WHERE poll_name = ?", pollName)) {
if (resultSet == null)
return votes;
@ -123,10 +129,10 @@ public class HSQLDBVotingRepository implements VotingRepository {
}
}
@Override
public VoteOnPollData getVote(String pollName, byte[] voterPublicKey) throws DataException {
try {
ResultSet resultSet = this.repository.checkedExecute("SELECT option_index FROM PollVotes WHERE poll_name = ? AND voter = ?", pollName,
voterPublicKey);
try (ResultSet resultSet = this.repository.checkedExecute("SELECT option_index FROM PollVotes WHERE poll_name = ? AND voter = ?", pollName,
voterPublicKey)) {
if (resultSet == null)
return null;
@ -138,6 +144,7 @@ public class HSQLDBVotingRepository implements VotingRepository {
}
}
@Override
public void save(VoteOnPollData voteOnPollData) throws DataException {
HSQLDBSaver saveHelper = new HSQLDBSaver("PollVotes");
@ -151,6 +158,7 @@ public class HSQLDBVotingRepository implements VotingRepository {
}
}
@Override
public void delete(String pollName, byte[] voterPublicKey) throws DataException {
try {
this.repository.delete("PollVotes", "poll_name = ? AND voter = ?", pollName, voterPublicKey);

View File

@ -20,16 +20,15 @@ public class HSQLDBArbitraryTransactionRepository extends HSQLDBTransactionRepos
}
TransactionData fromBase(byte[] signature, byte[] reference, byte[] creatorPublicKey, long timestamp, BigDecimal fee) throws DataException {
try {
ResultSet rs = this.repository.checkedExecute("SELECT sender, version, service, data_hash from ArbitraryTransactions WHERE signature = ?",
signature);
if (rs == null)
try (ResultSet resultSet = this.repository.checkedExecute("SELECT sender, version, service, data_hash from ArbitraryTransactions WHERE signature = ?",
signature)) {
if (resultSet == null)
return null;
byte[] senderPublicKey = rs.getBytes(1);
int version = rs.getInt(2);
int service = rs.getInt(3);
byte[] dataHash = rs.getBytes(4);
byte[] senderPublicKey = resultSet.getBytes(1);
int version = resultSet.getInt(2);
int service = resultSet.getInt(3);
byte[] dataHash = resultSet.getBytes(4);
List<PaymentData> payments = this.getPaymentsFromSignature(signature);
@ -51,7 +50,8 @@ public class HSQLDBArbitraryTransactionRepository extends HSQLDBTransactionRepos
HSQLDBSaver saveHelper = new HSQLDBSaver("ArbitraryTransactions");
saveHelper.bind("signature", arbitraryTransactionData.getSignature()).bind("sender", arbitraryTransactionData.getSenderPublicKey())
.bind("version", arbitraryTransactionData.getVersion()).bind("service", arbitraryTransactionData.getService()).bind("data_hash", arbitraryTransactionData.getData());
.bind("version", arbitraryTransactionData.getVersion()).bind("service", arbitraryTransactionData.getService())
.bind("data_hash", arbitraryTransactionData.getData());
try {
saveHelper.execute(this.repository);

View File

@ -17,16 +17,15 @@ public class HSQLDBBuyNameTransactionRepository extends HSQLDBTransactionReposit
}
TransactionData fromBase(byte[] signature, byte[] reference, byte[] buyerPublicKey, long timestamp, BigDecimal fee) throws DataException {
try {
ResultSet rs = this.repository.checkedExecute("SELECT name, amount, seller, name_reference FROM BuyNameTransactions WHERE signature = ?",
signature);
if (rs == null)
try (ResultSet resultSet = this.repository.checkedExecute("SELECT name, amount, seller, name_reference FROM BuyNameTransactions WHERE signature = ?",
signature)) {
if (resultSet == null)
return null;
String name = rs.getString(1);
BigDecimal amount = rs.getBigDecimal(2);
String seller = rs.getString(3);
byte[] nameReference = rs.getBytes(4);
String name = resultSet.getString(1);
BigDecimal amount = resultSet.getBigDecimal(2);
String seller = resultSet.getString(3);
byte[] nameReference = resultSet.getBytes(4);
return new BuyNameTransactionData(buyerPublicKey, name, amount, seller, nameReference, fee, timestamp, reference, signature);
} catch (SQLException e) {

View File

@ -17,12 +17,11 @@ public class HSQLDBCancelOrderTransactionRepository extends HSQLDBTransactionRep
}
TransactionData fromBase(byte[] signature, byte[] reference, byte[] creatorPublicKey, long timestamp, BigDecimal fee) throws DataException {
try {
ResultSet rs = this.repository.checkedExecute("SELECT asset_order_id FROM CancelAssetOrderTransactions WHERE signature = ?", signature);
if (rs == null)
try (ResultSet resultSet = this.repository.checkedExecute("SELECT asset_order_id FROM CancelAssetOrderTransactions WHERE signature = ?", signature)) {
if (resultSet == null)
return null;
byte[] assetOrderId = rs.getBytes(1);
byte[] assetOrderId = resultSet.getBytes(1);
return new CancelOrderTransactionData(creatorPublicKey, assetOrderId, fee, timestamp, reference, signature);
} catch (SQLException e) {

View File

@ -17,12 +17,11 @@ public class HSQLDBCancelSellNameTransactionRepository extends HSQLDBTransaction
}
TransactionData fromBase(byte[] signature, byte[] reference, byte[] ownerPublicKey, long timestamp, BigDecimal fee) throws DataException {
try {
ResultSet rs = this.repository.checkedExecute("SELECT name FROM CancelSellNameTransactions WHERE signature = ?", signature);
if (rs == null)
try (ResultSet resultSet = this.repository.checkedExecute("SELECT name FROM CancelSellNameTransactions WHERE signature = ?", signature)) {
if (resultSet == null)
return null;
String name = rs.getString(1);
String name = resultSet.getString(1);
return new CancelSellNameTransactionData(ownerPublicKey, name, fee, timestamp, reference, signature);
} catch (SQLException e) {

View File

@ -17,16 +17,15 @@ public class HSQLDBCreateOrderTransactionRepository extends HSQLDBTransactionRep
}
TransactionData fromBase(byte[] signature, byte[] reference, byte[] creatorPublicKey, long timestamp, BigDecimal fee) throws DataException {
try {
ResultSet rs = this.repository
.checkedExecute("SELECT have_asset_id, amount, want_asset_id, price FROM CreateAssetOrderTransactions WHERE signature = ?", signature);
if (rs == null)
try (ResultSet resultSet = this.repository
.checkedExecute("SELECT have_asset_id, amount, want_asset_id, price FROM CreateAssetOrderTransactions WHERE signature = ?", signature)) {
if (resultSet == null)
return null;
long haveAssetId = rs.getLong(1);
BigDecimal amount = rs.getBigDecimal(2);
long wantAssetId = rs.getLong(3);
BigDecimal price = rs.getBigDecimal(4);
long haveAssetId = resultSet.getLong(1);
BigDecimal amount = resultSet.getBigDecimal(2);
long wantAssetId = resultSet.getLong(3);
BigDecimal price = resultSet.getBigDecimal(4);
return new CreateOrderTransactionData(creatorPublicKey, haveAssetId, wantAssetId, amount, price, fee, timestamp, reference, signature);
} catch (SQLException e) {

View File

@ -20,30 +20,31 @@ public class HSQLDBCreatePollTransactionRepository extends HSQLDBTransactionRepo
}
TransactionData fromBase(byte[] signature, byte[] reference, byte[] creatorPublicKey, long timestamp, BigDecimal fee) throws DataException {
try {
ResultSet rs = this.repository.checkedExecute("SELECT owner, poll_name, description FROM CreatePollTransactions WHERE signature = ?", signature);
if (rs == null)
try (ResultSet resultSet = this.repository.checkedExecute("SELECT owner, poll_name, description FROM CreatePollTransactions WHERE signature = ?",
signature)) {
if (resultSet == null)
return null;
String owner = rs.getString(1);
String pollName = rs.getString(2);
String description = rs.getString(3);
String owner = resultSet.getString(1);
String pollName = resultSet.getString(2);
String description = resultSet.getString(3);
rs = this.repository.checkedExecute("SELECT option_name FROM CreatePollTransactionOptions where signature = ? ORDER BY option_index ASC",
signature);
if (rs == null)
return null;
try (ResultSet optionsResultSet = this.repository
.checkedExecute("SELECT option_name FROM CreatePollTransactionOptions where signature = ? ORDER BY option_index ASC", signature)) {
if (optionsResultSet == null)
return null;
List<PollOptionData> pollOptions = new ArrayList<PollOptionData>();
List<PollOptionData> pollOptions = new ArrayList<PollOptionData>();
// NOTE: do-while because checkedExecute() above has already called rs.next() for us
do {
String optionName = rs.getString(1);
// NOTE: do-while because checkedExecute() above has already called rs.next() for us
do {
String optionName = optionsResultSet.getString(1);
pollOptions.add(new PollOptionData(optionName));
} while (rs.next());
pollOptions.add(new PollOptionData(optionName));
} while (optionsResultSet.next());
return new CreatePollTransactionData(creatorPublicKey, owner, pollName, description, pollOptions, fee, timestamp, reference, signature);
return new CreatePollTransactionData(creatorPublicKey, owner, pollName, description, pollOptions, fee, timestamp, reference, signature);
}
} catch (SQLException e) {
throw new DataException("Unable to fetch create poll transaction from repository", e);
}

View File

@ -17,13 +17,12 @@ public class HSQLDBGenesisTransactionRepository extends HSQLDBTransactionReposit
}
TransactionData fromBase(byte[] signature, byte[] reference, byte[] creatorPublicKey, long timestamp, BigDecimal fee) throws DataException {
try {
ResultSet rs = this.repository.checkedExecute("SELECT recipient, amount FROM GenesisTransactions WHERE signature = ?", signature);
if (rs == null)
try (ResultSet resultSet = this.repository.checkedExecute("SELECT recipient, amount FROM GenesisTransactions WHERE signature = ?", signature)) {
if (resultSet == null)
return null;
String recipient = rs.getString(1);
BigDecimal amount = rs.getBigDecimal(2).setScale(8);
String recipient = resultSet.getString(1);
BigDecimal amount = resultSet.getBigDecimal(2).setScale(8);
return new GenesisTransactionData(recipient, amount, timestamp, signature);
} catch (SQLException e) {

View File

@ -17,20 +17,18 @@ public class HSQLDBIssueAssetTransactionRepository extends HSQLDBTransactionRepo
}
TransactionData fromBase(byte[] signature, byte[] reference, byte[] creatorPublicKey, long timestamp, BigDecimal fee) throws DataException {
try {
ResultSet rs = this.repository.checkedExecute(
"SELECT issuer, owner, asset_name, description, quantity, is_divisible, asset_id FROM IssueAssetTransactions WHERE signature = ?",
signature);
if (rs == null)
try (ResultSet resultSet = this.repository.checkedExecute(
"SELECT issuer, owner, asset_name, description, quantity, is_divisible, asset_id FROM IssueAssetTransactions WHERE signature = ?", signature)) {
if (resultSet == null)
return null;
byte[] issuerPublicKey = rs.getBytes(1);
String owner = rs.getString(2);
String assetName = rs.getString(3);
String description = rs.getString(4);
long quantity = rs.getLong(5);
boolean isDivisible = rs.getBoolean(6);
Long assetId = rs.getLong(7);
byte[] issuerPublicKey = resultSet.getBytes(1);
String owner = resultSet.getString(2);
String assetName = resultSet.getString(3);
String description = resultSet.getString(4);
long quantity = resultSet.getLong(5);
boolean isDivisible = resultSet.getBoolean(6);
Long assetId = resultSet.getLong(7);
return new IssueAssetTransactionData(assetId, issuerPublicKey, owner, assetName, description, quantity, isDivisible, fee, timestamp, reference,
signature);

View File

@ -17,20 +17,19 @@ public class HSQLDBMessageTransactionRepository extends HSQLDBTransactionReposit
}
TransactionData fromBase(byte[] signature, byte[] reference, byte[] creatorPublicKey, long timestamp, BigDecimal fee) throws DataException {
try {
ResultSet rs = this.repository.checkedExecute(
"SELECT version, sender, recipient, is_text, is_encrypted, amount, asset_id, data FROM MessageTransactions WHERE signature = ?", signature);
if (rs == null)
try (ResultSet resultSet = this.repository.checkedExecute(
"SELECT version, sender, recipient, is_text, is_encrypted, amount, asset_id, data FROM MessageTransactions WHERE signature = ?", signature)) {
if (resultSet == null)
return null;
int version = rs.getInt(1);
byte[] senderPublicKey = rs.getBytes(2);
String recipient = rs.getString(3);
boolean isText = rs.getBoolean(4);
boolean isEncrypted = rs.getBoolean(5);
BigDecimal amount = rs.getBigDecimal(6);
Long assetId = rs.getLong(7);
byte[] data = rs.getBytes(8);
int version = resultSet.getInt(1);
byte[] senderPublicKey = resultSet.getBytes(2);
String recipient = resultSet.getString(3);
boolean isText = resultSet.getBoolean(4);
boolean isEncrypted = resultSet.getBoolean(5);
BigDecimal amount = resultSet.getBigDecimal(6);
Long assetId = resultSet.getLong(7);
byte[] data = resultSet.getBytes(8);
return new MessageTransactionData(version, senderPublicKey, recipient, assetId, amount, data, isText, isEncrypted, fee, timestamp, reference,
signature);

View File

@ -19,12 +19,11 @@ public class HSQLDBMultiPaymentTransactionRepository extends HSQLDBTransactionRe
}
TransactionData fromBase(byte[] signature, byte[] reference, byte[] creatorPublicKey, long timestamp, BigDecimal fee) throws DataException {
try {
ResultSet rs = this.repository.checkedExecute("SELECT sender from MultiPaymentTransactions WHERE signature = ?", signature);
if (rs == null)
try (ResultSet resultSet = this.repository.checkedExecute("SELECT sender from MultiPaymentTransactions WHERE signature = ?", signature)) {
if (resultSet == null)
return null;
byte[] senderPublicKey = rs.getBytes(1);
byte[] senderPublicKey = resultSet.getBytes(1);
List<PaymentData> payments = this.getPaymentsFromSignature(signature);

View File

@ -17,14 +17,13 @@ public class HSQLDBPaymentTransactionRepository extends HSQLDBTransactionReposit
}
TransactionData fromBase(byte[] signature, byte[] reference, byte[] creatorPublicKey, long timestamp, BigDecimal fee) throws DataException {
try {
ResultSet rs = this.repository.checkedExecute("SELECT sender, recipient, amount FROM PaymentTransactions WHERE signature = ?", signature);
if (rs == null)
try (ResultSet resultSet = this.repository.checkedExecute("SELECT sender, recipient, amount FROM PaymentTransactions WHERE signature = ?", signature)) {
if (resultSet == null)
return null;
byte[] senderPublicKey = rs.getBytes(1);
String recipient = rs.getString(2);
BigDecimal amount = rs.getBigDecimal(3);
byte[] senderPublicKey = resultSet.getBytes(1);
String recipient = resultSet.getString(2);
BigDecimal amount = resultSet.getBigDecimal(3);
return new PaymentTransactionData(senderPublicKey, recipient, amount, fee, timestamp, reference, signature);
} catch (SQLException e) {

View File

@ -17,14 +17,13 @@ public class HSQLDBRegisterNameTransactionRepository extends HSQLDBTransactionRe
}
TransactionData fromBase(byte[] signature, byte[] reference, byte[] registrantPublicKey, long timestamp, BigDecimal fee) throws DataException {
try {
ResultSet rs = this.repository.checkedExecute("SELECT owner, name, data FROM RegisterNameTransactions WHERE signature = ?", signature);
if (rs == null)
try (ResultSet resultSet = this.repository.checkedExecute("SELECT owner, name, data FROM RegisterNameTransactions WHERE signature = ?", signature)) {
if (resultSet == null)
return null;
String owner = rs.getString(1);
String name = rs.getString(2);
String data = rs.getString(3);
String owner = resultSet.getString(1);
String name = resultSet.getString(2);
String data = resultSet.getString(3);
return new RegisterNameTransactionData(registrantPublicKey, owner, name, data, fee, timestamp, reference, signature);
} catch (SQLException e) {

View File

@ -17,13 +17,12 @@ public class HSQLDBSellNameTransactionRepository extends HSQLDBTransactionReposi
}
TransactionData fromBase(byte[] signature, byte[] reference, byte[] ownerPublicKey, long timestamp, BigDecimal fee) throws DataException {
try {
ResultSet rs = this.repository.checkedExecute("SELECT name, amount FROM SellNameTransactions WHERE signature = ?", signature);
if (rs == null)
try (ResultSet resultSet = this.repository.checkedExecute("SELECT name, amount FROM SellNameTransactions WHERE signature = ?", signature)) {
if (resultSet == null)
return null;
String name = rs.getString(1);
BigDecimal amount = rs.getBigDecimal(2);
String name = resultSet.getString(1);
BigDecimal amount = resultSet.getBigDecimal(2);
return new SellNameTransactionData(ownerPublicKey, name, amount, fee, timestamp, reference, signature);
} catch (SQLException e) {

View File

@ -5,6 +5,7 @@ import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
import data.PaymentData;
@ -59,17 +60,18 @@ public class HSQLDBTransactionRepository implements TransactionRepository {
protected HSQLDBTransactionRepository() {
}
@Override
public TransactionData fromSignature(byte[] signature) throws DataException {
try {
ResultSet rs = this.repository.checkedExecute("SELECT type, reference, creator, creation, fee FROM Transactions WHERE signature = ?", signature);
if (rs == null)
try (ResultSet resultSet = this.repository.checkedExecute("SELECT type, reference, creator, creation, fee FROM Transactions WHERE signature = ?",
signature)) {
if (resultSet == null)
return null;
TransactionType type = TransactionType.valueOf(rs.getInt(1));
byte[] reference = rs.getBytes(2);
byte[] creatorPublicKey = rs.getBytes(3);
long timestamp = rs.getTimestamp(4).getTime();
BigDecimal fee = rs.getBigDecimal(5).setScale(8);
TransactionType type = TransactionType.valueOf(resultSet.getInt(1));
byte[] reference = resultSet.getBytes(2);
byte[] creatorPublicKey = resultSet.getBytes(3);
long timestamp = resultSet.getTimestamp(4, Calendar.getInstance(HSQLDBRepository.UTC)).getTime();
BigDecimal fee = resultSet.getBigDecimal(5).setScale(8);
return this.fromBase(type, signature, reference, creatorPublicKey, timestamp, fee);
} catch (SQLException e) {
@ -77,17 +79,18 @@ public class HSQLDBTransactionRepository implements TransactionRepository {
}
}
@Override
public TransactionData fromReference(byte[] reference) throws DataException {
try {
ResultSet rs = this.repository.checkedExecute("SELECT type, signature, creator, creation, fee FROM Transactions WHERE reference = ?", reference);
if (rs == null)
try (ResultSet resultSet = this.repository.checkedExecute("SELECT type, signature, creator, creation, fee FROM Transactions WHERE reference = ?",
reference)) {
if (resultSet == null)
return null;
TransactionType type = TransactionType.valueOf(rs.getInt(1));
byte[] signature = rs.getBytes(2);
byte[] creatorPublicKey = rs.getBytes(3);
long timestamp = rs.getTimestamp(4).getTime();
BigDecimal fee = rs.getBigDecimal(5).setScale(8);
TransactionType type = TransactionType.valueOf(resultSet.getInt(1));
byte[] signature = resultSet.getBytes(2);
byte[] creatorPublicKey = resultSet.getBytes(3);
long timestamp = resultSet.getTimestamp(4, Calendar.getInstance(HSQLDBRepository.UTC)).getTime();
BigDecimal fee = resultSet.getBigDecimal(5).setScale(8);
return this.fromBase(type, signature, reference, creatorPublicKey, timestamp, fee);
} catch (SQLException e) {
@ -152,21 +155,21 @@ public class HSQLDBTransactionRepository implements TransactionRepository {
}
protected List<PaymentData> getPaymentsFromSignature(byte[] signature) throws DataException {
try {
ResultSet rs = this.repository.checkedExecute("SELECT recipient, amount, asset_id FROM SharedTransactionPayments WHERE signature = ?", signature);
if (rs == null)
try (ResultSet resultSet = this.repository.checkedExecute("SELECT recipient, amount, asset_id FROM SharedTransactionPayments WHERE signature = ?",
signature)) {
if (resultSet == null)
return null;
List<PaymentData> payments = new ArrayList<PaymentData>();
// NOTE: do-while because checkedExecute() above has already called rs.next() for us
do {
String recipient = rs.getString(1);
BigDecimal amount = rs.getBigDecimal(2);
long assetId = rs.getLong(3);
String recipient = resultSet.getString(1);
BigDecimal amount = resultSet.getBigDecimal(2);
long assetId = resultSet.getLong(3);
payments.add(new PaymentData(recipient, assetId, amount));
} while (rs.next());
} while (resultSet.next());
return payments;
} catch (SQLException e) {
@ -194,16 +197,15 @@ public class HSQLDBTransactionRepository implements TransactionRepository {
if (signature == null)
return 0;
// in one go?
try {
ResultSet rs = this.repository.checkedExecute(
"SELECT height from BlockTransactions JOIN Blocks ON Blocks.signature = BlockTransactions.block_signature WHERE transaction_signature = ? LIMIT 1",
signature);
// Fetch height using join via block's transactions
try (ResultSet resultSet = this.repository.checkedExecute(
"SELECT height from BlockTransactions JOIN Blocks ON Blocks.signature = BlockTransactions.block_signature WHERE transaction_signature = ? LIMIT 1",
signature)) {
if (rs == null)
if (resultSet == null)
return 0;
return rs.getInt(1);
return resultSet.getInt(1);
} catch (SQLException e) {
throw new DataException("Unable to fetch transaction's height from repository", e);
}
@ -215,12 +217,12 @@ public class HSQLDBTransactionRepository implements TransactionRepository {
return null;
// Fetch block signature (if any)
try {
ResultSet rs = this.repository.checkedExecute("SELECT block_signature from BlockTransactions WHERE transaction_signature = ? LIMIT 1", signature);
if (rs == null)
try (ResultSet resultSet = this.repository.checkedExecute("SELECT block_signature from BlockTransactions WHERE transaction_signature = ? LIMIT 1",
signature)) {
if (resultSet == null)
return null;
byte[] blockSignature = rs.getBytes(1);
byte[] blockSignature = resultSet.getBytes(1);
return this.repository.getBlockRepository().fromSignature(blockSignature);
} catch (SQLException | DataException e) {

View File

@ -17,16 +17,15 @@ public class HSQLDBTransferAssetTransactionRepository extends HSQLDBTransactionR
}
TransactionData fromBase(byte[] signature, byte[] reference, byte[] creatorPublicKey, long timestamp, BigDecimal fee) throws DataException {
try {
ResultSet rs = this.repository.checkedExecute("SELECT sender, recipient, asset_id, amount FROM TransferAssetTransactions WHERE signature = ?",
signature);
if (rs == null)
try (ResultSet resultSet = this.repository
.checkedExecute("SELECT sender, recipient, asset_id, amount FROM TransferAssetTransactions WHERE signature = ?", signature)) {
if (resultSet == null)
return null;
byte[] senderPublicKey = rs.getBytes(1);
String recipient = rs.getString(2);
long assetId = rs.getLong(3);
BigDecimal amount = rs.getBigDecimal(4);
byte[] senderPublicKey = resultSet.getBytes(1);
String recipient = resultSet.getString(2);
long assetId = resultSet.getLong(3);
BigDecimal amount = resultSet.getBigDecimal(4);
return new TransferAssetTransactionData(senderPublicKey, recipient, amount, assetId, fee, timestamp, reference, signature);
} catch (SQLException e) {

View File

@ -17,15 +17,15 @@ public class HSQLDBUpdateNameTransactionRepository extends HSQLDBTransactionRepo
}
TransactionData fromBase(byte[] signature, byte[] reference, byte[] ownerPublicKey, long timestamp, BigDecimal fee) throws DataException {
try {
ResultSet rs = this.repository.checkedExecute("SELECT new_owner, name, new_data, name_reference FROM UpdateNameTransactions WHERE signature = ?", signature);
if (rs == null)
try (ResultSet resultSet = this.repository
.checkedExecute("SELECT new_owner, name, new_data, name_reference FROM UpdateNameTransactions WHERE signature = ?", signature)) {
if (resultSet == null)
return null;
String newOwner = rs.getString(1);
String name = rs.getString(2);
String newData = rs.getString(3);
byte[] nameReference = rs.getBytes(4);
String newOwner = resultSet.getString(1);
String name = resultSet.getString(2);
String newData = resultSet.getString(3);
byte[] nameReference = resultSet.getBytes(4);
return new UpdateNameTransactionData(ownerPublicKey, newOwner, name, newData, nameReference, fee, timestamp, reference, signature);
} catch (SQLException e) {

View File

@ -17,15 +17,14 @@ public class HSQLDBVoteOnPollTransactionRepository extends HSQLDBTransactionRepo
}
TransactionData fromBase(byte[] signature, byte[] reference, byte[] creatorPublicKey, long timestamp, BigDecimal fee) throws DataException {
try {
ResultSet rs = this.repository
.checkedExecute("SELECT poll_name, option_index, previous_option_index FROM VoteOnPollTransactions WHERE signature = ?", signature);
if (rs == null)
try (ResultSet resultSet = this.repository
.checkedExecute("SELECT poll_name, option_index, previous_option_index FROM VoteOnPollTransactions WHERE signature = ?", signature)) {
if (resultSet == null)
return null;
String pollName = rs.getString(1);
int optionIndex = rs.getInt(2);
Integer previousOptionIndex = rs.getInt(3);
String pollName = resultSet.getString(1);
int optionIndex = resultSet.getInt(2);
Integer previousOptionIndex = resultSet.getInt(3);
return new VoteOnPollTransactionData(creatorPublicKey, pollName, optionIndex, previousOptionIndex, fee, timestamp, reference, signature);
} catch (SQLException e) {

View File

@ -10,7 +10,8 @@ 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;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";
@BeforeClass
public static void setRepository() throws DataException {

View File

@ -35,15 +35,16 @@ public class RepositoryTests extends Common {
@Test
public void testAccessAfterClose() throws DataException {
Repository repository = RepositoryManager.getRepository();
assertNotNull(repository);
try (Repository repository = RepositoryManager.getRepository()) {
assertNotNull(repository);
repository.close();
repository.close();
try {
repository.discardChanges();
fail();
} catch (NullPointerException | DataException e) {
try {
repository.discardChanges();
fail();
} catch (NullPointerException | DataException e) {
}
}
}

View File

@ -18,17 +18,16 @@ public class PaymentTransformer extends Transformer {
// Property lengths
private static final int RECIPIENT_LENGTH = ADDRESS_LENGTH;
private static final int ASSET_ID_LENGTH = LONG_LENGTH;
private static final int AMOUNT_LENGTH = BIG_DECIMAL_LENGTH;
private static final int AMOUNT_LENGTH = 12;
private static final int TOTAL_LENGTH = RECIPIENT_LENGTH + ASSET_ID_LENGTH + AMOUNT_LENGTH;
public static PaymentData fromByteBuffer(ByteBuffer byteBuffer) throws TransformationException {
if (byteBuffer.remaining() < TOTAL_LENGTH)
throw new TransformationException("Byte data too short for payment information");
String recipient = Serialization.deserializeAddress(byteBuffer);
long assetId = byteBuffer.getLong();
BigDecimal amount = Serialization.deserializeBigDecimal(byteBuffer);
BigDecimal amount = Serialization.deserializeBigDecimal(byteBuffer, AMOUNT_LENGTH);
return new PaymentData(recipient, assetId, amount);
}
@ -42,8 +41,10 @@ public class PaymentTransformer extends Transformer {
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
Serialization.serializeAddress(bytes, paymentData.getRecipient());
bytes.write(Longs.toByteArray(paymentData.getAssetId()));
Serialization.serializeBigDecimal(bytes, paymentData.getAmount());
Serialization.serializeBigDecimal(bytes, paymentData.getAmount(), AMOUNT_LENGTH);
return bytes.toByteArray();
} catch (IOException | ClassCastException e) {

View File

@ -79,10 +79,12 @@ public class BlockTransformer extends Transformer {
byteBuffer.get(reference);
BigDecimal generatingBalance = BigDecimal.valueOf(byteBuffer.getLong()).setScale(8);
byte[] generatorPublicKey = Serialization.deserializePublicKey(byteBuffer);
byte[] transactionsSignature = new byte[TRANSACTIONS_SIGNATURE_LENGTH];
byteBuffer.get(transactionsSignature);
byte[] generatorSignature = new byte[GENERATOR_SIGNATURE_LENGTH];
byteBuffer.get(generatorSignature);
@ -105,13 +107,16 @@ public class BlockTransformer extends Transformer {
// Parse transactions now, compared to deferred parsing in Gen1, so we can throw ParseException if need be.
List<TransactionData> transactions = new ArrayList<TransactionData>();
BigDecimal totalFees = BigDecimal.ZERO.setScale(8);
for (int t = 0; t < transactionCount; ++t) {
if (byteBuffer.remaining() < TRANSACTION_SIZE_LENGTH)
throw new TransformationException("Byte data too short for Block Transaction length");
int transactionLength = byteBuffer.getInt();
if (byteBuffer.remaining() < transactionLength)
throw new TransformationException("Byte data too short for Block Transaction");
if (transactionLength > Block.MAX_BLOCK_BYTES)
throw new TransformationException("Byte data too long for Block Transaction");
@ -293,7 +298,7 @@ public class BlockTransformer extends Transformer {
for (Transaction transaction : block.getTransactions()) {
if (!transaction.isSignatureValid())
return null;
throw new TransformationException("Transaction signature invalid when building block's transactions signature");
bytes.write(transaction.getTransactionData().getSignature());
}

View File

@ -5,6 +5,7 @@ import java.io.IOException;
import java.math.BigDecimal;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.json.simple.JSONArray;
@ -16,6 +17,7 @@ import com.google.common.primitives.Longs;
import data.transaction.TransactionData;
import qora.account.PublicKeyAccount;
import qora.block.BlockChain;
import qora.transaction.ArbitraryTransaction;
import data.PaymentData;
import data.transaction.ArbitraryTransactionData;
@ -120,6 +122,34 @@ public class ArbitraryTransactionTransformer extends TransactionTransformer {
}
}
/**
* In Qora v1, the bytes used for verification are really mangled so we need to test for v1-ness and adjust the bytes accordingly.
*
* @param transactionData
* @return byte[]
* @throws TransformationException
*/
public static byte[] toBytesForSigningImpl(TransactionData transactionData) throws TransformationException {
ArbitraryTransactionData arbitraryTransactionData = (ArbitraryTransactionData) transactionData;
byte[] bytes = TransactionTransformer.toBytesForSigningImpl(transactionData);
if (arbitraryTransactionData.getVersion() == 1 || transactionData.getTimestamp() >= BlockChain.getArbitraryTransactionV2Timestamp())
return bytes;
// Special v1 version
// In v1, a coding error means that all bytes prior to final payment entry are lost!
// If there are no payments then we can skip mangling
if (arbitraryTransactionData.getPayments().size() == 0)
return bytes;
// So we're left with: final payment entry, service, data size, data, fee
int v1Length = PaymentTransformer.getDataLength() + SERVICE_LENGTH + DATA_SIZE_LENGTH + arbitraryTransactionData.getData().length + FEE_LENGTH;
int v1Start = bytes.length - v1Length;
return Arrays.copyOfRange(bytes, v1Start, bytes.length);
}
@SuppressWarnings("unchecked")
public static JSONObject toJSON(TransactionData transactionData) throws TransformationException {
JSONObject json = TransactionTransformer.getBaseJSON(transactionData);

View File

@ -155,7 +155,7 @@ public class CreatePollTransactionTransformer extends TransactionTransformer {
// Special v1 version
// Replace transaction type with incorrect Register Name value
System.arraycopy(Ints.toByteArray(TransactionType.REGISTER_NAME.value), 0, bytes, 0, TransactionTransformer.INT_LENGTH);
System.arraycopy(Ints.toByteArray(TransactionType.REGISTER_NAME.value), 0, bytes, 0, INT_LENGTH);
System.out.println(HashCode.fromBytes(bytes).toString());

View File

@ -131,8 +131,8 @@ public class IssueAssetTransactionTransformer extends TransactionTransformer {
// Special v1 version
// Zero duplicate signature/reference
int start = bytes.length - TransactionTransformer.SIGNATURE_LENGTH - TransactionTransformer.BIG_DECIMAL_LENGTH;
int end = start + TransactionTransformer.SIGNATURE_LENGTH;
int start = bytes.length - SIGNATURE_LENGTH - BIG_DECIMAL_LENGTH;
int end = start + SIGNATURE_LENGTH;
Arrays.fill(bytes, start, end, (byte) 0);
return bytes;

View File

@ -5,6 +5,7 @@ import java.io.IOException;
import java.math.BigDecimal;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.json.simple.JSONArray;
@ -16,6 +17,7 @@ import com.google.common.primitives.Longs;
import data.transaction.TransactionData;
import qora.account.PublicKeyAccount;
import qora.block.BlockChain;
import data.PaymentData;
import data.transaction.MultiPaymentTransactionData;
import transform.PaymentTransformer;
@ -87,6 +89,29 @@ public class MultiPaymentTransactionTransformer extends TransactionTransformer {
}
}
/**
* In Qora v1, the bytes used for verification are really mangled so we need to test for v1-ness and adjust the bytes accordingly.
*
* @param transactionData
* @return byte[]
* @throws TransformationException
*/
public static byte[] toBytesForSigningImpl(TransactionData transactionData) throws TransformationException {
byte[] bytes = TransactionTransformer.toBytesForSigningImpl(transactionData);
if (transactionData.getTimestamp() >= BlockChain.getIssueAssetV2Timestamp())
return bytes;
// Special v1 version
// In v1, a coding error means that all bytes prior to final payment entry are lost!
// So we're left with: final payment entry and fee. Signature has already been stripped
int v1Length = PaymentTransformer.getDataLength() + FEE_LENGTH;
int v1Start = bytes.length - v1Length;
return Arrays.copyOfRange(bytes, v1Start, bytes.length);
}
@SuppressWarnings("unchecked")
public static JSONObject toJSON(TransactionData transactionData) throws TransformationException {
JSONObject json = TransactionTransformer.getBaseJSON(transactionData);

View File

@ -36,27 +36,27 @@ public class v1feeder extends Thread {
private static final int DEFAULT_PORT = 9084;
private static final int MAGIC_LENGTH = 4;
private static final int TYPE_LENGTH = 4;
// private static final int TYPE_LENGTH = 4;
private static final int HAS_ID_LENGTH = 1;
private static final int ID_LENGTH = 4;
private static final int DATA_SIZE_LENGTH = 4;
// private static final int ID_LENGTH = 4;
// private static final int DATA_SIZE_LENGTH = 4;
private static final int CHECKSUM_LENGTH = 4;
private static final int SIGNATURE_LENGTH = 128;
private static final byte[] MAINNET_MAGIC = { 0x12, 0x34, 0x56, 0x78 };
private static final int GET_PEERS_TYPE = 1;
private static final int PEERS_TYPE = 2;
// private static final int GET_PEERS_TYPE = 1;
// private static final int PEERS_TYPE = 2;
private static final int HEIGHT_TYPE = 3;
private static final int GET_SIGNATURES_TYPE = 4;
private static final int SIGNATURES_TYPE = 5;
private static final int GET_BLOCK_TYPE = 6;
private static final int BLOCK_TYPE = 7;
private static final int TRANSACTION_TYPE = 8;
// private static final int TRANSACTION_TYPE = 8;
private static final int PING_TYPE = 9;
private static final int VERSION_TYPE = 10;
private static final int FIND_MYSELF_TYPE = 11;
// private static final int FIND_MYSELF_TYPE = 11;
private Socket socket;
private OutputStream out;
@ -237,7 +237,7 @@ public class v1feeder extends Thread {
break;
case VERSION_TYPE:
long timestamp = byteBuffer.getLong();
@SuppressWarnings("unused") long timestamp = byteBuffer.getLong();
int versionLength = byteBuffer.getInt();
byte[] versionBytes = new byte[versionLength];
byteBuffer.get(versionBytes);
@ -299,6 +299,7 @@ public class v1feeder extends Thread {
return newBufferEnd;
}
@Override
public void run() {
try {
DataInputStream in = new DataInputStream(socket.getInputStream());