diff --git a/src/qora/account/Account.java b/src/qora/account/Account.java index 2f8c38ce..bbf03dfd 100644 --- a/src/qora/account/Account.java +++ b/src/qora/account/Account.java @@ -2,6 +2,9 @@ package qora.account; import java.math.BigDecimal; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + import data.account.AccountBalanceData; import data.account.AccountData; import data.block.BlockData; @@ -15,6 +18,8 @@ import repository.Repository; public class Account { + private static final Logger LOGGER = LogManager.getLogger(Account.class); + public static final int ADDRESS_LENGTH = 25; protected Repository repository; @@ -128,6 +133,8 @@ public class Account { public void setConfirmedBalance(long assetId, BigDecimal balance) throws DataException { AccountBalanceData accountBalanceData = new AccountBalanceData(this.accountData.getAddress(), assetId, balance); this.repository.getAccountRepository().save(accountBalanceData); + + LOGGER.trace(this.accountData.getAddress() + " balance now: " + balance.toPlainString() + " [assetId " + assetId + "]"); } public void deleteBalance(long assetId) throws DataException { diff --git a/src/qora/assets/Order.java b/src/qora/assets/Order.java index aa173628..56b15cea 100644 --- a/src/qora/assets/Order.java +++ b/src/qora/assets/Order.java @@ -5,6 +5,11 @@ import java.math.BigInteger; import java.math.RoundingMode; import java.util.List; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import com.google.common.hash.HashCode; + import data.assets.AssetData; import data.assets.OrderData; import data.assets.TradeData; @@ -16,6 +21,8 @@ import repository.Repository; public class Order { + private static final Logger LOGGER = LogManager.getLogger(Order.class); + // Properties private Repository repository; private OrderData orderData; @@ -104,10 +111,14 @@ public class Order { this.repository.getAssetRepository().save(this.orderData); // Attempt to match orders + LOGGER.debug("Processing our order " + HashCode.fromBytes(this.orderData.getOrderId()).toString()); + LOGGER.trace("We have: " + this.orderData.getAmount().toPlainString() + " " + haveAssetData.getName()); + LOGGER.trace("We want " + this.orderData.getPrice().toPlainString() + " " + wantAssetData.getName() + " per " + haveAssetData.getName()); // Fetch corresponding open orders that might potentially match, hence reversed want/have assetId args. // Returned orders are sorted with lowest "price" first. List orders = assetRepository.getOpenOrders(wantAssetId, haveAssetId); + LOGGER.trace("Open orders fetched from repository: " + orders.size()); /* * Our order example: @@ -123,6 +134,11 @@ public class Order { BigDecimal ourPrice = this.orderData.getPrice(); for (OrderData theirOrderData : orders) { + LOGGER.trace("Considering order " + HashCode.fromBytes(theirOrderData.getOrderId()).toString()); + // Note swapped use of have/want asset data as this is from 'their' perspective. + LOGGER.trace("They have: " + theirOrderData.getAmount().toPlainString() + " " + wantAssetData.getName()); + LOGGER.trace("They want " + theirOrderData.getPrice().toPlainString() + " " + haveAssetData.getName() + " per " + wantAssetData.getName()); + /* * Potential matching order example: * @@ -137,6 +153,7 @@ public class Order { // Round down otherwise their buyingPrice would be better than advertised and cause issues BigDecimal theirBuyingPrice = BigDecimal.ONE.setScale(8).divide(theirOrderData.getPrice(), RoundingMode.DOWN); + LOGGER.trace("theirBuyingPrice: " + theirBuyingPrice.toPlainString() + " " + wantAssetData.getName() + " per " + haveAssetData.getName()); // If their buyingPrice is less than what we're willing to pay then we're done as prices only get worse as we iterate through list of orders if (theirBuyingPrice.compareTo(ourPrice) < 0) @@ -144,10 +161,13 @@ public class Order { // Calculate how many want-asset we could buy at their price BigDecimal ourAmountLeft = this.getAmountLeft().multiply(theirBuyingPrice).setScale(8, RoundingMode.DOWN); - // How many want-asset is left available in this order + LOGGER.trace("ourAmountLeft (max we could buy at their price): " + ourAmountLeft.toPlainString() + " " + wantAssetData.getName()); + // How many want-asset is remaining available in this order BigDecimal theirAmountLeft = Order.getAmountLeft(theirOrderData); + LOGGER.trace("theirAmountLeft (max amount remaining in order): " + theirAmountLeft.toPlainString() + " " + wantAssetData.getName()); // So matchable want-asset amount is the minimum of above two values BigDecimal matchedAmount = ourAmountLeft.min(theirAmountLeft); + LOGGER.trace("matchedAmount: " + matchedAmount.toPlainString() + " " + wantAssetData.getName()); // If we can't buy anything then we're done if (matchedAmount.compareTo(BigDecimal.ZERO) <= 0) @@ -155,7 +175,9 @@ public class Order { // Calculate amount granularity based on both assets' divisibility BigDecimal increment = this.calculateAmountGranularity(haveAssetData, wantAssetData, theirOrderData); + LOGGER.trace("increment (want-asset amount granularity): " + increment.toPlainString() + " " + wantAssetData.getName()); matchedAmount = matchedAmount.subtract(matchedAmount.remainder(increment)); + LOGGER.trace("matchedAmount adjusted for granularity: " + matchedAmount.toPlainString() + " " + wantAssetData.getName()); // If we can't buy anything then we're done if (matchedAmount.compareTo(BigDecimal.ZERO) <= 0) @@ -165,6 +187,7 @@ public class Order { // Calculate the total cost to us, in have-asset, based on their price BigDecimal tradePrice = matchedAmount.multiply(theirOrderData.getPrice()).setScale(8); + LOGGER.trace("tradePrice ('want' trade agreed): " + tradePrice.toPlainString() + " " + haveAssetData.getName()); // Construct trade TradeData tradeData = new TradeData(this.orderData.getOrderId(), theirOrderData.getOrderId(), matchedAmount, tradePrice, @@ -175,8 +198,12 @@ public class Order { // 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(tradePrice)); + LOGGER.trace("Updated our order's fulfilled amount to: " + this.orderData.getFulfilled().toPlainString() + " " + haveAssetData.getName()); + LOGGER.trace("Our order's amount remaining: " + this.getAmountLeft().toPlainString() + " " + haveAssetData.getName()); - // Continue on to process other open orders in case we still have amount left to match + // Continue on to process other open orders if we still have amount left to match + if (this.getAmountLeft().compareTo(BigDecimal.ZERO) <= 0) + break; } } diff --git a/src/qora/block/Block.java b/src/qora/block/Block.java index 16110038..22c49bf6 100644 --- a/src/qora/block/Block.java +++ b/src/qora/block/Block.java @@ -10,6 +10,9 @@ import java.util.Arrays; import java.util.List; import java.util.Map; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + import com.google.common.primitives.Bytes; import data.block.BlockData; @@ -80,6 +83,7 @@ public class Block { protected PublicKeyAccount generator; // Other properties + private static final Logger LOGGER = LogManager.getLogger(Block.class); protected List transactions; protected BigDecimal cachedNextGeneratingBalance; @@ -565,7 +569,7 @@ public class Block { // NOTE: in Gen1 there was an extra block height passed to DeployATTransaction.isValid Transaction.ValidationResult validationResult = transaction.isValid(); if (validationResult != Transaction.ValidationResult.OK) { - System.err.println("Error during transaction validation, tx " + Base58.encode(transaction.getTransactionData().getSignature()) + ": " + LOGGER.error("Error during transaction validation, tx " + Base58.encode(transaction.getTransactionData().getSignature()) + ": " + validationResult.value); return ValidationResult.TRANSACTION_INVALID; } @@ -574,9 +578,7 @@ public class Block { try { transaction.process(); } catch (Exception e) { - // LOGGER.error("Exception during transaction validation, tx " + Base58.encode(transaction.getSignature()), e); - System.err.println("Exception during transaction validation, tx " + Base58.encode(transaction.getTransactionData().getSignature()) + ": " - + e.getMessage()); + LOGGER.error("Exception during transaction validation, tx " + Base58.encode(transaction.getTransactionData().getSignature()), e); e.printStackTrace(); return ValidationResult.TRANSACTION_PROCESSING_FAILED; } diff --git a/src/qora/payment/Payment.java b/src/qora/payment/Payment.java index 7625a981..fbf0899f 100644 --- a/src/qora/payment/Payment.java +++ b/src/qora/payment/Payment.java @@ -94,7 +94,8 @@ public class Payment { return isValid(senderPublicKey, paymentData, fee, false); } - public void process(byte[] senderPublicKey, List payments, BigDecimal fee, byte[] signature) throws DataException { + public void process(byte[] senderPublicKey, List payments, BigDecimal fee, byte[] signature, boolean alwaysInitializeRecipientReference) + throws DataException { Account sender = new PublicKeyAccount(this.repository, senderPublicKey); // Update sender's balance due to fee @@ -117,16 +118,18 @@ public class Payment { recipient.setConfirmedBalance(assetId, recipient.getConfirmedBalance(assetId).add(amount)); // For QORA amounts only: if recipient has no reference yet, then this is their starting reference - if (assetId == Asset.QORA && recipient.getLastReference() == null) + if ((alwaysInitializeRecipientReference || assetId == Asset.QORA) && recipient.getLastReference() == null) recipient.setLastReference(signature); } } - public void process(byte[] senderPublicKey, PaymentData paymentData, BigDecimal fee, byte[] signature) throws DataException { - process(senderPublicKey, Collections.singletonList(paymentData), fee, signature); + public void process(byte[] senderPublicKey, PaymentData paymentData, BigDecimal fee, byte[] signature, boolean alwaysInitializeRecipientReference) + throws DataException { + process(senderPublicKey, Collections.singletonList(paymentData), fee, signature, alwaysInitializeRecipientReference); } - public void orphan(byte[] senderPublicKey, List payments, BigDecimal fee, byte[] signature, byte[] reference) throws DataException { + public void orphan(byte[] senderPublicKey, List payments, BigDecimal fee, byte[] signature, byte[] reference, + boolean alwaysUninitializeRecipientReference) throws DataException { Account sender = new PublicKeyAccount(this.repository, senderPublicKey); // Update sender's balance due to fee @@ -152,13 +155,14 @@ public class Payment { * For QORA amounts only: If recipient's last reference is this transaction's signature, then they can't have made any transactions of their own * (which would have changed their last reference) thus this is their first reference so remove it. */ - if (assetId == Asset.QORA && Arrays.equals(recipient.getLastReference(), signature)) + if ((alwaysUninitializeRecipientReference || assetId == Asset.QORA) && Arrays.equals(recipient.getLastReference(), signature)) recipient.setLastReference(null); } } - public void orphan(byte[] senderPublicKey, PaymentData paymentData, BigDecimal fee, byte[] signature, byte[] reference) throws DataException { - orphan(senderPublicKey, Collections.singletonList(paymentData), fee, signature, reference); + public void orphan(byte[] senderPublicKey, PaymentData paymentData, BigDecimal fee, byte[] signature, byte[] reference, + boolean alwaysUninitializeRecipientReference) throws DataException { + orphan(senderPublicKey, Collections.singletonList(paymentData), fee, signature, reference, alwaysUninitializeRecipientReference); } } diff --git a/src/qora/transaction/ArbitraryTransaction.java b/src/qora/transaction/ArbitraryTransaction.java index 8fa87ebd..92d7f07b 100644 --- a/src/qora/transaction/ArbitraryTransaction.java +++ b/src/qora/transaction/ArbitraryTransaction.java @@ -166,9 +166,9 @@ public class ArbitraryTransaction extends Transaction { // Save this transaction itself this.repository.getTransactionRepository().save(this.transactionData); - // Wrap and delegate payment processing to Payment class + // Wrap and delegate payment processing to Payment class. Always update recipients' last references regardless of asset. new Payment(this.repository).process(arbitraryTransactionData.getSenderPublicKey(), arbitraryTransactionData.getPayments(), - arbitraryTransactionData.getFee(), arbitraryTransactionData.getSignature()); + arbitraryTransactionData.getFee(), arbitraryTransactionData.getSignature(), true); } @Override @@ -190,9 +190,9 @@ public class ArbitraryTransaction extends Transaction { // Delete this transaction itself this.repository.getTransactionRepository().delete(this.transactionData); - // Wrap and delegate payment processing to Payment class + // Wrap and delegate payment processing to Payment class. Always revert recipients' last references regardless of asset. new Payment(this.repository).orphan(arbitraryTransactionData.getSenderPublicKey(), arbitraryTransactionData.getPayments(), - arbitraryTransactionData.getFee(), arbitraryTransactionData.getSignature(), arbitraryTransactionData.getReference()); + arbitraryTransactionData.getFee(), arbitraryTransactionData.getSignature(), arbitraryTransactionData.getReference(), true); } } diff --git a/src/qora/transaction/MessageTransaction.java b/src/qora/transaction/MessageTransaction.java index a23b933c..b01dcd3b 100644 --- a/src/qora/transaction/MessageTransaction.java +++ b/src/qora/transaction/MessageTransaction.java @@ -119,9 +119,9 @@ public class MessageTransaction extends Transaction { // Save this transaction itself this.repository.getTransactionRepository().save(this.transactionData); - // Wrap and delegate payment processing to Payment class + // Wrap and delegate payment processing to Payment class. Only update recipient's last reference if transferring QORA. new Payment(this.repository).process(messageTransactionData.getSenderPublicKey(), getPaymentData(), messageTransactionData.getFee(), - messageTransactionData.getSignature()); + messageTransactionData.getSignature(), false); } @Override @@ -129,9 +129,9 @@ public class MessageTransaction extends Transaction { // Delete this transaction itself this.repository.getTransactionRepository().delete(this.transactionData); - // Wrap and delegate payment processing to Payment class + // Wrap and delegate payment processing to Payment class. Only revert recipient's last reference if transferring QORA. new Payment(this.repository).orphan(messageTransactionData.getSenderPublicKey(), getPaymentData(), messageTransactionData.getFee(), - messageTransactionData.getSignature(), messageTransactionData.getReference()); + messageTransactionData.getSignature(), messageTransactionData.getReference(), false); } } diff --git a/src/qora/transaction/MultiPaymentTransaction.java b/src/qora/transaction/MultiPaymentTransaction.java index 321f4822..98053190 100644 --- a/src/qora/transaction/MultiPaymentTransaction.java +++ b/src/qora/transaction/MultiPaymentTransaction.java @@ -119,9 +119,9 @@ public class MultiPaymentTransaction extends Transaction { // Save this transaction itself this.repository.getTransactionRepository().save(this.transactionData); - // Wrap and delegate payment processing to Payment class + // Wrap and delegate payment processing to Payment class. Always update recipients' last references regardless of asset. new Payment(this.repository).process(multiPaymentTransactionData.getSenderPublicKey(), multiPaymentTransactionData.getPayments(), - multiPaymentTransactionData.getFee(), multiPaymentTransactionData.getSignature()); + multiPaymentTransactionData.getFee(), multiPaymentTransactionData.getSignature(), true); } @Override @@ -129,9 +129,9 @@ public class MultiPaymentTransaction extends Transaction { // Delete this transaction itself this.repository.getTransactionRepository().delete(this.transactionData); - // Wrap and delegate payment processing to Payment class + // Wrap and delegate payment processing to Payment class. Always revert recipients' last references regardless of asset. new Payment(this.repository).orphan(multiPaymentTransactionData.getSenderPublicKey(), multiPaymentTransactionData.getPayments(), - multiPaymentTransactionData.getFee(), multiPaymentTransactionData.getSignature(), multiPaymentTransactionData.getReference()); + multiPaymentTransactionData.getFee(), multiPaymentTransactionData.getSignature(), multiPaymentTransactionData.getReference(), true); } } diff --git a/src/qora/transaction/PaymentTransaction.java b/src/qora/transaction/PaymentTransaction.java index 6e1ec8d6..f7d124f9 100644 --- a/src/qora/transaction/PaymentTransaction.java +++ b/src/qora/transaction/PaymentTransaction.java @@ -91,9 +91,9 @@ public class PaymentTransaction extends Transaction { // Save this transaction itself this.repository.getTransactionRepository().save(this.transactionData); - // Wrap and delegate payment processing to Payment class + // Wrap and delegate payment processing to Payment class. Only update recipient's last reference if transferring QORA. new Payment(this.repository).process(paymentTransactionData.getSenderPublicKey(), getPaymentData(), paymentTransactionData.getFee(), - paymentTransactionData.getSignature()); + paymentTransactionData.getSignature(), false); } @Override @@ -101,9 +101,9 @@ public class PaymentTransaction extends Transaction { // Delete this transaction this.repository.getTransactionRepository().delete(this.transactionData); - // Wrap and delegate payment processing to Payment class + // Wrap and delegate payment processing to Payment class. Only revert recipient's last reference if transferring QORA. new Payment(this.repository).orphan(paymentTransactionData.getSenderPublicKey(), getPaymentData(), paymentTransactionData.getFee(), - paymentTransactionData.getSignature(), paymentTransactionData.getReference()); + paymentTransactionData.getSignature(), paymentTransactionData.getReference(), false); } } diff --git a/src/qora/transaction/TransferAssetTransaction.java b/src/qora/transaction/TransferAssetTransaction.java index 17a8ed7b..40bda07b 100644 --- a/src/qora/transaction/TransferAssetTransaction.java +++ b/src/qora/transaction/TransferAssetTransaction.java @@ -104,9 +104,9 @@ public class TransferAssetTransaction extends Transaction { // Save this transaction itself this.repository.getTransactionRepository().save(this.transactionData); - // Wrap asset transfer as a payment and delegate processing to Payment class + // Wrap asset transfer as a payment and delegate processing to Payment class. Only update recipient's last reference if transferring QORA. new Payment(this.repository).process(transferAssetTransactionData.getSenderPublicKey(), getPaymentData(), transferAssetTransactionData.getFee(), - transferAssetTransactionData.getSignature()); + transferAssetTransactionData.getSignature(), false); } @Override @@ -114,9 +114,9 @@ public class TransferAssetTransaction extends Transaction { // Delete this transaction itself this.repository.getTransactionRepository().delete(this.transactionData); - // Wrap asset transfer as a payment and delegate processing to Payment class + // Wrap asset transfer as a payment and delegate processing to Payment class. Only revert recipient's last reference if transferring QORA. new Payment(this.repository).orphan(transferAssetTransactionData.getSenderPublicKey(), getPaymentData(), transferAssetTransactionData.getFee(), - transferAssetTransactionData.getSignature(), transferAssetTransactionData.getReference()); + transferAssetTransactionData.getSignature(), transferAssetTransactionData.getReference(), false); } } diff --git a/src/qora/transaction/VoteOnPollTransaction.java b/src/qora/transaction/VoteOnPollTransaction.java index 276a0a18..83600fab 100644 --- a/src/qora/transaction/VoteOnPollTransaction.java +++ b/src/qora/transaction/VoteOnPollTransaction.java @@ -5,6 +5,9 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + import com.google.common.base.Utf8; import data.transaction.TransactionData; @@ -23,6 +26,8 @@ import repository.VotingRepository; public class VoteOnPollTransaction extends Transaction { + private static final Logger LOGGER = LogManager.getLogger(VoteOnPollTransaction.class); + // Properties private VoteOnPollTransactionData voteOnPollTransactionData; @@ -130,13 +135,18 @@ public class VoteOnPollTransaction extends Transaction { // Check for previous vote so we can save option in case of orphaning VoteOnPollData previousVoteOnPollData = votingRepository.getVote(voteOnPollTransactionData.getPollName(), voteOnPollTransactionData.getVoterPublicKey()); - if (previousVoteOnPollData != null) + if (previousVoteOnPollData != null) { voteOnPollTransactionData.setPreviousOptionIndex(previousVoteOnPollData.getOptionIndex()); + LOGGER.trace("Previous vote by " + voter.getAddress() + " on poll \"" + voteOnPollTransactionData.getPollName() + "\" was option index " + + previousVoteOnPollData.getOptionIndex()); + } // Save this transaction, now with possible previous vote this.repository.getTransactionRepository().save(voteOnPollTransactionData); // Apply vote to poll + LOGGER.trace("Vote by " + voter.getAddress() + " on poll \"" + voteOnPollTransactionData.getPollName() + "\" with option index " + + voteOnPollTransactionData.getOptionIndex()); VoteOnPollData newVoteOnPollData = new VoteOnPollData(voteOnPollTransactionData.getPollName(), voteOnPollTransactionData.getVoterPublicKey(), voteOnPollTransactionData.getOptionIndex()); votingRepository.save(newVoteOnPollData); @@ -156,11 +166,15 @@ public class VoteOnPollTransaction extends Transaction { Integer previousOptionIndex = voteOnPollTransactionData.getPreviousOptionIndex(); if (previousOptionIndex != null) { // Reinstate previous vote + LOGGER.trace("Reinstating previous vote by " + voter.getAddress() + " on poll \"" + voteOnPollTransactionData.getPollName() + + "\" with option index " + previousOptionIndex); VoteOnPollData previousVoteOnPollData = new VoteOnPollData(voteOnPollTransactionData.getPollName(), voteOnPollTransactionData.getVoterPublicKey(), previousOptionIndex); votingRepository.save(previousVoteOnPollData); } else { // Delete vote + LOGGER.trace("Deleting vote by " + voter.getAddress() + " on poll \"" + voteOnPollTransactionData.getPollName() + "\" with option index " + + voteOnPollTransactionData.getOptionIndex()); votingRepository.delete(voteOnPollTransactionData.getPollName(), voteOnPollTransactionData.getVoterPublicKey()); } diff --git a/src/repository/hsqldb/HSQLDBAssetRepository.java b/src/repository/hsqldb/HSQLDBAssetRepository.java index 79b2eb7e..1cc32de3 100644 --- a/src/repository/hsqldb/HSQLDBAssetRepository.java +++ b/src/repository/hsqldb/HSQLDBAssetRepository.java @@ -148,7 +148,8 @@ public class HSQLDBAssetRepository implements AssetRepository { 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", + + "WHERE have_asset_id = ? AND want_asset_id = ? AND is_closed = FALSE AND is_fulfilled = FALSE " + + "ORDER BY price ASC, ordered ASC", haveAssetId, wantAssetId)) { if (resultSet == null) return orders; diff --git a/src/repository/hsqldb/HSQLDBDatabaseUpdates.java b/src/repository/hsqldb/HSQLDBDatabaseUpdates.java index 4bb79da5..836b2a1e 100644 --- a/src/repository/hsqldb/HSQLDBDatabaseUpdates.java +++ b/src/repository/hsqldb/HSQLDBDatabaseUpdates.java @@ -309,7 +309,7 @@ public class HSQLDBDatabaseUpdates { + "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)"); + stmt.execute("CREATE INDEX AssetOrderMatchingIndex on AssetOrders (have_asset_id, want_asset_id, is_closed, is_fulfilled, price, ordered)"); // 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; diff --git a/src/repository/hsqldb/HSQLDBRepository.java b/src/repository/hsqldb/HSQLDBRepository.java index e4c8be7c..3081ee55 100644 --- a/src/repository/hsqldb/HSQLDBRepository.java +++ b/src/repository/hsqldb/HSQLDBRepository.java @@ -8,6 +8,9 @@ import java.sql.SQLException; import java.sql.Statement; import java.util.TimeZone; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + import repository.AccountRepository; import repository.AssetRepository; import repository.BlockRepository; @@ -20,6 +23,8 @@ import repository.hsqldb.transaction.HSQLDBTransactionRepository; public class HSQLDBRepository implements Repository { + private static final Logger LOGGER = LogManager.getLogger(HSQLDBRepository.class); + public static final TimeZone UTC = TimeZone.getTimeZone("UTC"); protected Connection connection; @@ -86,12 +91,12 @@ public class HSQLDBRepository implements Repository { try (ResultSet resultSet = stmt.getResultSet()) { if (resultSet == null || !resultSet.next()) - System.out.println("Unable to check repository status during close"); + LOGGER.warn("Unable to check repository status during close"); boolean inTransaction = resultSet.getBoolean(1); int transactionCount = resultSet.getInt(2); if (inTransaction && transactionCount != 0) - System.out.println("Uncommitted changes (" + transactionCount + ") during repository close"); + LOGGER.warn("Uncommitted changes (" + transactionCount + ") during repository close"); } // give connection back to the pool diff --git a/src/repository/hsqldb/transaction/HSQLDBIssueAssetTransactionRepository.java b/src/repository/hsqldb/transaction/HSQLDBIssueAssetTransactionRepository.java index ad417a95..f48eaac8 100644 --- a/src/repository/hsqldb/transaction/HSQLDBIssueAssetTransactionRepository.java +++ b/src/repository/hsqldb/transaction/HSQLDBIssueAssetTransactionRepository.java @@ -28,7 +28,11 @@ public class HSQLDBIssueAssetTransactionRepository extends HSQLDBTransactionRepo String description = resultSet.getString(4); long quantity = resultSet.getLong(5); boolean isDivisible = resultSet.getBoolean(6); + + // Special null-checking for asset ID Long assetId = resultSet.getLong(7); + if (resultSet.wasNull()) + assetId = null; return new IssueAssetTransactionData(assetId, issuerPublicKey, owner, assetName, description, quantity, isDivisible, fee, timestamp, reference, signature); diff --git a/src/repository/hsqldb/transaction/HSQLDBMessageTransactionRepository.java b/src/repository/hsqldb/transaction/HSQLDBMessageTransactionRepository.java index 7bfd964b..60a618ca 100644 --- a/src/repository/hsqldb/transaction/HSQLDBMessageTransactionRepository.java +++ b/src/repository/hsqldb/transaction/HSQLDBMessageTransactionRepository.java @@ -28,7 +28,12 @@ public class HSQLDBMessageTransactionRepository extends HSQLDBTransactionReposit boolean isText = resultSet.getBoolean(4); boolean isEncrypted = resultSet.getBoolean(5); BigDecimal amount = resultSet.getBigDecimal(6); + + // Special null-checking for asset ID Long assetId = resultSet.getLong(7); + if (resultSet.wasNull()) + assetId = null; + byte[] data = resultSet.getBytes(8); return new MessageTransactionData(version, senderPublicKey, recipient, assetId, amount, data, isText, isEncrypted, fee, timestamp, reference, diff --git a/src/repository/hsqldb/transaction/HSQLDBVoteOnPollTransactionRepository.java b/src/repository/hsqldb/transaction/HSQLDBVoteOnPollTransactionRepository.java index baeb5125..4b9c7030 100644 --- a/src/repository/hsqldb/transaction/HSQLDBVoteOnPollTransactionRepository.java +++ b/src/repository/hsqldb/transaction/HSQLDBVoteOnPollTransactionRepository.java @@ -24,7 +24,11 @@ public class HSQLDBVoteOnPollTransactionRepository extends HSQLDBTransactionRepo String pollName = resultSet.getString(1); int optionIndex = resultSet.getInt(2); + + // Special null-checking for previous option index Integer previousOptionIndex = resultSet.getInt(3); + if (resultSet.wasNull()) + previousOptionIndex = null; return new VoteOnPollTransactionData(creatorPublicKey, pollName, optionIndex, previousOptionIndex, fee, timestamp, reference, signature); } catch (SQLException e) { diff --git a/src/transform/transaction/CreatePollTransactionTransformer.java b/src/transform/transaction/CreatePollTransactionTransformer.java index c29ad508..464827c3 100644 --- a/src/transform/transaction/CreatePollTransactionTransformer.java +++ b/src/transform/transaction/CreatePollTransactionTransformer.java @@ -157,8 +157,6 @@ public class CreatePollTransactionTransformer extends TransactionTransformer { // Replace transaction type with incorrect Register Name value System.arraycopy(Ints.toByteArray(TransactionType.REGISTER_NAME.value), 0, bytes, 0, INT_LENGTH); - System.out.println(HashCode.fromBytes(bytes).toString()); - return bytes; } diff --git a/src/transform/transaction/TransactionTransformer.java b/src/transform/transaction/TransactionTransformer.java index 1149d67f..3732f1ee 100644 --- a/src/transform/transaction/TransactionTransformer.java +++ b/src/transform/transaction/TransactionTransformer.java @@ -4,6 +4,8 @@ import java.nio.BufferUnderflowException; import java.nio.ByteBuffer; import java.util.Arrays; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.json.simple.JSONObject; import com.google.common.hash.HashCode; @@ -18,6 +20,8 @@ import utils.Base58; public class TransactionTransformer extends Transformer { + private static final Logger LOGGER = LogManager.getLogger(TransactionTransformer.class); + protected static final int TYPE_LENGTH = INT_LENGTH; protected static final int REFERENCE_LENGTH = SIGNATURE_LENGTH; protected static final int FEE_LENGTH = BIG_DECIMAL_LENGTH; @@ -30,7 +34,7 @@ public class TransactionTransformer extends Transformer { if (bytes.length < TYPE_LENGTH) throw new TransformationException("Byte data too short to determine transaction type"); - System.out.println("v1 tx hex: " + HashCode.fromBytes(bytes).toString()); + LOGGER.trace("tx hex: " + HashCode.fromBytes(bytes).toString()); ByteBuffer byteBuffer = ByteBuffer.wrap(bytes); diff --git a/src/v1feeder.java b/src/v1feeder.java index d8c05e45..42d69f7f 100644 --- a/src/v1feeder.java +++ b/src/v1feeder.java @@ -13,6 +13,9 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + import com.google.common.primitives.Ints; import data.block.BlockData; @@ -30,6 +33,8 @@ import utils.Pair; public class v1feeder extends Thread { + private static final Logger LOGGER = LogManager.getLogger(v1feeder.class); + private static final int INACTIVITY_TIMEOUT = 60 * 1000; // milliseconds private static final int CONNECTION_TIMEOUT = 2 * 1000; // milliseconds private static final int PING_INTERVAL = 10 * 1000; // milliseconds @@ -86,11 +91,11 @@ public class v1feeder extends Thread { this.socket.connect(socketAddress, CONNECTION_TIMEOUT); break; } catch (SocketTimeoutException e) { - System.err.println("Timed out trying to connect to " + address + " - retrying"); + LOGGER.info("Timed out trying to connect to " + address + " - retrying"); Thread.sleep(1000); this.socket = null; } catch (Exception e) { - System.err.println("Failed to connect to " + address + ": " + e.getMessage()); + LOGGER.error("Failed to connect to " + address, e); return; } @@ -110,9 +115,9 @@ public class v1feeder extends Thread { // Start main communication thread this.start(); } catch (SocketException e) { - System.err.println("Failed to set socket timeout for address " + address + ": " + e.getMessage()); + LOGGER.error("Failed to set socket timeout for address " + address, e); } catch (IOException e) { - System.err.println("Failed to get output stream for address " + address + ": " + e.getMessage()); + LOGGER.error("Failed to get output stream for address " + address, e); } } @@ -145,7 +150,7 @@ public class v1feeder extends Thread { bytes.write(data); } - // System.out.println("Creating message type [" + type + "] with " + (hasId ? "id [" + id + "]" : "no id") + " and data length " + data.length); + LOGGER.trace("Creating message type [" + type + "] with " + (hasId ? "id [" + id + "]" : "no id") + " and data length " + data.length); return bytes.toByteArray(); } @@ -158,14 +163,14 @@ public class v1feeder extends Thread { } private void processMessage(int type, int id, byte[] data) throws IOException { - // System.out.println("Received message type [" + type + "] with id [" + id + "] and data length " + data.length); + LOGGER.trace("Received message type [" + type + "] with id [" + id + "] and data length " + data.length); ByteBuffer byteBuffer = ByteBuffer.wrap(data); switch (type) { case HEIGHT_TYPE: int height = byteBuffer.getInt(); - System.out.println("Peer height: " + height); + LOGGER.info("Peer height: " + height); break; case SIGNATURES_TYPE: @@ -178,7 +183,7 @@ public class v1feeder extends Thread { signatures.add(signature); } - // System.out.println("We now have " + signatures.size() + " signature(s) to process"); + LOGGER.trace("We now have " + signatures.size() + " signature(s) to process"); feederState = HAVE_HEADERS_STATE; break; @@ -191,7 +196,7 @@ public class v1feeder extends Thread { // read block and process int claimedHeight = byteBuffer.getInt(); - System.out.println("Received block allegedly at height " + claimedHeight); + LOGGER.info("Received block allegedly at height " + claimedHeight); byte[] blockBytes = new byte[byteBuffer.remaining()]; byteBuffer.get(blockBytes); @@ -201,53 +206,54 @@ public class v1feeder extends Thread { try { blockInfo = BlockTransformer.fromBytes(blockBytes); } catch (TransformationException e) { - System.err.println("Couldn't parse block bytes from peer: " + e.getMessage()); - System.exit(3); + LOGGER.error("Couldn't parse block bytes from peer", e); + throw new RuntimeException("Couldn't parse block bytes from peer", e); } try (final Repository repository = RepositoryManager.getRepository()) { Block block = new Block(repository, blockInfo.getA(), blockInfo.getB()); if (!block.isSignatureValid()) { - System.err.println("Invalid block signature"); - System.exit(4); + LOGGER.error("Invalid block signature"); + throw new RuntimeException("Invalid block signature"); } ValidationResult result = block.isValid(); if (result != ValidationResult.OK) { - System.err.println("Invalid block, validation result code: " + result.value); - System.exit(4); + LOGGER.error("Invalid block, validation result code: " + result.value); + throw new RuntimeException("Invalid block, validation result code: " + result.value); } block.process(); repository.saveChanges(); } catch (DataException e) { - System.err.println("Unable to process block: " + e.getMessage()); - e.printStackTrace(); + LOGGER.error("Unable to process block", e); + throw new RuntimeException("Unable to process block", e); } feederState = HAVE_BLOCK_STATE; break; case PING_TYPE: - // System.out.println("Sending pong for ping [" + id + "]"); + LOGGER.trace("Sending pong for ping [" + id + "]"); byte[] pongMessage = createMessage(PING_TYPE, true, id, null); sendMessage(pongMessage); break; case VERSION_TYPE: - @SuppressWarnings("unused") long timestamp = byteBuffer.getLong(); + @SuppressWarnings("unused") + long timestamp = byteBuffer.getLong(); int versionLength = byteBuffer.getInt(); byte[] versionBytes = new byte[versionLength]; byteBuffer.get(versionBytes); String version = new String(versionBytes, Charset.forName("UTF-8")); - System.out.println("Peer version info: " + version); + LOGGER.info("Peer version info: " + version); break; default: - System.out.println("Discarding message type [" + type + "] with id [" + id + "] and data length " + data.length); + LOGGER.trace("Discarding message type [" + type + "] with id [" + id + "] and data length " + data.length); } } @@ -309,7 +315,7 @@ public class v1feeder extends Thread { // Send our height try (final Repository repository = RepositoryManager.getRepository()) { int height = repository.getBlockRepository().getBlockchainHeight(); - System.out.println("Sending our height " + height + " to peer"); + LOGGER.trace("Sending our height " + height + " to peer"); byte[] heightMessage = createMessage(HEIGHT_TYPE, false, null, Ints.toByteArray(height)); sendMessage(heightMessage); } @@ -321,7 +327,7 @@ public class v1feeder extends Thread { int numRead = in.read(buffer, bufferEnd, in.available()); if (numRead == -1) { // input EOF - System.out.println("Socket EOF"); + LOGGER.info("Socket EOF"); return; } @@ -353,11 +359,11 @@ public class v1feeder extends Thread { // done? if (signature == null) { - System.out.println("No last block in repository?"); + LOGGER.warn("No last block in repository?"); return; } - System.out.println("Requesting more signatures..."); + LOGGER.trace("Requesting more signatures..."); byte[] getSignaturesMessage = createMessage(GET_SIGNATURES_TYPE, true, null, signature); sendMessage(getSignaturesMessage); feederState = AWAITING_HEADERS_STATE; @@ -371,7 +377,7 @@ public class v1feeder extends Thread { break; } - System.out.println("Requesting next block..."); + LOGGER.trace("Requesting next block..."); signature = signatures.remove(0); this.messageId = (int) ((Math.random() * 1000000) + 1); byte[] getBlockMessage = createMessage(GET_BLOCK_TYPE, true, this.messageId, signature); @@ -380,9 +386,9 @@ public class v1feeder extends Thread { break; } } - } catch (IOException | DataException e) { + } catch (IOException | DataException | RuntimeException e) { // give up - System.err.println("Exiting due to: " + e.getMessage()); + LOGGER.info("Exiting", e); } try { @@ -401,14 +407,14 @@ public class v1feeder extends Thread { try { test.Common.setRepository(); } catch (DataException e) { - System.err.println("Couldn't connect to repository: " + e.getMessage()); + LOGGER.error("Couldn't connect to repository", e); System.exit(2); } try { BlockChain.validate(); } catch (DataException e) { - System.err.println("Couldn't validate repository: " + e.getMessage()); + LOGGER.error("Couldn't validate repository", e); System.exit(2); } @@ -422,7 +428,7 @@ public class v1feeder extends Thread { e.printStackTrace(); } - System.out.println("Exiting v1feeder"); + LOGGER.info("Exiting v1feeder"); try { test.Common.closeRepository();