forked from Qortal/qortal
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:
parent
e56d8f5e02
commit
ad250e57c8
@ -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 {
|
||||
|
@ -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<OrderData> 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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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<Transaction> 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;
|
||||
}
|
||||
|
@ -94,7 +94,8 @@ public class Payment {
|
||||
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);
|
||||
|
||||
// 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<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);
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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());
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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,
|
||||
|
@ -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) {
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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();
|
||||
|
Loading…
Reference in New Issue
Block a user