forked from Qortal/qortal
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:
parent
7da84b2b85
commit
e56d8f5e02
@ -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
BIN
lib/hsqldb-r5836.jar
Normal file
Binary file not shown.
5
pom.xml
5
pom.xml
@ -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>
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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());
|
||||
}
|
||||
|
@ -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());
|
||||
}
|
||||
|
@ -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());
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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(),
|
||||
|
@ -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 = ?",
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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) {
|
||||
|
@ -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) {
|
||||
|
@ -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) {
|
||||
|
@ -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) {
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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) {
|
||||
|
@ -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) {
|
||||
|
@ -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) {
|
||||
|
@ -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) {
|
||||
|
@ -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) {
|
||||
|
@ -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) {
|
||||
|
@ -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) {
|
||||
|
@ -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 {
|
||||
|
@ -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) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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) {
|
||||
|
@ -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());
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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());
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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());
|
||||
|
Loading…
Reference in New Issue
Block a user