Fix issues with payments, asset trades and voting.

Some payments don't always initialize the recipient's
last reference, depending on whether the asset is QORA or not and
what type of transaction is being processed.
Calls to Payment.process/orphan now take boolean to indicate
whether recipient's last reference should always be initialized
regardless of asset.
("initialized" here means setting an initial last reference for
an account if the current value is null/empty/missing)

When matching orders to produce trades, it isn't enough to only
sort orders with best price first. Orders with the same price also
need to be further sorted by earliest order first. (This was
implicitly done in Qora v1). Added additional ORDER BY sub-clause
to achieve this and improved the corresponding table index too.

Converted a lot of logging from simplistic System.out/err.println
to Apache log4j2. Added several "trace"-level logging statements
to aid debugging which can be activated given appropriate
configuration in log4j2.properties.

With voting, detection of previous votes was broken during orphan
as previousOptionIndex was set to 0 instead of null when fetching
a VoteOnPollTransaction from the HSQLDB repository. This was due
to lack of null-checking - now fixed.
Corresponding changes made in IssueAsset and Message
transaction HSQLDB repository classes.

v1feeder now syncs up to block 99055. Block 99056 contains DeployAT.
Orphaning back to block 1 and then resync works without issue too.
This commit is contained in:
catbref 2018-08-08 17:02:41 +01:00
parent e56d8f5e02
commit ad250e57c8
19 changed files with 154 additions and 73 deletions

View File

@ -2,6 +2,9 @@ package qora.account;
import java.math.BigDecimal; import java.math.BigDecimal;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import data.account.AccountBalanceData; import data.account.AccountBalanceData;
import data.account.AccountData; import data.account.AccountData;
import data.block.BlockData; import data.block.BlockData;
@ -15,6 +18,8 @@ import repository.Repository;
public class Account { public class Account {
private static final Logger LOGGER = LogManager.getLogger(Account.class);
public static final int ADDRESS_LENGTH = 25; public static final int ADDRESS_LENGTH = 25;
protected Repository repository; protected Repository repository;
@ -128,6 +133,8 @@ public class Account {
public void setConfirmedBalance(long assetId, BigDecimal balance) throws DataException { public void setConfirmedBalance(long assetId, BigDecimal balance) throws DataException {
AccountBalanceData accountBalanceData = new AccountBalanceData(this.accountData.getAddress(), assetId, balance); AccountBalanceData accountBalanceData = new AccountBalanceData(this.accountData.getAddress(), assetId, balance);
this.repository.getAccountRepository().save(accountBalanceData); this.repository.getAccountRepository().save(accountBalanceData);
LOGGER.trace(this.accountData.getAddress() + " balance now: " + balance.toPlainString() + " [assetId " + assetId + "]");
} }
public void deleteBalance(long assetId) throws DataException { public void deleteBalance(long assetId) throws DataException {

View File

@ -5,6 +5,11 @@ import java.math.BigInteger;
import java.math.RoundingMode; import java.math.RoundingMode;
import java.util.List; 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.AssetData;
import data.assets.OrderData; import data.assets.OrderData;
import data.assets.TradeData; import data.assets.TradeData;
@ -16,6 +21,8 @@ import repository.Repository;
public class Order { public class Order {
private static final Logger LOGGER = LogManager.getLogger(Order.class);
// Properties // Properties
private Repository repository; private Repository repository;
private OrderData orderData; private OrderData orderData;
@ -104,10 +111,14 @@ public class Order {
this.repository.getAssetRepository().save(this.orderData); this.repository.getAssetRepository().save(this.orderData);
// Attempt to match orders // 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. // Fetch corresponding open orders that might potentially match, hence reversed want/have assetId args.
// Returned orders are sorted with lowest "price" first. // Returned orders are sorted with lowest "price" first.
List<OrderData> orders = assetRepository.getOpenOrders(wantAssetId, haveAssetId); List<OrderData> orders = assetRepository.getOpenOrders(wantAssetId, haveAssetId);
LOGGER.trace("Open orders fetched from repository: " + orders.size());
/* /*
* Our order example: * Our order example:
@ -123,6 +134,11 @@ public class Order {
BigDecimal ourPrice = this.orderData.getPrice(); BigDecimal ourPrice = this.orderData.getPrice();
for (OrderData theirOrderData : orders) { 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: * Potential matching order example:
* *
@ -137,6 +153,7 @@ public class Order {
// Round down otherwise their buyingPrice would be better than advertised and cause issues // Round down otherwise their buyingPrice would be better than advertised and cause issues
BigDecimal theirBuyingPrice = BigDecimal.ONE.setScale(8).divide(theirOrderData.getPrice(), RoundingMode.DOWN); 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 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) if (theirBuyingPrice.compareTo(ourPrice) < 0)
@ -144,10 +161,13 @@ public class Order {
// Calculate how many want-asset we could buy at their price // Calculate how many want-asset we could buy at their price
BigDecimal ourAmountLeft = this.getAmountLeft().multiply(theirBuyingPrice).setScale(8, RoundingMode.DOWN); 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); 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 // So matchable want-asset amount is the minimum of above two values
BigDecimal matchedAmount = ourAmountLeft.min(theirAmountLeft); BigDecimal matchedAmount = ourAmountLeft.min(theirAmountLeft);
LOGGER.trace("matchedAmount: " + matchedAmount.toPlainString() + " " + wantAssetData.getName());
// If we can't buy anything then we're done // If we can't buy anything then we're done
if (matchedAmount.compareTo(BigDecimal.ZERO) <= 0) if (matchedAmount.compareTo(BigDecimal.ZERO) <= 0)
@ -155,7 +175,9 @@ public class Order {
// Calculate amount granularity based on both assets' divisibility // Calculate amount granularity based on both assets' divisibility
BigDecimal increment = this.calculateAmountGranularity(haveAssetData, wantAssetData, theirOrderData); BigDecimal increment = this.calculateAmountGranularity(haveAssetData, wantAssetData, theirOrderData);
LOGGER.trace("increment (want-asset amount granularity): " + increment.toPlainString() + " " + wantAssetData.getName());
matchedAmount = matchedAmount.subtract(matchedAmount.remainder(increment)); 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 we can't buy anything then we're done
if (matchedAmount.compareTo(BigDecimal.ZERO) <= 0) 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 // Calculate the total cost to us, in have-asset, based on their price
BigDecimal tradePrice = matchedAmount.multiply(theirOrderData.getPrice()).setScale(8); BigDecimal tradePrice = matchedAmount.multiply(theirOrderData.getPrice()).setScale(8);
LOGGER.trace("tradePrice ('want' trade agreed): " + tradePrice.toPlainString() + " " + haveAssetData.getName());
// Construct trade // Construct trade
TradeData tradeData = new TradeData(this.orderData.getOrderId(), theirOrderData.getOrderId(), matchedAmount, tradePrice, 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 // 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)); 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;
} }
} }

View File

@ -10,6 +10,9 @@ import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import com.google.common.primitives.Bytes; import com.google.common.primitives.Bytes;
import data.block.BlockData; import data.block.BlockData;
@ -80,6 +83,7 @@ public class Block {
protected PublicKeyAccount generator; protected PublicKeyAccount generator;
// Other properties // Other properties
private static final Logger LOGGER = LogManager.getLogger(Block.class);
protected List<Transaction> transactions; protected List<Transaction> transactions;
protected BigDecimal cachedNextGeneratingBalance; protected BigDecimal cachedNextGeneratingBalance;
@ -565,7 +569,7 @@ public class Block {
// NOTE: in Gen1 there was an extra block height passed to DeployATTransaction.isValid // NOTE: in Gen1 there was an extra block height passed to DeployATTransaction.isValid
Transaction.ValidationResult validationResult = transaction.isValid(); Transaction.ValidationResult validationResult = transaction.isValid();
if (validationResult != Transaction.ValidationResult.OK) { 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); + validationResult.value);
return ValidationResult.TRANSACTION_INVALID; return ValidationResult.TRANSACTION_INVALID;
} }
@ -574,9 +578,7 @@ public class Block {
try { try {
transaction.process(); transaction.process();
} catch (Exception e) { } catch (Exception e) {
// LOGGER.error("Exception during transaction validation, tx " + Base58.encode(transaction.getSignature()), e); LOGGER.error("Exception during transaction validation, tx " + Base58.encode(transaction.getTransactionData().getSignature()), e);
System.err.println("Exception during transaction validation, tx " + Base58.encode(transaction.getTransactionData().getSignature()) + ": "
+ e.getMessage());
e.printStackTrace(); e.printStackTrace();
return ValidationResult.TRANSACTION_PROCESSING_FAILED; return ValidationResult.TRANSACTION_PROCESSING_FAILED;
} }

View File

@ -94,7 +94,8 @@ public class Payment {
return isValid(senderPublicKey, paymentData, fee, false); return isValid(senderPublicKey, paymentData, fee, false);
} }
public void process(byte[] senderPublicKey, List<PaymentData> payments, BigDecimal fee, byte[] signature) throws DataException { public void process(byte[] senderPublicKey, List<PaymentData> payments, BigDecimal fee, byte[] signature, boolean alwaysInitializeRecipientReference)
throws DataException {
Account sender = new PublicKeyAccount(this.repository, senderPublicKey); Account sender = new PublicKeyAccount(this.repository, senderPublicKey);
// Update sender's balance due to fee // Update sender's balance due to fee
@ -117,16 +118,18 @@ public class Payment {
recipient.setConfirmedBalance(assetId, recipient.getConfirmedBalance(assetId).add(amount)); recipient.setConfirmedBalance(assetId, recipient.getConfirmedBalance(assetId).add(amount));
// For QORA amounts only: if recipient has no reference yet, then this is their starting reference // 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); recipient.setLastReference(signature);
} }
} }
public void process(byte[] senderPublicKey, PaymentData paymentData, BigDecimal fee, byte[] signature) throws DataException { public void process(byte[] senderPublicKey, PaymentData paymentData, BigDecimal fee, byte[] signature, boolean alwaysInitializeRecipientReference)
process(senderPublicKey, Collections.singletonList(paymentData), fee, signature); throws DataException {
process(senderPublicKey, Collections.singletonList(paymentData), fee, signature, alwaysInitializeRecipientReference);
} }
public void orphan(byte[] senderPublicKey, List<PaymentData> payments, BigDecimal fee, byte[] signature, byte[] reference) throws DataException { public void orphan(byte[] senderPublicKey, List<PaymentData> payments, BigDecimal fee, byte[] signature, byte[] reference,
boolean alwaysUninitializeRecipientReference) throws DataException {
Account sender = new PublicKeyAccount(this.repository, senderPublicKey); Account sender = new PublicKeyAccount(this.repository, senderPublicKey);
// Update sender's balance due to fee // 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 * 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. * (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); recipient.setLastReference(null);
} }
} }
public void orphan(byte[] senderPublicKey, PaymentData paymentData, BigDecimal fee, byte[] signature, byte[] reference) throws DataException { public void orphan(byte[] senderPublicKey, PaymentData paymentData, BigDecimal fee, byte[] signature, byte[] reference,
orphan(senderPublicKey, Collections.singletonList(paymentData), fee, signature, reference); boolean alwaysUninitializeRecipientReference) throws DataException {
orphan(senderPublicKey, Collections.singletonList(paymentData), fee, signature, reference, alwaysUninitializeRecipientReference);
} }
} }

View File

@ -166,9 +166,9 @@ public class ArbitraryTransaction extends Transaction {
// Save this transaction itself // Save this transaction itself
this.repository.getTransactionRepository().save(this.transactionData); 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(), new Payment(this.repository).process(arbitraryTransactionData.getSenderPublicKey(), arbitraryTransactionData.getPayments(),
arbitraryTransactionData.getFee(), arbitraryTransactionData.getSignature()); arbitraryTransactionData.getFee(), arbitraryTransactionData.getSignature(), true);
} }
@Override @Override
@ -190,9 +190,9 @@ public class ArbitraryTransaction extends Transaction {
// Delete this transaction itself // Delete this transaction itself
this.repository.getTransactionRepository().delete(this.transactionData); 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(), new Payment(this.repository).orphan(arbitraryTransactionData.getSenderPublicKey(), arbitraryTransactionData.getPayments(),
arbitraryTransactionData.getFee(), arbitraryTransactionData.getSignature(), arbitraryTransactionData.getReference()); arbitraryTransactionData.getFee(), arbitraryTransactionData.getSignature(), arbitraryTransactionData.getReference(), true);
} }
} }

View File

@ -119,9 +119,9 @@ public class MessageTransaction extends Transaction {
// Save this transaction itself // Save this transaction itself
this.repository.getTransactionRepository().save(this.transactionData); 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(), new Payment(this.repository).process(messageTransactionData.getSenderPublicKey(), getPaymentData(), messageTransactionData.getFee(),
messageTransactionData.getSignature()); messageTransactionData.getSignature(), false);
} }
@Override @Override
@ -129,9 +129,9 @@ public class MessageTransaction extends Transaction {
// Delete this transaction itself // Delete this transaction itself
this.repository.getTransactionRepository().delete(this.transactionData); 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(), new Payment(this.repository).orphan(messageTransactionData.getSenderPublicKey(), getPaymentData(), messageTransactionData.getFee(),
messageTransactionData.getSignature(), messageTransactionData.getReference()); messageTransactionData.getSignature(), messageTransactionData.getReference(), false);
} }
} }

View File

@ -119,9 +119,9 @@ public class MultiPaymentTransaction extends Transaction {
// Save this transaction itself // Save this transaction itself
this.repository.getTransactionRepository().save(this.transactionData); 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(), new Payment(this.repository).process(multiPaymentTransactionData.getSenderPublicKey(), multiPaymentTransactionData.getPayments(),
multiPaymentTransactionData.getFee(), multiPaymentTransactionData.getSignature()); multiPaymentTransactionData.getFee(), multiPaymentTransactionData.getSignature(), true);
} }
@Override @Override
@ -129,9 +129,9 @@ public class MultiPaymentTransaction extends Transaction {
// Delete this transaction itself // Delete this transaction itself
this.repository.getTransactionRepository().delete(this.transactionData); 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(), new Payment(this.repository).orphan(multiPaymentTransactionData.getSenderPublicKey(), multiPaymentTransactionData.getPayments(),
multiPaymentTransactionData.getFee(), multiPaymentTransactionData.getSignature(), multiPaymentTransactionData.getReference()); multiPaymentTransactionData.getFee(), multiPaymentTransactionData.getSignature(), multiPaymentTransactionData.getReference(), true);
} }
} }

View File

@ -91,9 +91,9 @@ public class PaymentTransaction extends Transaction {
// Save this transaction itself // Save this transaction itself
this.repository.getTransactionRepository().save(this.transactionData); 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(), new Payment(this.repository).process(paymentTransactionData.getSenderPublicKey(), getPaymentData(), paymentTransactionData.getFee(),
paymentTransactionData.getSignature()); paymentTransactionData.getSignature(), false);
} }
@Override @Override
@ -101,9 +101,9 @@ public class PaymentTransaction extends Transaction {
// Delete this transaction // Delete this transaction
this.repository.getTransactionRepository().delete(this.transactionData); 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(), new Payment(this.repository).orphan(paymentTransactionData.getSenderPublicKey(), getPaymentData(), paymentTransactionData.getFee(),
paymentTransactionData.getSignature(), paymentTransactionData.getReference()); paymentTransactionData.getSignature(), paymentTransactionData.getReference(), false);
} }
} }

View File

@ -104,9 +104,9 @@ public class TransferAssetTransaction extends Transaction {
// Save this transaction itself // Save this transaction itself
this.repository.getTransactionRepository().save(this.transactionData); 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(), new Payment(this.repository).process(transferAssetTransactionData.getSenderPublicKey(), getPaymentData(), transferAssetTransactionData.getFee(),
transferAssetTransactionData.getSignature()); transferAssetTransactionData.getSignature(), false);
} }
@Override @Override
@ -114,9 +114,9 @@ public class TransferAssetTransaction extends Transaction {
// Delete this transaction itself // Delete this transaction itself
this.repository.getTransactionRepository().delete(this.transactionData); 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(), new Payment(this.repository).orphan(transferAssetTransactionData.getSenderPublicKey(), getPaymentData(), transferAssetTransactionData.getFee(),
transferAssetTransactionData.getSignature(), transferAssetTransactionData.getReference()); transferAssetTransactionData.getSignature(), transferAssetTransactionData.getReference(), false);
} }
} }

View File

@ -5,6 +5,9 @@ import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import com.google.common.base.Utf8; import com.google.common.base.Utf8;
import data.transaction.TransactionData; import data.transaction.TransactionData;
@ -23,6 +26,8 @@ import repository.VotingRepository;
public class VoteOnPollTransaction extends Transaction { public class VoteOnPollTransaction extends Transaction {
private static final Logger LOGGER = LogManager.getLogger(VoteOnPollTransaction.class);
// Properties // Properties
private VoteOnPollTransactionData voteOnPollTransactionData; 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 // Check for previous vote so we can save option in case of orphaning
VoteOnPollData previousVoteOnPollData = votingRepository.getVote(voteOnPollTransactionData.getPollName(), VoteOnPollData previousVoteOnPollData = votingRepository.getVote(voteOnPollTransactionData.getPollName(),
voteOnPollTransactionData.getVoterPublicKey()); voteOnPollTransactionData.getVoterPublicKey());
if (previousVoteOnPollData != null) if (previousVoteOnPollData != null) {
voteOnPollTransactionData.setPreviousOptionIndex(previousVoteOnPollData.getOptionIndex()); 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 // Save this transaction, now with possible previous vote
this.repository.getTransactionRepository().save(voteOnPollTransactionData); this.repository.getTransactionRepository().save(voteOnPollTransactionData);
// Apply vote to poll // 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(), VoteOnPollData newVoteOnPollData = new VoteOnPollData(voteOnPollTransactionData.getPollName(), voteOnPollTransactionData.getVoterPublicKey(),
voteOnPollTransactionData.getOptionIndex()); voteOnPollTransactionData.getOptionIndex());
votingRepository.save(newVoteOnPollData); votingRepository.save(newVoteOnPollData);
@ -156,11 +166,15 @@ public class VoteOnPollTransaction extends Transaction {
Integer previousOptionIndex = voteOnPollTransactionData.getPreviousOptionIndex(); Integer previousOptionIndex = voteOnPollTransactionData.getPreviousOptionIndex();
if (previousOptionIndex != null) { if (previousOptionIndex != null) {
// Reinstate previous vote // 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(), VoteOnPollData previousVoteOnPollData = new VoteOnPollData(voteOnPollTransactionData.getPollName(), voteOnPollTransactionData.getVoterPublicKey(),
previousOptionIndex); previousOptionIndex);
votingRepository.save(previousVoteOnPollData); votingRepository.save(previousVoteOnPollData);
} else { } else {
// Delete vote // Delete vote
LOGGER.trace("Deleting vote by " + voter.getAddress() + " on poll \"" + voteOnPollTransactionData.getPollName() + "\" with option index "
+ voteOnPollTransactionData.getOptionIndex());
votingRepository.delete(voteOnPollTransactionData.getPollName(), voteOnPollTransactionData.getVoterPublicKey()); votingRepository.delete(voteOnPollTransactionData.getPollName(), voteOnPollTransactionData.getVoterPublicKey());
} }

View File

@ -148,7 +148,8 @@ public class HSQLDBAssetRepository implements AssetRepository {
try (ResultSet resultSet = this.repository.checkedExecute( try (ResultSet resultSet = this.repository.checkedExecute(
"SELECT creator, asset_order_id, amount, fulfilled, price, ordered FROM AssetOrders " "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)) { haveAssetId, wantAssetId)) {
if (resultSet == null) if (resultSet == null)
return orders; return orders;

View File

@ -309,7 +309,7 @@ public class HSQLDBDatabaseUpdates {
+ "ordered TIMESTAMP WITH TIME ZONE NOT NULL, is_closed BOOLEAN NOT NULL, is_fulfilled BOOLEAN NOT NULL, " + "ordered TIMESTAMP WITH TIME ZONE NOT NULL, is_closed BOOLEAN NOT NULL, is_fulfilled BOOLEAN NOT NULL, "
+ "PRIMARY KEY (asset_order_id))"); + "PRIMARY KEY (asset_order_id))");
// For quick matching of orders. is_closed are is_fulfilled included so inactive orders can be filtered out. // 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. // 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)"); stmt.execute("CREATE INDEX AssetOrderCreatorIndex on AssetOrders (creator, is_closed)");
break; break;

View File

@ -8,6 +8,9 @@ import java.sql.SQLException;
import java.sql.Statement; import java.sql.Statement;
import java.util.TimeZone; import java.util.TimeZone;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import repository.AccountRepository; import repository.AccountRepository;
import repository.AssetRepository; import repository.AssetRepository;
import repository.BlockRepository; import repository.BlockRepository;
@ -20,6 +23,8 @@ import repository.hsqldb.transaction.HSQLDBTransactionRepository;
public class HSQLDBRepository implements Repository { public class HSQLDBRepository implements Repository {
private static final Logger LOGGER = LogManager.getLogger(HSQLDBRepository.class);
public static final TimeZone UTC = TimeZone.getTimeZone("UTC"); public static final TimeZone UTC = TimeZone.getTimeZone("UTC");
protected Connection connection; protected Connection connection;
@ -86,12 +91,12 @@ public class HSQLDBRepository implements Repository {
try (ResultSet resultSet = stmt.getResultSet()) { try (ResultSet resultSet = stmt.getResultSet()) {
if (resultSet == null || !resultSet.next()) 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); boolean inTransaction = resultSet.getBoolean(1);
int transactionCount = resultSet.getInt(2); int transactionCount = resultSet.getInt(2);
if (inTransaction && transactionCount != 0) 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 // give connection back to the pool

View File

@ -28,7 +28,11 @@ public class HSQLDBIssueAssetTransactionRepository extends HSQLDBTransactionRepo
String description = resultSet.getString(4); String description = resultSet.getString(4);
long quantity = resultSet.getLong(5); long quantity = resultSet.getLong(5);
boolean isDivisible = resultSet.getBoolean(6); boolean isDivisible = resultSet.getBoolean(6);
// Special null-checking for asset ID
Long assetId = resultSet.getLong(7); Long assetId = resultSet.getLong(7);
if (resultSet.wasNull())
assetId = null;
return new IssueAssetTransactionData(assetId, issuerPublicKey, owner, assetName, description, quantity, isDivisible, fee, timestamp, reference, return new IssueAssetTransactionData(assetId, issuerPublicKey, owner, assetName, description, quantity, isDivisible, fee, timestamp, reference,
signature); signature);

View File

@ -28,7 +28,12 @@ public class HSQLDBMessageTransactionRepository extends HSQLDBTransactionReposit
boolean isText = resultSet.getBoolean(4); boolean isText = resultSet.getBoolean(4);
boolean isEncrypted = resultSet.getBoolean(5); boolean isEncrypted = resultSet.getBoolean(5);
BigDecimal amount = resultSet.getBigDecimal(6); BigDecimal amount = resultSet.getBigDecimal(6);
// Special null-checking for asset ID
Long assetId = resultSet.getLong(7); Long assetId = resultSet.getLong(7);
if (resultSet.wasNull())
assetId = null;
byte[] data = resultSet.getBytes(8); byte[] data = resultSet.getBytes(8);
return new MessageTransactionData(version, senderPublicKey, recipient, assetId, amount, data, isText, isEncrypted, fee, timestamp, reference, return new MessageTransactionData(version, senderPublicKey, recipient, assetId, amount, data, isText, isEncrypted, fee, timestamp, reference,

View File

@ -24,7 +24,11 @@ public class HSQLDBVoteOnPollTransactionRepository extends HSQLDBTransactionRepo
String pollName = resultSet.getString(1); String pollName = resultSet.getString(1);
int optionIndex = resultSet.getInt(2); int optionIndex = resultSet.getInt(2);
// Special null-checking for previous option index
Integer previousOptionIndex = resultSet.getInt(3); Integer previousOptionIndex = resultSet.getInt(3);
if (resultSet.wasNull())
previousOptionIndex = null;
return new VoteOnPollTransactionData(creatorPublicKey, pollName, optionIndex, previousOptionIndex, fee, timestamp, reference, signature); return new VoteOnPollTransactionData(creatorPublicKey, pollName, optionIndex, previousOptionIndex, fee, timestamp, reference, signature);
} catch (SQLException e) { } catch (SQLException e) {

View File

@ -157,8 +157,6 @@ public class CreatePollTransactionTransformer extends TransactionTransformer {
// Replace transaction type with incorrect Register Name value // Replace transaction type with incorrect Register Name value
System.arraycopy(Ints.toByteArray(TransactionType.REGISTER_NAME.value), 0, bytes, 0, INT_LENGTH); System.arraycopy(Ints.toByteArray(TransactionType.REGISTER_NAME.value), 0, bytes, 0, INT_LENGTH);
System.out.println(HashCode.fromBytes(bytes).toString());
return bytes; return bytes;
} }

View File

@ -4,6 +4,8 @@ import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.Arrays; import java.util.Arrays;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.json.simple.JSONObject; import org.json.simple.JSONObject;
import com.google.common.hash.HashCode; import com.google.common.hash.HashCode;
@ -18,6 +20,8 @@ import utils.Base58;
public class TransactionTransformer extends Transformer { 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 TYPE_LENGTH = INT_LENGTH;
protected static final int REFERENCE_LENGTH = SIGNATURE_LENGTH; protected static final int REFERENCE_LENGTH = SIGNATURE_LENGTH;
protected static final int FEE_LENGTH = BIG_DECIMAL_LENGTH; protected static final int FEE_LENGTH = BIG_DECIMAL_LENGTH;
@ -30,7 +34,7 @@ public class TransactionTransformer extends Transformer {
if (bytes.length < TYPE_LENGTH) if (bytes.length < TYPE_LENGTH)
throw new TransformationException("Byte data too short to determine transaction type"); 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); ByteBuffer byteBuffer = ByteBuffer.wrap(bytes);

View File

@ -13,6 +13,9 @@ import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import com.google.common.primitives.Ints; import com.google.common.primitives.Ints;
import data.block.BlockData; import data.block.BlockData;
@ -30,6 +33,8 @@ import utils.Pair;
public class v1feeder extends Thread { 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 INACTIVITY_TIMEOUT = 60 * 1000; // milliseconds
private static final int CONNECTION_TIMEOUT = 2 * 1000; // milliseconds private static final int CONNECTION_TIMEOUT = 2 * 1000; // milliseconds
private static final int PING_INTERVAL = 10 * 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); this.socket.connect(socketAddress, CONNECTION_TIMEOUT);
break; break;
} catch (SocketTimeoutException e) { } 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); Thread.sleep(1000);
this.socket = null; this.socket = null;
} catch (Exception e) { } catch (Exception e) {
System.err.println("Failed to connect to " + address + ": " + e.getMessage()); LOGGER.error("Failed to connect to " + address, e);
return; return;
} }
@ -110,9 +115,9 @@ public class v1feeder extends Thread {
// Start main communication thread // Start main communication thread
this.start(); this.start();
} catch (SocketException e) { } 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) { } 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); 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(); return bytes.toByteArray();
} }
@ -158,14 +163,14 @@ public class v1feeder extends Thread {
} }
private void processMessage(int type, int id, byte[] data) throws IOException { 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); ByteBuffer byteBuffer = ByteBuffer.wrap(data);
switch (type) { switch (type) {
case HEIGHT_TYPE: case HEIGHT_TYPE:
int height = byteBuffer.getInt(); int height = byteBuffer.getInt();
System.out.println("Peer height: " + height); LOGGER.info("Peer height: " + height);
break; break;
case SIGNATURES_TYPE: case SIGNATURES_TYPE:
@ -178,7 +183,7 @@ public class v1feeder extends Thread {
signatures.add(signature); 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; feederState = HAVE_HEADERS_STATE;
break; break;
@ -191,7 +196,7 @@ public class v1feeder extends Thread {
// read block and process // read block and process
int claimedHeight = byteBuffer.getInt(); 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()]; byte[] blockBytes = new byte[byteBuffer.remaining()];
byteBuffer.get(blockBytes); byteBuffer.get(blockBytes);
@ -201,53 +206,54 @@ public class v1feeder extends Thread {
try { try {
blockInfo = BlockTransformer.fromBytes(blockBytes); blockInfo = BlockTransformer.fromBytes(blockBytes);
} catch (TransformationException e) { } catch (TransformationException e) {
System.err.println("Couldn't parse block bytes from peer: " + e.getMessage()); LOGGER.error("Couldn't parse block bytes from peer", e);
System.exit(3); throw new RuntimeException("Couldn't parse block bytes from peer", e);
} }
try (final Repository repository = RepositoryManager.getRepository()) { try (final Repository repository = RepositoryManager.getRepository()) {
Block block = new Block(repository, blockInfo.getA(), blockInfo.getB()); Block block = new Block(repository, blockInfo.getA(), blockInfo.getB());
if (!block.isSignatureValid()) { if (!block.isSignatureValid()) {
System.err.println("Invalid block signature"); LOGGER.error("Invalid block signature");
System.exit(4); throw new RuntimeException("Invalid block signature");
} }
ValidationResult result = block.isValid(); ValidationResult result = block.isValid();
if (result != ValidationResult.OK) { if (result != ValidationResult.OK) {
System.err.println("Invalid block, validation result code: " + result.value); LOGGER.error("Invalid block, validation result code: " + result.value);
System.exit(4); throw new RuntimeException("Invalid block, validation result code: " + result.value);
} }
block.process(); block.process();
repository.saveChanges(); repository.saveChanges();
} catch (DataException e) { } catch (DataException e) {
System.err.println("Unable to process block: " + e.getMessage()); LOGGER.error("Unable to process block", e);
e.printStackTrace(); throw new RuntimeException("Unable to process block", e);
} }
feederState = HAVE_BLOCK_STATE; feederState = HAVE_BLOCK_STATE;
break; break;
case PING_TYPE: 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); byte[] pongMessage = createMessage(PING_TYPE, true, id, null);
sendMessage(pongMessage); sendMessage(pongMessage);
break; break;
case VERSION_TYPE: case VERSION_TYPE:
@SuppressWarnings("unused") long timestamp = byteBuffer.getLong(); @SuppressWarnings("unused")
long timestamp = byteBuffer.getLong();
int versionLength = byteBuffer.getInt(); int versionLength = byteBuffer.getInt();
byte[] versionBytes = new byte[versionLength]; byte[] versionBytes = new byte[versionLength];
byteBuffer.get(versionBytes); byteBuffer.get(versionBytes);
String version = new String(versionBytes, Charset.forName("UTF-8")); String version = new String(versionBytes, Charset.forName("UTF-8"));
System.out.println("Peer version info: " + version); LOGGER.info("Peer version info: " + version);
break; break;
default: 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 // Send our height
try (final Repository repository = RepositoryManager.getRepository()) { try (final Repository repository = RepositoryManager.getRepository()) {
int height = repository.getBlockRepository().getBlockchainHeight(); 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)); byte[] heightMessage = createMessage(HEIGHT_TYPE, false, null, Ints.toByteArray(height));
sendMessage(heightMessage); sendMessage(heightMessage);
} }
@ -321,7 +327,7 @@ public class v1feeder extends Thread {
int numRead = in.read(buffer, bufferEnd, in.available()); int numRead = in.read(buffer, bufferEnd, in.available());
if (numRead == -1) { if (numRead == -1) {
// input EOF // input EOF
System.out.println("Socket EOF"); LOGGER.info("Socket EOF");
return; return;
} }
@ -353,11 +359,11 @@ public class v1feeder extends Thread {
// done? // done?
if (signature == null) { if (signature == null) {
System.out.println("No last block in repository?"); LOGGER.warn("No last block in repository?");
return; return;
} }
System.out.println("Requesting more signatures..."); LOGGER.trace("Requesting more signatures...");
byte[] getSignaturesMessage = createMessage(GET_SIGNATURES_TYPE, true, null, signature); byte[] getSignaturesMessage = createMessage(GET_SIGNATURES_TYPE, true, null, signature);
sendMessage(getSignaturesMessage); sendMessage(getSignaturesMessage);
feederState = AWAITING_HEADERS_STATE; feederState = AWAITING_HEADERS_STATE;
@ -371,7 +377,7 @@ public class v1feeder extends Thread {
break; break;
} }
System.out.println("Requesting next block..."); LOGGER.trace("Requesting next block...");
signature = signatures.remove(0); signature = signatures.remove(0);
this.messageId = (int) ((Math.random() * 1000000) + 1); this.messageId = (int) ((Math.random() * 1000000) + 1);
byte[] getBlockMessage = createMessage(GET_BLOCK_TYPE, true, this.messageId, signature); byte[] getBlockMessage = createMessage(GET_BLOCK_TYPE, true, this.messageId, signature);
@ -380,9 +386,9 @@ public class v1feeder extends Thread {
break; break;
} }
} }
} catch (IOException | DataException e) { } catch (IOException | DataException | RuntimeException e) {
// give up // give up
System.err.println("Exiting due to: " + e.getMessage()); LOGGER.info("Exiting", e);
} }
try { try {
@ -401,14 +407,14 @@ public class v1feeder extends Thread {
try { try {
test.Common.setRepository(); test.Common.setRepository();
} catch (DataException e) { } catch (DataException e) {
System.err.println("Couldn't connect to repository: " + e.getMessage()); LOGGER.error("Couldn't connect to repository", e);
System.exit(2); System.exit(2);
} }
try { try {
BlockChain.validate(); BlockChain.validate();
} catch (DataException e) { } catch (DataException e) {
System.err.println("Couldn't validate repository: " + e.getMessage()); LOGGER.error("Couldn't validate repository", e);
System.exit(2); System.exit(2);
} }
@ -422,7 +428,7 @@ public class v1feeder extends Thread {
e.printStackTrace(); e.printStackTrace();
} }
System.out.println("Exiting v1feeder"); LOGGER.info("Exiting v1feeder");
try { try {
test.Common.closeRepository(); test.Common.closeRepository();