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 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 {

View File

@ -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;
}
}

View File

@ -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;
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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());
}

View File

@ -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;

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, "
+ "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;

View File

@ -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

View File

@ -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);

View File

@ -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,

View File

@ -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) {

View File

@ -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;
}

View File

@ -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);

View File

@ -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();