From c5a32ffa1cc1ea0527e07a1a57a590b3b3e90907 Mon Sep 17 00:00:00 2001 From: catbref Date: Tue, 19 Jun 2018 09:50:58 +0100 Subject: [PATCH] Block/Transaction processing * Add implementation for Account.getBalance(assetId, numberOfConfirmations) * Added orphan() code to Block (CIYAM AT not yet supported) * Added getOrder() 'navigation' method to CreateOrderTransaction * Added missing transaction-type cases to various switches in Transaction, transformers, repositories, etc. * Various repository delete() methods added * Added save/delete support for transaction types that include payments, like multipayment and arbitrary * Changed "recipient" in HSQLDB SharedTransactionPayments from QoraPublicKey to QoraAddress --- src/qora/account/Account.java | 40 +++++++++- src/qora/block/Block.java | 42 +++++++++- .../transaction/CreateOrderTransaction.java | 8 +- src/qora/transaction/Transaction.java | 23 +++++- src/repository/BlockRepository.java | 4 + src/repository/TransactionRepository.java | 4 +- .../hsqldb/HSQLDBBlockRepository.java | 17 +++++ .../hsqldb/HSQLDBDatabaseUpdates.java | 2 +- src/repository/hsqldb/HSQLDBSaver.java | 2 - ...SQLDBCancelOrderTransactionRepository.java | 49 ++++++++++++ ...SQLDBCreateOrderTransactionRepository.java | 4 +- ...QLDBMultiPaymentTransactionRepository.java | 55 ++++++++++++++ .../HSQLDBTransactionRepository.java | 76 +++++++++++++++++-- ...LDBTransferAssetTransactionRepository.java | 54 +++++++++++++ src/test/NavigationTests.java | 2 +- src/test/TransactionTests.java | 1 - .../transaction/TransactionTransformer.java | 50 ++++++++++-- 17 files changed, 397 insertions(+), 36 deletions(-) create mode 100644 src/repository/hsqldb/transaction/HSQLDBCancelOrderTransactionRepository.java create mode 100644 src/repository/hsqldb/transaction/HSQLDBMultiPaymentTransactionRepository.java create mode 100644 src/repository/hsqldb/transaction/HSQLDBTransferAssetTransactionRepository.java diff --git a/src/qora/account/Account.java b/src/qora/account/Account.java index ff57a610..30902d7e 100644 --- a/src/qora/account/Account.java +++ b/src/qora/account/Account.java @@ -60,7 +60,7 @@ public class Account { } } - // TODO + // TODO - CIYAM AT support needed /* * LinkedHashMap, AT_Transaction> atTxs = db.getATTransactionMap().getATTransactions(block.getHeight(db)); * Iterator iter = atTxs.values().iterator(); while (iter.hasNext()) { AT_Transaction key = iter.next(); @@ -80,9 +80,41 @@ public class Account { // Balance manipulations - assetId is 0 for QORA - public BigDecimal getBalance(long assetId, int confirmations) { - // TODO - return null; + public BigDecimal getBalance(long assetId, int confirmations) throws DataException { + // Simple case: we only need balance with 1 confirmation + if (confirmations == 1) + return this.getConfirmedBalance(assetId); + + /* + * For a balance with more confirmations work back from last block, undoing transactions involving this account, until we have processed required number + * of blocks. + */ + BlockRepository blockRepository = this.repository.getBlockRepository(); + BigDecimal balance = this.getConfirmedBalance(assetId); + BlockData blockData = blockRepository.getLastBlock(); + + // Note: "blockData.getHeight() > 1" to make sure we don't examine genesis block + for (int i = 1; i < confirmations && blockData != null && blockData.getHeight() > 1; ++i) { + Block block = new Block(this.repository, blockData); + + for (Transaction transaction : block.getTransactions()) + if (transaction.isInvolved(this)) + balance = balance.subtract(transaction.getAmount(this)); + + // TODO - CIYAM AT support + /* + * // Also check AT transactions for amounts received to this account LinkedHashMap, AT_Transaction> atTxs = + * db.getATTransactionMap().getATTransactions(block.getHeight(db)); Iterator iter = atTxs.values().iterator(); while + * (iter.hasNext()) { AT_Transaction key = iter.next(); + * + * if (key.getRecipient().equals(this.getAddress())) balance = balance.subtract(BigDecimal.valueOf(key.getAmount(), 8)); } + */ + + blockData = block.getParent(); + } + + // Return balance + return balance; } public BigDecimal getConfirmedBalance(long assetId) throws DataException { diff --git a/src/qora/block/Block.java b/src/qora/block/Block.java index 2d226434..c7bbc594 100644 --- a/src/qora/block/Block.java +++ b/src/qora/block/Block.java @@ -5,7 +5,6 @@ import static java.util.stream.Collectors.toMap; import java.math.BigDecimal; import java.math.BigInteger; -import java.sql.Connection; import java.sql.SQLException; import java.util.ArrayList; import java.util.Arrays; @@ -609,8 +608,47 @@ public class Block { } } - public void orphan(Connection connection) { + public void orphan() throws DataException { // TODO + + // Orphan block's CIYAM ATs + orphanAutomatedTransactions(); + + // Orphan transactions in reverse order, and unlink them from this block + List transactions = this.getTransactions(); + for (int sequence = transactions.size() - 1; sequence >= 0; --sequence) { + Transaction transaction = transactions.get(sequence); + transaction.orphan(); + + BlockTransactionData blockTransactionData = new BlockTransactionData(this.getSignature(), sequence, + transaction.getTransactionData().getSignature()); + this.repository.getBlockRepository().delete(blockTransactionData); + } + + // If fees are non-zero then remove fees from generator's balance + BigDecimal blockFee = this.blockData.getTotalFees(); + if (blockFee.compareTo(BigDecimal.ZERO) == 1) + this.generator.setConfirmedBalance(Asset.QORA, this.generator.getConfirmedBalance(Asset.QORA).subtract(blockFee)); + + // Delete block from blockchain + this.repository.getBlockRepository().delete(this.blockData); + } + + public void orphanAutomatedTransactions() throws DataException { + // TODO - CIYAM AT support + /* + * LinkedHashMap< Tuple2 , AT_Transaction > atTxs = DBSet.getInstance().getATTransactionMap().getATTransactions(this.getHeight(db)); + * + * Iterator iter = atTxs.values().iterator(); + * + * while ( iter.hasNext() ) { AT_Transaction key = iter.next(); Long amount = key.getAmount(); if (key.getRecipientId() != null && + * !Arrays.equals(key.getRecipientId(), new byte[ AT_Constants.AT_ID_SIZE ]) && !key.getRecipient().equalsIgnoreCase("1") ) { Account recipient = new + * Account( key.getRecipient() ); recipient.setConfirmedBalance( recipient.getConfirmedBalance( db ).subtract( BigDecimal.valueOf( amount, 8 ) ) , db ); + * if ( Arrays.equals(recipient.getLastReference(db),new byte[64])) { recipient.removeReference(db); } } Account sender = new Account( key.getSender() + * ); sender.setConfirmedBalance( sender.getConfirmedBalance( db ).add( BigDecimal.valueOf( amount, 8 ) ) , db ); + * + * } + */ } } diff --git a/src/qora/transaction/CreateOrderTransaction.java b/src/qora/transaction/CreateOrderTransaction.java index a37f03a9..253afb8c 100644 --- a/src/qora/transaction/CreateOrderTransaction.java +++ b/src/qora/transaction/CreateOrderTransaction.java @@ -52,10 +52,10 @@ public class CreateOrderTransaction extends Transaction { // Navigation - public Order getOrder() { - // TODO Something like: - // return this.repository.getAssetRepository().getOrder(this.transactionData); - return null; + public Order getOrder() throws DataException { + // orderId is the transaction signature + OrderData orderData = this.repository.getAssetRepository().fromOrderId(this.createOrderTransactionData.getSignature()); + return new Order(this.repository, orderData); } // Processing diff --git a/src/qora/transaction/Transaction.java b/src/qora/transaction/Transaction.java index b65e4e06..6014f500 100644 --- a/src/qora/transaction/Transaction.java +++ b/src/qora/transaction/Transaction.java @@ -90,9 +90,18 @@ public abstract class Transaction { case ISSUE_ASSET: return new IssueAssetTransaction(repository, transactionData); + case TRANSFER_ASSET: + return new TransferAssetTransaction(repository, transactionData); + case CREATE_ASSET_ORDER: return new CreateOrderTransaction(repository, transactionData); + case CANCEL_ASSET_ORDER: + return new CancelOrderTransaction(repository, transactionData); + + case MULTIPAYMENT: + return new MultiPaymentTransaction(repository, transactionData); + case MESSAGE: return new MessageTransaction(repository, transactionData); @@ -150,6 +159,12 @@ public abstract class Transaction { } } + /** + * Return the transaction version number that should be used, based on passed timestamp. + * + * @param timestamp + * @return transaction version number, likely 1 or 3 + */ public static int getVersionByTimestamp(long timestamp) { if (timestamp < BlockChain.POWFIX_RELEASE_TIMESTAMP) { return 1; @@ -162,9 +177,10 @@ public abstract class Transaction { * Get block height for this transaction in the blockchain. * * @return height, or 0 if not in blockchain (i.e. unconfirmed) + * @throws DataException */ - public int getHeight() { - return this.repository.getTransactionRepository().getHeight(this.transactionData); + public int getHeight() throws DataException { + return this.repository.getTransactionRepository().getHeightFromSignature(this.transactionData.getSignature()); } /** @@ -235,7 +251,7 @@ public abstract class Transaction { * @throws DataException */ public BlockData getBlock() throws DataException { - return this.repository.getTransactionRepository().toBlock(this.transactionData); + return this.repository.getTransactionRepository().getBlockDataFromSignature(this.transactionData.getSignature()); } /** @@ -282,7 +298,6 @@ public abstract class Transaction { return Arrays.copyOf(bytes, bytes.length - Transformer.SIGNATURE_LENGTH); } catch (TransformationException e) { - // XXX this isn't good throw new RuntimeException("Unable to transform transaction to signature-less byte array", e); } } diff --git a/src/repository/BlockRepository.java b/src/repository/BlockRepository.java index 38861374..6077cc3a 100644 --- a/src/repository/BlockRepository.java +++ b/src/repository/BlockRepository.java @@ -42,6 +42,10 @@ public interface BlockRepository { public void save(BlockData blockData) throws DataException; + public void delete(BlockData blockData) throws DataException; + public void save(BlockTransactionData blockTransactionData) throws DataException; + public void delete(BlockTransactionData blockTransactionData) throws DataException; + } diff --git a/src/repository/TransactionRepository.java b/src/repository/TransactionRepository.java index 6a75a6a9..b15a36b3 100644 --- a/src/repository/TransactionRepository.java +++ b/src/repository/TransactionRepository.java @@ -9,9 +9,9 @@ public interface TransactionRepository { public TransactionData fromReference(byte[] reference) throws DataException; - public int getHeight(TransactionData transactionData); + public int getHeightFromSignature(byte[] signature) throws DataException; - public BlockData toBlock(TransactionData transactionData) throws DataException; + public BlockData getBlockDataFromSignature(byte[] signature) throws DataException; public void save(TransactionData transactionData) throws DataException; diff --git a/src/repository/hsqldb/HSQLDBBlockRepository.java b/src/repository/hsqldb/HSQLDBBlockRepository.java index a0a3f2ed..54257c99 100644 --- a/src/repository/hsqldb/HSQLDBBlockRepository.java +++ b/src/repository/hsqldb/HSQLDBBlockRepository.java @@ -144,6 +144,14 @@ public class HSQLDBBlockRepository implements BlockRepository { } } + public void delete(BlockData blockData) throws DataException { + try { + this.repository.checkedExecute("DELETE FROM Blocks WHERE signature = ?", blockData.getSignature()); + } catch (SQLException e) { + throw new DataException("Unable to delete Block from repository", e); + } + } + public void save(BlockTransactionData blockTransactionData) throws DataException { HSQLDBSaver saveHelper = new HSQLDBSaver("BlockTransactions"); saveHelper.bind("block_signature", blockTransactionData.getBlockSignature()).bind("sequence", blockTransactionData.getSequence()) @@ -156,4 +164,13 @@ public class HSQLDBBlockRepository implements BlockRepository { } } + public void delete(BlockTransactionData blockTransactionData) throws DataException { + try { + this.repository.checkedExecute("DELETE FROM BlockTransactions WHERE block_signature = ? AND sequence = ? AND transaction_signature = ?", + blockTransactionData.getBlockSignature(), blockTransactionData.getSequence(), blockTransactionData.getTransactionSignature()); + } catch (SQLException e) { + throw new DataException("Unable to delete BlockTransaction from repository", e); + } + } + } diff --git a/src/repository/hsqldb/HSQLDBDatabaseUpdates.java b/src/repository/hsqldb/HSQLDBDatabaseUpdates.java index d88acc92..fd6b0bc3 100644 --- a/src/repository/hsqldb/HSQLDBDatabaseUpdates.java +++ b/src/repository/hsqldb/HSQLDBDatabaseUpdates.java @@ -201,7 +201,7 @@ public class HSQLDBDatabaseUpdates { case 12: // Arbitrary/Multi-payment Transaction Payments - stmt.execute("CREATE TABLE SharedTransactionPayments (signature Signature, recipient QoraPublicKey NOT NULL, " + 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; diff --git a/src/repository/hsqldb/HSQLDBSaver.java b/src/repository/hsqldb/HSQLDBSaver.java index 0d6172dd..6c81be29 100644 --- a/src/repository/hsqldb/HSQLDBSaver.java +++ b/src/repository/hsqldb/HSQLDBSaver.java @@ -50,8 +50,6 @@ public class HSQLDBSaver { * Build PreparedStatement using bound column-value pairs then execute it. * * @param repository - * TODO - * @param repository * * @return the result from {@link PreparedStatement#execute()} * @throws SQLException diff --git a/src/repository/hsqldb/transaction/HSQLDBCancelOrderTransactionRepository.java b/src/repository/hsqldb/transaction/HSQLDBCancelOrderTransactionRepository.java new file mode 100644 index 00000000..da29ce57 --- /dev/null +++ b/src/repository/hsqldb/transaction/HSQLDBCancelOrderTransactionRepository.java @@ -0,0 +1,49 @@ +package repository.hsqldb.transaction; + +import java.math.BigDecimal; +import java.sql.ResultSet; +import java.sql.SQLException; + +import data.transaction.CancelOrderTransactionData; +import data.transaction.TransactionData; +import repository.DataException; +import repository.hsqldb.HSQLDBRepository; +import repository.hsqldb.HSQLDBSaver; + +public class HSQLDBCancelOrderTransactionRepository extends HSQLDBTransactionRepository { + + public HSQLDBCancelOrderTransactionRepository(HSQLDBRepository repository) { + this.repository = repository; + } + + 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) + return null; + + byte[] assetOrderId = this.repository.getResultSetBytes(rs.getBinaryStream(1)); + + return new CancelOrderTransactionData(creatorPublicKey, assetOrderId, fee, timestamp, reference, signature); + } catch (SQLException e) { + throw new DataException("Unable to fetch cancel order transaction from repository", e); + } + } + + @Override + public void save(TransactionData transactionData) throws DataException { + CancelOrderTransactionData cancelOrderTransactionData = (CancelOrderTransactionData) transactionData; + + HSQLDBSaver saveHelper = new HSQLDBSaver("CancelAssetOrderTransactions"); + + saveHelper.bind("signature", cancelOrderTransactionData.getSignature()).bind("creator", cancelOrderTransactionData.getCreatorPublicKey()) + .bind("asset_order_id", cancelOrderTransactionData.getOrderId()); + + try { + saveHelper.execute(this.repository); + } catch (SQLException e) { + throw new DataException("Unable to save cancel order transaction into repository", e); + } + } + +} diff --git a/src/repository/hsqldb/transaction/HSQLDBCreateOrderTransactionRepository.java b/src/repository/hsqldb/transaction/HSQLDBCreateOrderTransactionRepository.java index f30f3b7c..952b020f 100644 --- a/src/repository/hsqldb/transaction/HSQLDBCreateOrderTransactionRepository.java +++ b/src/repository/hsqldb/transaction/HSQLDBCreateOrderTransactionRepository.java @@ -18,8 +18,8 @@ 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); + ResultSet rs = this.repository + .checkedExecute("SELECT have_asset_id, amount, want_asset_id, price FROM CreateAssetOrderTransactions WHERE signature = ?", signature); if (rs == null) return null; diff --git a/src/repository/hsqldb/transaction/HSQLDBMultiPaymentTransactionRepository.java b/src/repository/hsqldb/transaction/HSQLDBMultiPaymentTransactionRepository.java new file mode 100644 index 00000000..806f0990 --- /dev/null +++ b/src/repository/hsqldb/transaction/HSQLDBMultiPaymentTransactionRepository.java @@ -0,0 +1,55 @@ +package repository.hsqldb.transaction; + +import java.math.BigDecimal; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.List; + +import data.PaymentData; +import data.transaction.MultiPaymentTransactionData; +import data.transaction.TransactionData; +import repository.DataException; +import repository.hsqldb.HSQLDBRepository; +import repository.hsqldb.HSQLDBSaver; + +public class HSQLDBMultiPaymentTransactionRepository extends HSQLDBTransactionRepository { + + public HSQLDBMultiPaymentTransactionRepository(HSQLDBRepository repository) { + this.repository = repository; + } + + TransactionData fromBase(byte[] signature, byte[] reference, byte[] creatorPublicKey, long timestamp, BigDecimal fee) throws DataException { + try { + ResultSet rs = this.repository.checkedExecute("SELECT sender MultiPaymentTransactions WHERE signature = ?", signature); + if (rs == null) + return null; + + byte[] senderPublicKey = this.repository.getResultSetBytes(rs.getBinaryStream(1)); + + List payments = this.getPaymentsFromSignature(signature); + + return new MultiPaymentTransactionData(senderPublicKey, payments, fee, timestamp, reference, signature); + } catch (SQLException e) { + throw new DataException("Unable to fetch multi-payment transaction from repository", e); + } + } + + @Override + public void save(TransactionData transactionData) throws DataException { + MultiPaymentTransactionData multiPaymentTransactionData = (MultiPaymentTransactionData) transactionData; + + HSQLDBSaver saveHelper = new HSQLDBSaver("MultiPaymentTransactions"); + + saveHelper.bind("signature", multiPaymentTransactionData.getSignature()).bind("sender", multiPaymentTransactionData.getSenderPublicKey()); + + try { + saveHelper.execute(this.repository); + } catch (SQLException e) { + throw new DataException("Unable to save multi-payment transaction into repository", e); + } + + // Save payments. If this fails then it is the caller's responsibility to catch the DataException as the underlying transaction will have been lost. + this.savePayments(transactionData.getSignature(), multiPaymentTransactionData.getPayments()); + } + +} diff --git a/src/repository/hsqldb/transaction/HSQLDBTransactionRepository.java b/src/repository/hsqldb/transaction/HSQLDBTransactionRepository.java index d1dd702a..b99e81ae 100644 --- a/src/repository/hsqldb/transaction/HSQLDBTransactionRepository.java +++ b/src/repository/hsqldb/transaction/HSQLDBTransactionRepository.java @@ -4,7 +4,10 @@ import java.math.BigDecimal; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Timestamp; +import java.util.ArrayList; +import java.util.List; +import data.PaymentData; import data.block.BlockData; import data.transaction.TransactionData; import qora.transaction.Transaction.TransactionType; @@ -19,7 +22,10 @@ public class HSQLDBTransactionRepository implements TransactionRepository { private HSQLDBGenesisTransactionRepository genesisTransactionRepository; private HSQLDBPaymentTransactionRepository paymentTransactionRepository; private HSQLDBIssueAssetTransactionRepository issueAssetTransactionRepository; + private HSQLDBTransferAssetTransactionRepository transferAssetTransactionRepository; private HSQLDBCreateOrderTransactionRepository createOrderTransactionRepository; + private HSQLDBCancelOrderTransactionRepository cancelOrderTransactionRepository; + private HSQLDBMultiPaymentTransactionRepository multiPaymentTransactionRepository; private HSQLDBMessageTransactionRepository messageTransactionRepository; public HSQLDBTransactionRepository(HSQLDBRepository repository) { @@ -27,7 +33,10 @@ public class HSQLDBTransactionRepository implements TransactionRepository { this.genesisTransactionRepository = new HSQLDBGenesisTransactionRepository(repository); this.paymentTransactionRepository = new HSQLDBPaymentTransactionRepository(repository); this.issueAssetTransactionRepository = new HSQLDBIssueAssetTransactionRepository(repository); + this.transferAssetTransactionRepository = new HSQLDBTransferAssetTransactionRepository(repository); this.createOrderTransactionRepository = new HSQLDBCreateOrderTransactionRepository(repository); + this.cancelOrderTransactionRepository = new HSQLDBCancelOrderTransactionRepository(repository); + this.multiPaymentTransactionRepository = new HSQLDBMultiPaymentTransactionRepository(repository); this.messageTransactionRepository = new HSQLDBMessageTransactionRepository(repository); } @@ -82,9 +91,18 @@ public class HSQLDBTransactionRepository implements TransactionRepository { case ISSUE_ASSET: return this.issueAssetTransactionRepository.fromBase(signature, reference, creatorPublicKey, timestamp, fee); + case TRANSFER_ASSET: + return this.transferAssetTransactionRepository.fromBase(signature, reference, creatorPublicKey, timestamp, fee); + case CREATE_ASSET_ORDER: return this.createOrderTransactionRepository.fromBase(signature, reference, creatorPublicKey, timestamp, fee); + case CANCEL_ASSET_ORDER: + return this.cancelOrderTransactionRepository.fromBase(signature, reference, creatorPublicKey, timestamp, fee); + + case MULTIPAYMENT: + return this.multiPaymentTransactionRepository.fromBase(signature, reference, creatorPublicKey, timestamp, fee); + case MESSAGE: return this.messageTransactionRepository.fromBase(signature, reference, creatorPublicKey, timestamp, fee); @@ -93,9 +111,46 @@ public class HSQLDBTransactionRepository implements TransactionRepository { } } + protected List getPaymentsFromSignature(byte[] signature) throws DataException { + try { + ResultSet rs = this.repository.checkedExecute("SELECT recipient, amount, asset_id FROM SharedTransactionPayments WHERE signature = ?", signature); + if (rs == null) + return null; + + List payments = new ArrayList(); + + // 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); + + payments.add(new PaymentData(recipient, assetId, amount)); + } while (rs.next()); + + return payments; + } catch (SQLException e) { + throw new DataException("Unable to fetch payments from repository", e); + } + } + + protected void savePayments(byte[] signature, List payments) throws DataException { + for (PaymentData paymentData : payments) { + HSQLDBSaver saver = new HSQLDBSaver("SharedTransactionPayments"); + + saver.bind("signature", signature).bind("recipient", paymentData.getRecipient()).bind("amount", paymentData.getAmount()).bind("asset_id", + paymentData.getAssetId()); + + try { + saver.execute(this.repository); + } catch (SQLException e) { + throw new DataException("Unable to save payment into repository", e); + } + } + } + @Override - public int getHeight(TransactionData transactionData) { - byte[] signature = transactionData.getSignature(); + public int getHeightFromSignature(byte[] signature) throws DataException { if (signature == null) return 0; @@ -110,13 +165,12 @@ public class HSQLDBTransactionRepository implements TransactionRepository { return rs.getInt(1); } catch (SQLException e) { - return 0; + throw new DataException("Unable to fetch transaction's height from repository", e); } } @Override - public BlockData toBlock(TransactionData transactionData) throws DataException { - byte[] signature = transactionData.getSignature(); + public BlockData getBlockDataFromSignature(byte[] signature) throws DataException { if (signature == null) return null; @@ -143,7 +197,7 @@ public class HSQLDBTransactionRepository implements TransactionRepository { try { saver.execute(this.repository); } catch (SQLException e) { - throw new DataException(e); + throw new DataException("Unable to save transaction into repository", e); } // Now call transaction-type-specific save() method @@ -160,10 +214,20 @@ public class HSQLDBTransactionRepository implements TransactionRepository { this.issueAssetTransactionRepository.save(transactionData); break; + case TRANSFER_ASSET: + this.transferAssetTransactionRepository.save(transactionData); + break; + case CREATE_ASSET_ORDER: this.createOrderTransactionRepository.save(transactionData); break; + case CANCEL_ASSET_ORDER: + this.cancelOrderTransactionRepository.save(transactionData); + + case MULTIPAYMENT: + this.multiPaymentTransactionRepository.save(transactionData); + case MESSAGE: this.messageTransactionRepository.save(transactionData); break; diff --git a/src/repository/hsqldb/transaction/HSQLDBTransferAssetTransactionRepository.java b/src/repository/hsqldb/transaction/HSQLDBTransferAssetTransactionRepository.java new file mode 100644 index 00000000..b35a4ad6 --- /dev/null +++ b/src/repository/hsqldb/transaction/HSQLDBTransferAssetTransactionRepository.java @@ -0,0 +1,54 @@ +package repository.hsqldb.transaction; + +import java.math.BigDecimal; +import java.sql.ResultSet; +import java.sql.SQLException; + +import data.transaction.TransferAssetTransactionData; +import data.transaction.TransactionData; +import repository.DataException; +import repository.hsqldb.HSQLDBRepository; +import repository.hsqldb.HSQLDBSaver; + +public class HSQLDBTransferAssetTransactionRepository extends HSQLDBTransactionRepository { + + public HSQLDBTransferAssetTransactionRepository(HSQLDBRepository repository) { + this.repository = repository; + } + + 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) + return null; + + byte[] senderPublicKey = this.repository.getResultSetBytes(rs.getBinaryStream(1)); + String recipient = rs.getString(2); + long assetId = rs.getLong(3); + BigDecimal amount = rs.getBigDecimal(4); + + return new TransferAssetTransactionData(senderPublicKey, recipient, amount, assetId, fee, timestamp, reference, signature); + } catch (SQLException e) { + throw new DataException("Unable to fetch transfer asset transaction from repository", e); + } + } + + @Override + public void save(TransactionData transactionData) throws DataException { + TransferAssetTransactionData transferAssetTransactionData = (TransferAssetTransactionData) transactionData; + + HSQLDBSaver saveHelper = new HSQLDBSaver("TransferAssetTransactions"); + + saveHelper.bind("signature", transferAssetTransactionData.getSignature()).bind("sender", transferAssetTransactionData.getSenderPublicKey()) + .bind("recipient", transferAssetTransactionData.getRecipient()).bind("asset_id", transferAssetTransactionData.getAssetId()) + .bind("amount", transferAssetTransactionData.getAmount()); + + try { + saveHelper.execute(this.repository); + } catch (SQLException e) { + throw new DataException("Unable to save transfer asset transaction into repository", e); + } + } + +} diff --git a/src/test/NavigationTests.java b/src/test/NavigationTests.java index de9057e6..42cfdcb6 100644 --- a/src/test/NavigationTests.java +++ b/src/test/NavigationTests.java @@ -32,7 +32,7 @@ public class NavigationTests extends Common { assertNotNull("Transaction data not loaded from repository", transactionData); assertEquals("Transaction data not PAYMENT type", TransactionType.PAYMENT, transactionData.getType()); - BlockData blockData = transactionRepository.toBlock(transactionData); + BlockData blockData = transactionRepository.getBlockDataFromSignature(signature); assertNotNull("Block 49778 not loaded from database", blockData); System.out.println("Block " + blockData.getHeight() + ", signature: " + Base58.encode(blockData.getSignature())); diff --git a/src/test/TransactionTests.java b/src/test/TransactionTests.java index ac63ef14..a33ea64a 100644 --- a/src/test/TransactionTests.java +++ b/src/test/TransactionTests.java @@ -29,7 +29,6 @@ import repository.Repository; import repository.RepositoryFactory; import repository.RepositoryManager; import repository.hsqldb.HSQLDBRepositoryFactory; -import utils.NTP; // Don't extend Common as we want to use an in-memory database public class TransactionTests { diff --git a/src/transform/transaction/TransactionTransformer.java b/src/transform/transaction/TransactionTransformer.java index c9bae7bb..dd4586bd 100644 --- a/src/transform/transaction/TransactionTransformer.java +++ b/src/transform/transaction/TransactionTransformer.java @@ -40,9 +40,18 @@ public class TransactionTransformer extends Transformer { case ISSUE_ASSET: return IssueAssetTransactionTransformer.fromByteBuffer(byteBuffer); + case TRANSFER_ASSET: + return TransferAssetTransactionTransformer.fromByteBuffer(byteBuffer); + case CREATE_ASSET_ORDER: return CreateOrderTransactionTransformer.fromByteBuffer(byteBuffer); + case CANCEL_ASSET_ORDER: + return CancelOrderTransactionTransformer.fromByteBuffer(byteBuffer); + + case MULTIPAYMENT: + return MultiPaymentTransactionTransformer.fromByteBuffer(byteBuffer); + case MESSAGE: return MessageTransactionTransformer.fromByteBuffer(byteBuffer); @@ -62,9 +71,18 @@ public class TransactionTransformer extends Transformer { case ISSUE_ASSET: return IssueAssetTransactionTransformer.getDataLength(transactionData); + case TRANSFER_ASSET: + return TransferAssetTransactionTransformer.getDataLength(transactionData); + case CREATE_ASSET_ORDER: return CreateOrderTransactionTransformer.getDataLength(transactionData); + case CANCEL_ASSET_ORDER: + return CancelOrderTransactionTransformer.getDataLength(transactionData); + + case MULTIPAYMENT: + return MultiPaymentTransactionTransformer.getDataLength(transactionData); + case MESSAGE: return MessageTransactionTransformer.getDataLength(transactionData); @@ -84,9 +102,18 @@ public class TransactionTransformer extends Transformer { case ISSUE_ASSET: return IssueAssetTransactionTransformer.toBytes(transactionData); + case TRANSFER_ASSET: + return TransferAssetTransactionTransformer.toBytes(transactionData); + case CREATE_ASSET_ORDER: return CreateOrderTransactionTransformer.toBytes(transactionData); + case CANCEL_ASSET_ORDER: + return CancelOrderTransactionTransformer.toBytes(transactionData); + + case MULTIPAYMENT: + return MultiPaymentTransactionTransformer.toBytes(transactionData); + case MESSAGE: return MessageTransactionTransformer.toBytes(transactionData); @@ -95,22 +122,31 @@ public class TransactionTransformer extends Transformer { } } - public static JSONObject toJSON(TransactionData transaction) throws TransformationException { - switch (transaction.getType()) { + public static JSONObject toJSON(TransactionData transactionData) throws TransformationException { + switch (transactionData.getType()) { case GENESIS: - return GenesisTransactionTransformer.toJSON(transaction); + return GenesisTransactionTransformer.toJSON(transactionData); case PAYMENT: - return PaymentTransactionTransformer.toJSON(transaction); + return PaymentTransactionTransformer.toJSON(transactionData); case ISSUE_ASSET: - return IssueAssetTransactionTransformer.toJSON(transaction); + return IssueAssetTransactionTransformer.toJSON(transactionData); + + case TRANSFER_ASSET: + return TransferAssetTransactionTransformer.toJSON(transactionData); case CREATE_ASSET_ORDER: - return CreateOrderTransactionTransformer.toJSON(transaction); + return CreateOrderTransactionTransformer.toJSON(transactionData); + + case CANCEL_ASSET_ORDER: + return CancelOrderTransactionTransformer.toJSON(transactionData); + + case MULTIPAYMENT: + return MultiPaymentTransactionTransformer.toJSON(transactionData); case MESSAGE: - return MessageTransactionTransformer.toJSON(transaction); + return MessageTransactionTransformer.toJSON(transactionData); default: throw new TransformationException("Unsupported transaction type");