MAJOR: Don't delete transactions when orphaning - make them unconfirmed again

Lots of edits to Transaction subclasses to change/remove 'delete'.

Corresponding extra changes to help reset some transaction fields to pre-process
state during orphaning.

Changed Block, GenesisBlock & Synchronizer to save transactions where appropriate.

Added enhanced GET_SIGNATURES_V2 network message to reduce the number of
block signatures sent over network.

Peers are now version 2 if they send a new-style build version string,
instead of using first digit from build version.
This commit is contained in:
catbref 2019-05-22 14:50:37 +01:00
parent 07e8e01865
commit a3d4cf2900
56 changed files with 410 additions and 444 deletions

View File

@ -156,6 +156,9 @@ public class Asset {
// Save reverted asset
this.repository.getAssetRepository().save(this.assetData);
// Remove reference to previous asset-changing transaction
updateAssetTransactionData.setOrphanReference(null);
}
}

View File

@ -34,6 +34,7 @@ import org.qora.repository.Repository;
import org.qora.transaction.AtTransaction;
import org.qora.transaction.GenesisTransaction;
import org.qora.transaction.Transaction;
import org.qora.transaction.Transaction.TransactionType;
import org.qora.transform.TransformationException;
import org.qora.transform.block.BlockTransformer;
import org.qora.transform.transaction.TransactionTransformer;
@ -1045,10 +1046,15 @@ public class Block {
processBlockRewards();
// Process transactions (we'll link them to this block after saving the block itself)
// AT-generated transactions are already added to our transactions so no special handling is needed here.
// AT-generated transactions are already prepended to our transactions at this point.
List<Transaction> transactions = this.getTransactions();
for (Transaction transaction : transactions)
for (Transaction transaction : transactions) {
// AT_TRANSACTIONs are created locally and need saving into repository before processing
if (transaction.getTransactionData().getType() == TransactionType.AT)
this.repository.getTransactionRepository().save(transaction.getTransactionData());
transaction.process();
}
// Give transaction fees to generator/proxy
rewardTransactionFees();
@ -1158,9 +1164,11 @@ public class Block {
transaction.getTransactionData().getSignature());
this.repository.getBlockRepository().delete(blockTransactionData);
// Add to unconfirmed pile
// XXX WE CAN'T ADD TO UNCONFIRMED AS TRANSACTION HAS BEEN DELETED BY transaction.orphan() ABOVE
// this.repository.getTransactionRepository().unconfirmTransaction(transaction.getTransactionData());
// Add to unconfirmed pile, or delete if AT_TRANSACTION
if (transaction.getTransactionData().getType() == TransactionType.AT)
this.repository.getTransactionRepository().delete(transaction.getTransactionData());
else
this.repository.getTransactionRepository().unconfirmTransaction(transaction.getTransactionData());
this.repository.getTransactionRepository().deleteParticipants(transaction.getTransactionData());
}

View File

@ -318,6 +318,10 @@ public class GenesisBlock extends Block {
this.repository.rollbackToSavepoint();
}
// Save transactions into repository ready for processing
for (Transaction transaction : this.getTransactions())
this.repository.getTransactionRepository().save(transaction.getTransactionData());
super.process();
}

View File

@ -34,6 +34,7 @@ import org.qora.network.message.GetBlockMessage;
import org.qora.network.message.GetBlockSummariesMessage;
import org.qora.network.message.GetPeersMessage;
import org.qora.network.message.GetSignaturesMessage;
import org.qora.network.message.GetSignaturesV2Message;
import org.qora.network.message.HeightMessage;
import org.qora.network.message.Message;
import org.qora.network.message.SignaturesMessage;
@ -165,6 +166,7 @@ public class Controller extends Thread {
LOGGER.info("Validating blockchain");
try {
BlockChain.validate();
LOGGER.info(String.format("Our chain height at start-up: %d", getInstance().getChainHeight()));
} catch (DataException e) {
LOGGER.error("Couldn't validate blockchain", e);
System.exit(2);
@ -415,6 +417,32 @@ public class Controller extends Thread {
}
break;
case GET_SIGNATURES_V2:
try (final Repository repository = RepositoryManager.getRepository()) {
GetSignaturesV2Message getSignaturesMessage = (GetSignaturesV2Message) message;
byte[] parentSignature = getSignaturesMessage.getParentSignature();
List<byte[]> signatures = new ArrayList<>();
do {
BlockData blockData = repository.getBlockRepository().fromReference(parentSignature);
if (blockData == null)
break;
parentSignature = blockData.getSignature();
signatures.add(parentSignature);
} while (signatures.size() < getSignaturesMessage.getNumberRequested());
Message signaturesMessage = new SignaturesMessage(signatures);
signaturesMessage.setId(message.getId());
if (!peer.sendMessage(signaturesMessage))
peer.disconnect();
} catch (DataException e) {
LOGGER.error(String.format("Repository issue while responding to %s from peer %s", message.getType().name(), peer), e);
}
break;
case GET_BLOCK:
try (final Repository repository = RepositoryManager.getRepository()) {
GetBlockMessage getBlockMessage = (GetBlockMessage) message;

View File

@ -18,12 +18,14 @@ import org.qora.network.message.BlockSummariesMessage;
import org.qora.network.message.GetBlockMessage;
import org.qora.network.message.GetBlockSummariesMessage;
import org.qora.network.message.GetSignaturesMessage;
import org.qora.network.message.GetSignaturesV2Message;
import org.qora.network.message.Message;
import org.qora.network.message.Message.MessageType;
import org.qora.network.message.SignaturesMessage;
import org.qora.repository.DataException;
import org.qora.repository.Repository;
import org.qora.repository.RepositoryManager;
import org.qora.transaction.Transaction;
public class Synchronizer {
@ -168,10 +170,11 @@ public class Synchronizer {
// Fetch, and apply, blocks from peer
byte[] signature = commonBlockData.getSignature();
while (ourHeight < peerHeight && ourHeight < commonBlockHeight + SYNC_BATCH_SIZE) {
int maxBatchHeight = commonBlockHeight + SYNC_BATCH_SIZE;
while (ourHeight < peerHeight && ourHeight < maxBatchHeight) {
// Do we need more signatures?
if (signatures.isEmpty()) {
signatures = this.getBlockSignatures(peer, signature, MAXIMUM_BLOCK_STEP);
signatures = this.getBlockSignatures(peer, signature, maxBatchHeight - ourHeight);
if (signatures == null || signatures.isEmpty()) {
LOGGER.info(String.format("Peer %s failed to respond with more block signatures after height %d", peer, ourHeight));
return SynchronizationResult.NO_REPLY;
@ -200,6 +203,10 @@ public class Synchronizer {
return SynchronizationResult.INVALID_DATA;
}
// Save transactions attached to this block
for (Transaction transaction : newBlock.getTransactions())
repository.getTransactionRepository().save(transaction.getTransactionData());
newBlock.process();
// If we've grown our blockchain then at least save progress so far
@ -316,8 +323,9 @@ public class Synchronizer {
}
private List<byte[]> getBlockSignatures(Peer peer, byte[] parentSignature, int numberRequested) {
// TODO numberRequested is v2+ feature
Message getSignaturesMessage = new GetSignaturesMessage(parentSignature);
// numberRequested is v2+ feature
Message getSignaturesMessage = peer.getVersion() >= 2 ? new GetSignaturesV2Message(parentSignature, numberRequested) : new GetSignaturesMessage(parentSignature);
// Message getSignaturesMessage = new GetSignaturesMessage(parentSignature);
Message message = peer.getResponse(getSignaturesMessage);
if (message == null || message.getType() != MessageType.SIGNATURES)

View File

@ -65,7 +65,7 @@ public class SetGroupTransactionData extends TransactionData {
return this.previousDefaultGroupId;
}
public void setPreviousDefaultGroupId(int previousDefaultGroupId) {
public void setPreviousDefaultGroupId(Integer previousDefaultGroupId) {
this.previousDefaultGroupId = previousDefaultGroupId;
}

View File

@ -361,6 +361,9 @@ public class Group {
if (Arrays.equals(groupMemberData.getReference(), updateGroupTransactionData.getSignature()))
this.deleteMember(newOwner);
}
// Remove cached reference to previous group change from transaction data
updateGroupTransactionData.setGroupReference(null);
}
/** Reverts groupData using previous values stored in referenced transaction. */
@ -485,27 +488,27 @@ public class Group {
if (joinReference != null) {
// Rebuild join-request
this.rebuildJoinRequest(member, joinReference);
} else {
// Rebuild member entry using stored transaction reference
this.rebuildMember(member, groupKickTransactionData.getMemberReference());
return;
// Revert member's defaultGroupId if necessary
Integer previousDefaultGroupId = groupKickTransactionData.getPreviousGroupId();
if (previousDefaultGroupId != null) {
Account memberAccount = new Account(this.repository, member);
memberAccount.setDefaultGroupId(previousDefaultGroupId);
}
if (groupKickTransactionData.getAdminReference() != null)
// Rebuild admin entry using stored transaction reference
this.rebuildAdmin(member, groupKickTransactionData.getAdminReference());
}
// Rebuild member entry using stored transaction reference
this.rebuildMember(member, groupKickTransactionData.getMemberReference());
// Revert member's defaultGroupId if necessary
Integer previousDefaultGroupId = groupKickTransactionData.getPreviousGroupId();
if (previousDefaultGroupId != null) {
Account memberAccount = new Account(this.repository, member);
memberAccount.setDefaultGroupId(previousDefaultGroupId);
}
if (groupKickTransactionData.getAdminReference() != null)
// Rebuild admin entry using stored transaction reference
this.rebuildAdmin(member, groupKickTransactionData.getAdminReference());
// Clean cached references to transactions used to rebuild member/admin info
groupKickTransactionData.setMemberReference(null);
groupKickTransactionData.setAdminReference(null);
groupKickTransactionData.setJoinReference(null);
groupKickTransactionData.setPreviousGroupId(null);
}
public void ban(GroupBanTransactionData groupBanTransactionData) throws DataException {
@ -622,6 +625,12 @@ public class Group {
}
}
}
// Remove any group-related references from transaction data
groupBanTransactionData.setMemberReference(null);
groupBanTransactionData.setAdminReference(null);
groupBanTransactionData.setJoinInviteReference(null);
groupBanTransactionData.setPreviousGroupId(null);
}
public void cancelBan(CancelGroupBanTransactionData groupUnbanTransactionData) throws DataException {
@ -697,6 +706,10 @@ public class Group {
// Delete invite
this.deleteInvite(invitee);
// Clear cached references
groupInviteTransactionData.setJoinReference(null);
groupInviteTransactionData.setPreviousGroupId(null);
}
public void cancelInvite(CancelGroupInviteTransactionData cancelGroupInviteTransactionData) throws DataException {
@ -769,27 +782,29 @@ public class Group {
if (inviteReference == null && !groupData.getIsOpen()) {
// Delete join request
this.deleteJoinRequest(joiner.getAddress());
} else {
// Any invite to rebuild?
if (inviteReference != null) {
// Rebuild invite using cache reference to invite transaction
TransactionData transactionData = this.repository.getTransactionRepository().fromSignature(inviteReference);
this.addInvite((GroupInviteTransactionData) transactionData);
return;
// Clear cached reference to invite transaction
joinGroupTransactionData.setInviteReference(null);
}
// Delete member
this.deleteMember(joiner.getAddress());
// Revert joiner's defaultGroupId if necessary
Integer previousDefaultGroupId = joinGroupTransactionData.getPreviousGroupId();
if (previousDefaultGroupId != null)
joiner.setDefaultGroupId(previousDefaultGroupId);
}
// Any invite to rebuild?
if (inviteReference != null) {
// Rebuild invite using cache reference to invite transaction
TransactionData transactionData = this.repository.getTransactionRepository().fromSignature(inviteReference);
this.addInvite((GroupInviteTransactionData) transactionData);
// Clear cached reference to invite transaction
joinGroupTransactionData.setInviteReference(null);
}
// Delete member
this.deleteMember(joiner.getAddress());
// Revert joiner's defaultGroupId if necessary
Integer previousDefaultGroupId = joinGroupTransactionData.getPreviousGroupId();
if (previousDefaultGroupId != null)
joiner.setDefaultGroupId(previousDefaultGroupId);
// Clear cached references
joinGroupTransactionData.setInviteReference(null);
joinGroupTransactionData.setPreviousGroupId(null);
}
public void leave(LeaveGroupTransactionData leaveGroupTransactionData) throws DataException {
@ -842,6 +857,11 @@ public class Group {
Integer previousDefaultGroupId = leaveGroupTransactionData.getPreviousGroupId();
if (previousDefaultGroupId != null)
leaver.setDefaultGroupId(previousDefaultGroupId);
// Clear cached references
leaveGroupTransactionData.setAdminReference(null);
leaveGroupTransactionData.setMemberReference(null);
leaveGroupTransactionData.setPreviousGroupId(null);
}
}

View File

@ -113,6 +113,9 @@ public class Name {
// Save reverted name data
this.repository.getNameRepository().save(this.nameData);
// Remove reference to previous name-changing transaction
updateNameTransactionData.setNameReference(null);
}
public void sell(SellNameTransactionData sellNameTransactionData) throws DataException {
@ -133,7 +136,7 @@ public class Name {
this.repository.getNameRepository().save(this.nameData);
}
public void sell(CancelSellNameTransactionData cancelSellNameTransactionData) throws DataException {
public void cancelSell(CancelSellNameTransactionData cancelSellNameTransactionData) throws DataException {
// Mark not for-sale but leave price in case we want to orphan
this.nameData.setIsForSale(false);
@ -141,7 +144,7 @@ public class Name {
this.repository.getNameRepository().save(this.nameData);
}
public void unsell(CancelSellNameTransactionData cancelSellNameTransactionData) throws DataException {
public void uncancelSell(CancelSellNameTransactionData cancelSellNameTransactionData) throws DataException {
// Mark as for-sale using existing price
this.nameData.setIsForSale(true);
@ -180,6 +183,9 @@ public class Name {
// Previous name reference is taken from this transaction's cached copy
this.nameData.setReference(buyNameTransactionData.getNameReference());
// Remove reference in transaction data
buyNameTransactionData.setNameReference(null);
// Revert buyer's balance
Account buyer = new PublicKeyAccount(this.repository, buyNameTransactionData.getBuyerPublicKey());
buyer.setConfirmedBalance(Asset.QORA, buyer.getConfirmedBalance(Asset.QORA).add(buyNameTransactionData.getAmount()));

View File

@ -99,14 +99,9 @@ public class Peer implements Runnable {
this.versionMessage = versionMessage;
if (this.versionMessage.getVersionString().startsWith(Controller.VERSION_PREFIX)) {
int index = Controller.VERSION_PREFIX.length();
try {
this.version = Integer.parseInt(this.versionMessage.getVersionString().substring(index, index + 1));
} catch (NumberFormatException e) {
this.version = 1;
}
this.version = 2; // enhanced protocol
} else {
this.version = 1;
this.version = 1; // legacy protocol
}
}

View File

@ -0,0 +1,67 @@
package org.qora.network.message;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import org.qora.transform.Transformer;
import org.qora.transform.block.BlockTransformer;
import com.google.common.primitives.Ints;
public class GetSignaturesV2Message extends Message {
private static final int BLOCK_SIGNATURE_LENGTH = BlockTransformer.BLOCK_SIGNATURE_LENGTH;
private static final int NUMBER_REQUESTED_LENGTH = Transformer.INT_LENGTH;
private byte[] parentSignature;
private int numberRequested;
public GetSignaturesV2Message(byte[] parentSignature, int numberRequested) {
this(-1, parentSignature, numberRequested);
}
private GetSignaturesV2Message(int id, byte[] parentSignature, int numberRequested) {
super(id, MessageType.GET_SIGNATURES_V2);
this.parentSignature = parentSignature;
this.numberRequested = numberRequested;
}
public byte[] getParentSignature() {
return this.parentSignature;
}
public int getNumberRequested() {
return this.numberRequested;
}
public static Message fromByteBuffer(int id, ByteBuffer bytes) throws UnsupportedEncodingException {
if (bytes.remaining() != BLOCK_SIGNATURE_LENGTH + NUMBER_REQUESTED_LENGTH)
return null;
byte[] parentSignature = new byte[BLOCK_SIGNATURE_LENGTH];
bytes.get(parentSignature);
int numberRequested = bytes.getInt();
return new GetSignaturesV2Message(id, parentSignature, numberRequested);
}
@Override
protected byte[] toData() {
try {
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
bytes.write(this.parentSignature);
bytes.write(Ints.toByteArray(this.numberRequested));
return bytes.toByteArray();
} catch (IOException e) {
return null;
}
}
}

View File

@ -42,7 +42,8 @@ public abstract class Message {
PROOF(12),
PEERS_V2(13),
GET_BLOCK_SUMMARIES(14),
BLOCK_SUMMARIES(15);
BLOCK_SUMMARIES(15),
GET_SIGNATURES_V2(16);
public final int value;
public final Method fromByteBuffer;

View File

@ -129,8 +129,9 @@ public class AccountFlagsTransaction extends Transaction {
else
target.setFlags(previousFlags);
// Delete this transaction itself
this.repository.getTransactionRepository().delete(accountFlagsTransactionData);
// Remove previous flags from transaction itself
accountFlagsTransactionData.setPreviousFlags(null);
this.repository.getTransactionRepository().save(accountFlagsTransactionData);
Account creator = getCreator();

View File

@ -121,8 +121,7 @@ public class AddGroupAdminTransaction extends Transaction {
Group group = new Group(this.repository, addGroupAdminTransactionData.getGroupId());
group.promoteToAdmin(addGroupAdminTransactionData);
// Save this transaction
this.repository.getTransactionRepository().save(addGroupAdminTransactionData);
// We would save updated transaction at this point, but it hasn't been modified
// Update owner's balance
Account owner = getOwner();
@ -138,8 +137,7 @@ public class AddGroupAdminTransaction extends Transaction {
Group group = new Group(this.repository, addGroupAdminTransactionData.getGroupId());
group.unpromoteToAdmin(addGroupAdminTransactionData);
// Delete this transaction itself
this.repository.getTransactionRepository().delete(addGroupAdminTransactionData);
// We would save updated transaction at this point, but it hasn't been modified
// Update owner's balance
Account owner = getOwner();

View File

@ -115,14 +115,7 @@ public class ArbitraryTransaction extends Transaction {
@Override
public void process() throws DataException {
/*
* Save the transaction.
*
* We might have either raw data or only a hash of data, depending on content filtering.
* If we have raw data then the repository save will store the raw data somewhere and save the data's hash in the repository.
* This also modifies the passed transactionData.
*/
this.repository.getTransactionRepository().save(this.transactionData);
// We would save updated transaction at this point, but it hasn't been modified
// 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(),
@ -131,16 +124,11 @@ public class ArbitraryTransaction extends Transaction {
@Override
public void orphan() throws DataException {
/*
* Delete the transaction.
*
* The repository will also remove the stored raw data, if present.
*/
this.repository.getTransactionRepository().delete(this.transactionData);
// 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(), true);
// We would save transaction in orphaned form at this point, but it hasn't been modified
}
// Data access

View File

@ -159,8 +159,7 @@ public class AtTransaction extends Transaction {
@Override
public void process() throws DataException {
// Save this transaction itself
this.repository.getTransactionRepository().save(this.transactionData);
// We would save updated transaction at this point, but it hasn't been modified
if (this.atTransactionData.getAmount() != null) {
Account sender = getATAccount();
@ -185,9 +184,6 @@ public class AtTransaction extends Transaction {
@Override
public void orphan() throws DataException {
// Delete this transaction
this.repository.getTransactionRepository().delete(this.transactionData);
if (this.atTransactionData.getAmount() != null) {
Account sender = getATAccount();
Account recipient = getRecipient();
@ -208,6 +204,10 @@ public class AtTransaction extends Transaction {
if (assetId == Asset.QORA && Arrays.equals(recipient.getLastReference(), this.atTransactionData.getSignature()))
recipient.setLastReference(null);
}
// We would save updated transaction at this point, but it hasn't been modified
// As AT_TRANSACTIONs are really part of a block, the caller (Block) will probably delete this transaction after orphaning
}
}

View File

@ -127,7 +127,7 @@ public class BuyNameTransaction extends Transaction {
Name name = new Name(this.repository, buyNameTransactionData.getName());
name.buy(buyNameTransactionData);
// Save this transaction, now with updated "name reference" to previous transaction that updated name
// Save transaction with updated "name reference" pointing to previous transaction that updated name
this.repository.getTransactionRepository().save(buyNameTransactionData);
// Update buyer's balance
@ -144,8 +144,8 @@ public class BuyNameTransaction extends Transaction {
Name name = new Name(this.repository, buyNameTransactionData.getName());
name.unbuy(buyNameTransactionData);
// Delete this transaction itself
this.repository.getTransactionRepository().delete(buyNameTransactionData);
// Save this transaction, with removed "name reference"
this.repository.getTransactionRepository().save(buyNameTransactionData);
// Update buyer's balance
Account buyer = getBuyer();

View File

@ -101,8 +101,7 @@ public class CancelAssetOrderTransaction extends Transaction {
public void process() throws DataException {
Account creator = getCreator();
// Save this transaction itself
this.repository.getTransactionRepository().save(this.transactionData);
// We would save updated transaction at this point, but it hasn't been modified
// Update creator's balance regarding fee
creator.setConfirmedBalance(Asset.QORA, creator.getConfirmedBalance(Asset.QORA).subtract(cancelOrderTransactionData.getFee()));
@ -120,8 +119,7 @@ public class CancelAssetOrderTransaction extends Transaction {
public void orphan() throws DataException {
Account creator = getCreator();
// Save this transaction itself
this.repository.getTransactionRepository().delete(this.transactionData);
// We would save transaction in orphaned form at this point, but it hasn't been modified
// Update creator's balance regarding fee
creator.setConfirmedBalance(Asset.QORA, creator.getConfirmedBalance(Asset.QORA).add(cancelOrderTransactionData.getFee()));

View File

@ -134,8 +134,8 @@ public class CancelGroupBanTransaction extends Transaction {
Group group = new Group(this.repository, groupUnbanTransactionData.getGroupId());
group.uncancelBan(groupUnbanTransactionData);
// Delete this transaction itself
this.repository.getTransactionRepository().delete(groupUnbanTransactionData);
// Save this transaction with removed member/admin references
this.repository.getTransactionRepository().save(groupUnbanTransactionData);
// Update admin's balance
Account admin = getAdmin();

View File

@ -134,8 +134,8 @@ public class CancelGroupInviteTransaction extends Transaction {
Group group = new Group(this.repository, cancelGroupInviteTransactionData.getGroupId());
group.uncancelInvite(cancelGroupInviteTransactionData);
// Delete this transaction itself
this.repository.getTransactionRepository().delete(cancelGroupInviteTransactionData);
// Save this transaction with removed member/admin references
this.repository.getTransactionRepository().save(cancelGroupInviteTransactionData);
// Update admin's balance
Account admin = getAdmin();

View File

@ -112,9 +112,9 @@ public class CancelSellNameTransaction extends Transaction {
public void process() throws DataException {
// Update Name
Name name = new Name(this.repository, cancelSellNameTransactionData.getName());
name.sell(cancelSellNameTransactionData);
name.cancelSell(cancelSellNameTransactionData);
// Save this transaction, now with updated "name reference" to previous transaction that updated name
// Save this transaction, with updated "name reference" to previous transaction that updated name
this.repository.getTransactionRepository().save(cancelSellNameTransactionData);
// Update owner's balance
@ -129,10 +129,10 @@ public class CancelSellNameTransaction extends Transaction {
public void orphan() throws DataException {
// Revert name
Name name = new Name(this.repository, cancelSellNameTransactionData.getName());
name.unsell(cancelSellNameTransactionData);
name.uncancelSell(cancelSellNameTransactionData);
// Delete this transaction itself
this.repository.getTransactionRepository().delete(cancelSellNameTransactionData);
// Save this transaction, with removed "name reference"
this.repository.getTransactionRepository().save(cancelSellNameTransactionData);
// Update owner's balance
Account owner = getOwner();

View File

@ -186,8 +186,7 @@ public class CreateAssetOrderTransaction extends Transaction {
// Update creator's last reference
creator.setLastReference(createOrderTransactionData.getSignature());
// Save this transaction itself
this.repository.getTransactionRepository().save(createOrderTransactionData);
// We would save updated transaction at this point, but it hasn't been modified
// Order Id is transaction's signature
byte[] orderId = createOrderTransactionData.getSignature();
@ -217,8 +216,7 @@ public class CreateAssetOrderTransaction extends Transaction {
OrderData orderData = this.repository.getAssetRepository().fromOrderId(orderId);
new Order(this.repository, orderData).orphan();
// Delete this transaction
this.repository.getTransactionRepository().delete(createOrderTransactionData);
// We would save updated transaction at this point, but it hasn't been modified
}
}

View File

@ -139,8 +139,11 @@ public class CreateGroupTransaction extends Transaction {
Group group = new Group(this.repository, createGroupTransactionData.getGroupId());
group.uncreate();
// Delete this transaction itself
this.repository.getTransactionRepository().delete(createGroupTransactionData);
// Remove assigned group ID from transaction record
createGroupTransactionData.setGroupId(null);
// Save this transaction with removed group ID
this.repository.getTransactionRepository().save(createGroupTransactionData);
// Update creator's balance
Account creator = getCreator();

View File

@ -153,8 +153,7 @@ public class CreatePollTransaction extends Transaction {
Poll poll = new Poll(this.repository, createPollTransactionData);
poll.publish();
// Save this transaction, now with corresponding pollId
this.repository.getTransactionRepository().save(createPollTransactionData);
// We would save updated transaction at this point, but it hasn't been modified
// Update creator's balance
Account creator = getCreator();
@ -170,8 +169,7 @@ public class CreatePollTransaction extends Transaction {
Poll poll = new Poll(this.repository, createPollTransactionData.getPollName());
poll.unpublish();
// Delete this transaction itself
this.repository.getTransactionRepository().delete(createPollTransactionData);
// We would save updated transaction at this point, but it hasn't been modified
// Update creator's balance
Account creator = getCreator();

View File

@ -216,8 +216,7 @@ public class DeployAtTransaction extends Transaction {
AT at = new AT(this.repository, this.deployATTransactionData);
at.deploy();
// Save this transaction itself
this.repository.getTransactionRepository().save(this.transactionData);
// We would save updated transaction at this point, but it hasn't been modified
long assetId = deployATTransactionData.getAssetId();
@ -243,8 +242,7 @@ public class DeployAtTransaction extends Transaction {
AT at = new AT(this.repository, this.deployATTransactionData);
at.undeploy();
// Delete this transaction itself
this.repository.getTransactionRepository().delete(deployATTransactionData);
// We would save updated transaction at this point, but it hasn't been modified
long assetId = deployATTransactionData.getAssetId();

View File

@ -152,8 +152,7 @@ public class EnableForgingTransaction extends Transaction {
target.setFlags(targetFlags);
target.setForgingEnabler(creator.getAddress());
// Save this transaction
this.repository.getTransactionRepository().save(enableForgingTransactionData);
// We would save updated transaction at this point, but it hasn't been modified
// Update creator's balance
creator.setConfirmedBalance(Asset.QORA, creator.getConfirmedBalance(Asset.QORA).subtract(enableForgingTransactionData.getFee()));
@ -182,8 +181,7 @@ public class EnableForgingTransaction extends Transaction {
target.setFlags(targetFlags);
target.setForgingEnabler(null);
// Delete this transaction itself
this.repository.getTransactionRepository().delete(enableForgingTransactionData);
// We would save updated transaction at this point, but it hasn't been modified
// Update creator's balance
creator.setConfirmedBalance(Asset.QORA, creator.getConfirmedBalance(Asset.QORA).add(enableForgingTransactionData.getFee()));

View File

@ -136,8 +136,7 @@ public class GenesisTransaction extends Transaction {
@Override
public void process() throws DataException {
// Save this transaction itself
this.repository.getTransactionRepository().save(this.transactionData);
// We would save updated transaction at this point, but it hasn't been modified
Account recipient = new Account(repository, genesisTransactionData.getRecipient());
@ -150,8 +149,7 @@ public class GenesisTransaction extends Transaction {
@Override
public void orphan() throws DataException {
// Delete this transaction
this.repository.getTransactionRepository().delete(this.transactionData);
// We would save updated transaction at this point, but it hasn't been modified
// Delete recipient's account (and balance)
this.repository.getAccountRepository().delete(genesisTransactionData.getRecipient());

View File

@ -115,10 +115,9 @@ public class GroupApprovalTransaction extends Transaction {
@Override
public void orphan() throws DataException {
// Revert?
// Delete this transaction itself
this.repository.getTransactionRepository().delete(groupApprovalTransactionData);
// Save this transaction with removed prior reference
groupApprovalTransactionData.setPriorReference(null);
this.repository.getTransactionRepository().save(groupApprovalTransactionData);
// Update admin's balance
Account admin = getAdmin();

View File

@ -135,8 +135,8 @@ public class GroupBanTransaction extends Transaction {
Group group = new Group(this.repository, groupBanTransactionData.getGroupId());
group.unban(groupBanTransactionData);
// Delete this transaction itself
this.repository.getTransactionRepository().delete(groupBanTransactionData);
// Save this transaction with removed member/admin references
this.repository.getTransactionRepository().save(groupBanTransactionData);
// Update admin's balance
Account admin = getAdmin();

View File

@ -141,8 +141,8 @@ public class GroupInviteTransaction extends Transaction {
Group group = new Group(this.repository, groupInviteTransactionData.getGroupId());
group.uninvite(groupInviteTransactionData);
// Delete this transaction itself
this.repository.getTransactionRepository().delete(groupInviteTransactionData);
// Save this transaction with removed member/admin references
this.repository.getTransactionRepository().save(groupInviteTransactionData);
// Update admin's balance
Account admin = getAdmin();

View File

@ -141,8 +141,8 @@ public class GroupKickTransaction extends Transaction {
Group group = new Group(this.repository, groupKickTransactionData.getGroupId());
group.unkick(groupKickTransactionData);
// Delete this transaction itself
this.repository.getTransactionRepository().delete(groupKickTransactionData);
// Save this transaction with removed member/admin references
this.repository.getTransactionRepository().save(groupKickTransactionData);
// Update admin's balance
Account admin = getAdmin();

View File

@ -170,8 +170,11 @@ public class IssueAssetTransaction extends Transaction {
Asset asset = new Asset(this.repository, issueAssetTransactionData.getAssetId());
asset.deissue();
// Delete this transaction itself
this.repository.getTransactionRepository().delete(issueAssetTransactionData);
// Remove assigned asset ID from transaction info
issueAssetTransactionData.setAssetId(null);
// Save this transaction, with removed assetId
this.repository.getTransactionRepository().save(issueAssetTransactionData);
// Update issuer's balance
Account issuer = getIssuer();

View File

@ -104,7 +104,7 @@ public class JoinGroupTransaction extends Transaction {
Group group = new Group(this.repository, joinGroupTransactionData.getGroupId());
group.join(joinGroupTransactionData);
// Save this transaction
// Save this transaction with cached references to transactions that can help restore state
this.repository.getTransactionRepository().save(joinGroupTransactionData);
// Update joiner's balance
@ -122,8 +122,8 @@ public class JoinGroupTransaction extends Transaction {
Group group = new Group(this.repository, joinGroupTransactionData.getGroupId());
group.unjoin(joinGroupTransactionData);
// Delete this transaction itself
this.repository.getTransactionRepository().delete(joinGroupTransactionData);
// Save this transaction with removed references
this.repository.getTransactionRepository().save(joinGroupTransactionData);
// Update joiner's balance
Account joiner = getJoiner();

View File

@ -120,8 +120,8 @@ public class LeaveGroupTransaction extends Transaction {
Group group = new Group(this.repository, leaveGroupTransactionData.getGroupId());
group.unleave(leaveGroupTransactionData);
// Delete this transaction itself
this.repository.getTransactionRepository().delete(leaveGroupTransactionData);
// Save this transaction with removed member/admin references
this.repository.getTransactionRepository().save(leaveGroupTransactionData);
// Update leaver's balance
Account leaver = getLeaver();

View File

@ -116,8 +116,7 @@ public class MessageTransaction extends Transaction {
@Override
public void process() throws DataException {
// Save this transaction itself
this.repository.getTransactionRepository().save(this.transactionData);
// We would save updated transaction at this point, but it hasn't been modified
// 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(),
@ -126,12 +125,11 @@ public class MessageTransaction extends Transaction {
@Override
public void orphan() throws DataException {
// Delete this transaction itself
this.repository.getTransactionRepository().delete(this.transactionData);
// 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(), false);
// We would save updated transaction at this point, but it hasn't been modified
}
}

View File

@ -116,8 +116,7 @@ public class MultiPaymentTransaction extends Transaction {
@Override
public void process() throws DataException {
// Save this transaction itself
this.repository.getTransactionRepository().save(this.transactionData);
// We would save updated transaction at this point, but it hasn't been modified
// 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(),
@ -126,12 +125,11 @@ public class MultiPaymentTransaction extends Transaction {
@Override
public void orphan() throws DataException {
// Delete this transaction itself
this.repository.getTransactionRepository().delete(this.transactionData);
// 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(), true);
// We would save updated transaction at this point, but it hasn't been modified
}
}

View File

@ -88,8 +88,7 @@ public class PaymentTransaction extends Transaction {
@Override
public void process() throws DataException {
// Save this transaction itself
this.repository.getTransactionRepository().save(this.transactionData);
// We would save updated transaction at this point, but it hasn't been modified
// 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(),
@ -98,12 +97,11 @@ public class PaymentTransaction extends Transaction {
@Override
public void orphan() throws DataException {
// Delete this transaction
this.repository.getTransactionRepository().delete(this.transactionData);
// 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(), false);
// We would save updated transaction at this point, but it hasn't been modified
}
}

View File

@ -163,8 +163,9 @@ public class ProxyForgingTransaction extends Transaction {
this.repository.getAccountRepository().delete(forger.getPublicKey(), proxyForgingTransactionData.getRecipient());
}
// Delete this transaction itself
this.repository.getTransactionRepository().delete(proxyForgingTransactionData);
// Save this transaction, with removed previous share info
proxyForgingTransactionData.setPreviousShare(null);
this.repository.getTransactionRepository().save(proxyForgingTransactionData);
// Update forger's balance
forger.setConfirmedBalance(Asset.QORA, forger.getConfirmedBalance(Asset.QORA).add(proxyForgingTransactionData.getFee()));

View File

@ -125,8 +125,7 @@ public class RegisterNameTransaction extends Transaction {
Name name = new Name(this.repository, registerNameTransactionData);
name.register();
// Save this transaction
this.repository.getTransactionRepository().save(registerNameTransactionData);
// We would save updated transaction at this point, but it hasn't been modified
// Update registrant's balance
Account registrant = getRegistrant();
@ -142,8 +141,7 @@ public class RegisterNameTransaction extends Transaction {
Name name = new Name(this.repository, registerNameTransactionData.getName());
name.unregister();
// Delete this transaction itself
this.repository.getTransactionRepository().delete(registerNameTransactionData);
// We would save updated transaction at this point, but it hasn't been modified
// Update registrant's balance
Account registrant = getRegistrant();

View File

@ -117,7 +117,7 @@ public class RemoveGroupAdminTransaction extends Transaction {
Group group = new Group(this.repository, removeGroupAdminTransactionData.getGroupId());
group.demoteFromAdmin(removeGroupAdminTransactionData);
// Save this transaction
// Save this transaction with cached references to transactions that can help restore state
this.repository.getTransactionRepository().save(removeGroupAdminTransactionData);
// Update owner's balance
@ -134,8 +134,8 @@ public class RemoveGroupAdminTransaction extends Transaction {
Group group = new Group(this.repository, removeGroupAdminTransactionData.getGroupId());
group.undemoteFromAdmin(removeGroupAdminTransactionData);
// Delete this transaction itself
this.repository.getTransactionRepository().delete(removeGroupAdminTransactionData);
// Save this transaction with removed group references
this.repository.getTransactionRepository().save(removeGroupAdminTransactionData);
// Update owner's balance
Account owner = getOwner();

View File

@ -118,12 +118,11 @@ public class SellNameTransaction extends Transaction {
@Override
public void process() throws DataException {
// Update Name
// Sell Name
Name name = new Name(this.repository, sellNameTransactionData.getName());
name.sell(sellNameTransactionData);
// Save this transaction, now with updated "name reference" to previous transaction that updated name
this.repository.getTransactionRepository().save(sellNameTransactionData);
// We would save updated transaction at this point, but it hasn't been modified
// Update owner's balance
Account owner = getOwner();
@ -139,8 +138,7 @@ public class SellNameTransaction extends Transaction {
Name name = new Name(this.repository, sellNameTransactionData.getName());
name.unsell(sellNameTransactionData);
// Delete this transaction itself
this.repository.getTransactionRepository().delete(sellNameTransactionData);
// We would save updated transaction at this point, but it hasn't been modified
// Update owner's balance
Account owner = getOwner();

View File

@ -119,8 +119,9 @@ public class SetGroupTransaction extends Transaction {
creator.setDefaultGroupId(previousDefaultGroupId);
// Delete this transaction itself
this.repository.getTransactionRepository().delete(setGroupTransactionData);
// Save this transaction with removed previous defaultGroupId value
setGroupTransactionData.setPreviousDefaultGroupId(null);
this.repository.getTransactionRepository().save(setGroupTransactionData);
// Update creator's balance
creator.setConfirmedBalance(Asset.QORA, creator.getConfirmedBalance(Asset.QORA).add(setGroupTransactionData.getFee()));

View File

@ -101,8 +101,7 @@ public class TransferAssetTransaction extends Transaction {
@Override
public void process() throws DataException {
// Save this transaction itself
this.repository.getTransactionRepository().save(this.transactionData);
// We would save updated transaction at this point, but it hasn't been modified
// 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(),
@ -111,12 +110,11 @@ public class TransferAssetTransaction extends Transaction {
@Override
public void orphan() throws DataException {
// Delete this transaction itself
this.repository.getTransactionRepository().delete(this.transactionData);
// 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(), false);
// We would save updated transaction at this point, but it hasn't been modified
}
}

View File

@ -130,8 +130,7 @@ public class UpdateAssetTransaction extends Transaction {
Asset asset = new Asset(this.repository, updateAssetTransactionData.getAssetId());
asset.update(updateAssetTransactionData);
// Save this transaction, now with updated "name reference" to previous
// transaction that updated name
// Save this transaction, with updated "name reference" to previous transaction that updated name
this.repository.getTransactionRepository().save(updateAssetTransactionData);
// Update old owner's balance
@ -149,8 +148,8 @@ public class UpdateAssetTransaction extends Transaction {
Asset asset = new Asset(this.repository, updateAssetTransactionData.getAssetId());
asset.revert(updateAssetTransactionData);
// Delete this transaction itself
this.repository.getTransactionRepository().delete(updateAssetTransactionData);
// Save this transaction, with removed "name reference" to previous transaction that updated name
this.repository.getTransactionRepository().save(updateAssetTransactionData);
// Update owner's balance
Account owner = getOwner();

View File

@ -149,8 +149,8 @@ public class UpdateGroupTransaction extends Transaction {
Group group = new Group(this.repository, updateGroupTransactionData.getGroupId());
group.unupdateGroup(updateGroupTransactionData);
// Delete this transaction itself
this.repository.getTransactionRepository().delete(updateGroupTransactionData);
// Save this transaction, now with removed "group reference"
this.repository.getTransactionRepository().save(updateGroupTransactionData);
// Update owner's balance
Account owner = getOwner();

View File

@ -151,8 +151,8 @@ public class UpdateNameTransaction extends Transaction {
Name name = new Name(this.repository, updateNameTransactionData.getName());
name.revert(updateNameTransactionData);
// Delete this transaction itself
this.repository.getTransactionRepository().delete(updateNameTransactionData);
// Save this transaction, now with removed "name reference"
this.repository.getTransactionRepository().save(updateNameTransactionData);
// Update owner's balance
Account owner = getOwner();

View File

@ -177,8 +177,9 @@ public class VoteOnPollTransaction extends Transaction {
votingRepository.delete(voteOnPollTransactionData.getPollName(), voteOnPollTransactionData.getVoterPublicKey());
}
// Delete this transaction itself
this.repository.getTransactionRepository().delete(voteOnPollTransactionData);
// Save this transaction, with removed previous vote info
voteOnPollTransactionData.setPreviousOptionIndex(null);
this.repository.getTransactionRepository().save(voteOnPollTransactionData);
}
}

View File

@ -3,23 +3,37 @@ package org.qora.test;
import java.math.BigDecimal;
import java.util.List;
import org.junit.Before;
import org.junit.Test;
import org.qora.account.PrivateKeyAccount;
import org.qora.block.Block;
import org.qora.block.BlockGenerator;
import org.qora.block.GenesisBlock;
import org.qora.data.at.ATStateData;
import org.qora.data.block.BlockData;
import org.qora.data.transaction.TransactionData;
import org.qora.repository.DataException;
import org.qora.repository.Repository;
import org.qora.repository.RepositoryManager;
import org.qora.test.common.Common;
import org.qora.test.common.TransactionUtils;
import org.qora.transaction.Transaction;
import org.qora.transaction.Transaction.TransactionType;
import org.qora.transform.TransformationException;
import org.qora.transform.block.BlockTransformer;
import org.qora.transform.transaction.TransactionTransformer;
import org.qora.utils.Base58;
import org.qora.utils.Triple;
import static org.junit.Assert.*;
public class BlockTests extends Common {
@Before
public void beforeTest() throws DataException {
Common.useDefaultSettings();
}
@Test
public void testGenesisBlockTransactions() throws DataException {
try (final Repository repository = RepositoryManager.getRepository()) {
@ -33,20 +47,24 @@ public class BlockTests extends Common {
List<Transaction> transactions = block.getTransactions();
assertNotNull(transactions);
byte[] lastGenesisSignature = null;
for (Transaction transaction : transactions) {
assertNotNull(transaction);
TransactionData transactionData = transaction.getTransactionData();
assertEquals(Transaction.TransactionType.GENESIS, transactionData.getType());
if (transactionData.getType() != Transaction.TransactionType.GENESIS)
continue;
assertTrue(transactionData.getFee().compareTo(BigDecimal.ZERO) == 0);
assertNull(transactionData.getReference());
assertTrue(transaction.isSignatureValid());
assertEquals(Transaction.ValidationResult.OK, transaction.isValid());
lastGenesisSignature = transactionData.getSignature();
}
// Attempt to load first transaction directly from database
TransactionData transactionData = repository.getTransactionRepository().fromSignature(transactions.get(0).getTransactionData().getSignature());
// Attempt to load last GENESIS transaction directly from database
TransactionData transactionData = repository.getTransactionRepository().fromSignature(lastGenesisSignature);
assertNotNull(transactionData);
assertEquals(Transaction.TransactionType.GENESIS, transactionData.getType());
@ -61,61 +79,59 @@ public class BlockTests extends Common {
}
}
@Test
public void testBlockPaymentTransactions() throws DataException {
try (final Repository repository = RepositoryManager.getRepository()) {
// Block 949 has lots of varied transactions
// Blocks 390 & 754 have only payment transactions
BlockData blockData = repository.getBlockRepository().fromHeight(754);
assertNotNull("Block 754 is required for this test", blockData);
Block block = new Block(repository, blockData);
assertTrue(block.isSignatureValid());
List<Transaction> transactions = block.getTransactions();
assertNotNull(transactions);
for (Transaction transaction : transactions) {
assertNotNull(transaction);
TransactionData transactionData = transaction.getTransactionData();
assertEquals(Transaction.TransactionType.PAYMENT, transactionData.getType());
assertFalse(transactionData.getFee().compareTo(BigDecimal.ZERO) == 0);
assertNotNull(transactionData.getReference());
assertTrue(transaction.isSignatureValid());
}
// Attempt to load first transaction directly from database
TransactionData transactionData = repository.getTransactionRepository().fromSignature(transactions.get(0).getTransactionData().getSignature());
assertNotNull(transactionData);
assertEquals(Transaction.TransactionType.PAYMENT, transactionData.getType());
assertFalse(transactionData.getFee().compareTo(BigDecimal.ZERO) == 0);
assertNotNull(transactionData.getReference());
Transaction transaction = Transaction.fromData(repository, transactionData);
assertNotNull(transaction);
assertTrue(transaction.isSignatureValid());
}
}
@Test
public void testBlockSerialization() throws DataException, TransformationException {
try (final Repository repository = RepositoryManager.getRepository()) {
// Block 949 has lots of varied transactions
// Blocks 390 & 754 have only payment transactions
BlockData blockData = repository.getBlockRepository().fromHeight(754);
assertNotNull("Block 754 is required for this test", blockData);
PrivateKeyAccount signingAccount = Common.getTestAccount(repository, "alice");
// TODO: Fill block with random, valid transactions of every type (except GENESIS or AT)
for (Transaction.TransactionType txType : Transaction.TransactionType.values()) {
if (txType == TransactionType.GENESIS || txType == TransactionType.AT)
continue;
TransactionData transactionData = TransactionUtils.randomTransaction(repository, signingAccount, txType, true);
Transaction transaction = Transaction.fromData(repository, transactionData);
transaction.sign(signingAccount);
repository.getTransactionRepository().save(transactionData);
repository.getTransactionRepository().unconfirmTransaction(transactionData);
repository.saveChanges();
// TODO: more transactions
break;
}
// We might need to wait until transactions' timestamps are valid for the block we're about to generate
try {
Thread.sleep(1L);
} catch (InterruptedException e) {
}
BlockGenerator.generateTestingBlock(repository, signingAccount);
BlockData blockData = repository.getBlockRepository().getLastBlock();
Block block = new Block(repository, blockData);
assertTrue(block.isSignatureValid());
byte[] bytes = BlockTransformer.toBytes(block);
assertEquals(BlockTransformer.getDataLength(block), bytes.length);
Triple<BlockData, List<TransactionData>, List<ATStateData>> blockInfo = BlockTransformer.fromBytes(bytes);
// Compare transactions
List<TransactionData> deserializedTransactions = blockInfo.getB();
assertEquals("Transaction count differs", blockData.getTransactionCount(), deserializedTransactions.size());
for (int i = 0; i < blockData.getTransactionCount(); ++i) {
TransactionData deserializedTransactionData = deserializedTransactions.get(i);
Transaction originalTransaction = block.getTransactions().get(i);
TransactionData originalTransactionData = originalTransaction.getTransactionData();
assertEquals("Transaction signature differs", Base58.encode(originalTransactionData.getSignature()), Base58.encode(deserializedTransactionData.getSignature()));
assertEquals("Transaction declared length differs", TransactionTransformer.getDataLength(originalTransactionData), TransactionTransformer.getDataLength(deserializedTransactionData));
assertEquals("Transaction serialized length differs", TransactionTransformer.toBytes(originalTransactionData).length, TransactionTransformer.toBytes(deserializedTransactionData).length);
}
}
}

View File

@ -1,15 +0,0 @@
package org.qora.test;
import org.junit.Test;
import org.qora.block.BlockChain;
import org.qora.repository.DataException;
import org.qora.test.common.Common;
public class BlockchainTests extends Common {
@Test
public void testValidateOrRebuild() throws DataException {
BlockChain.validate();
}
}

View File

@ -1,54 +0,0 @@
package org.qora.test;
import java.math.BigDecimal;
import java.util.List;
import org.junit.Test;
import org.qora.block.Block;
import org.qora.block.GenesisBlock;
import org.qora.data.transaction.TransactionData;
import org.qora.repository.DataException;
import org.qora.repository.Repository;
import org.qora.repository.RepositoryManager;
import org.qora.test.common.Common;
import org.qora.transaction.Transaction;
import static org.junit.Assert.*;
public class GenesisTests extends Common {
@Test
public void testGenesisBlockTransactions() throws DataException {
try (final Repository repository = RepositoryManager.getRepository()) {
assertEquals("Blockchain should be empty for this test", 0, repository.getBlockRepository().getBlockchainHeight());
GenesisBlock block = GenesisBlock.getInstance(repository);
assertNotNull("No genesis block?", block);
assertTrue(block.isSignatureValid());
// Note: only true if blockchain is empty
assertEquals("Block invalid", Block.ValidationResult.OK, block.isValid());
List<Transaction> transactions = block.getTransactions();
assertNotNull("No transactions?", transactions);
for (Transaction transaction : transactions) {
assertNotNull(transaction);
TransactionData transactionData = transaction.getTransactionData();
assertEquals(Transaction.TransactionType.GENESIS, transactionData.getType());
assertTrue(transactionData.getFee().compareTo(BigDecimal.ZERO) == 0);
assertNull(transactionData.getReference());
assertNotNull(transactionData.getSignature());
assertTrue(transaction.isSignatureValid());
assertEquals(Transaction.ValidationResult.OK, transaction.isValid());
}
// Actually try to process genesis block onto empty blockchain
block.process();
repository.saveChanges();
}
}
}

View File

@ -1,93 +0,0 @@
package org.qora.test;
import org.junit.Test;
import org.qora.account.PublicKeyAccount;
import org.qora.data.transaction.PaymentTransactionData;
import org.qora.data.transaction.TransactionData;
import org.qora.repository.DataException;
import org.qora.repository.Repository;
import org.qora.repository.RepositoryManager;
import org.qora.repository.TransactionRepository;
import org.qora.test.common.Common;
import org.qora.transaction.Transaction.TransactionType;
import org.qora.utils.Base58;
import static org.junit.Assert.*;
public class LoadTests extends Common {
@Test
public void testLoadPaymentTransaction() throws DataException {
try (final Repository repository = RepositoryManager.getRepository()) {
TransactionRepository transactionRepository = repository.getTransactionRepository();
assertTrue("Migrate from old database to at least block 49778 before running this test", repository.getBlockRepository().getBlockchainHeight() >= 49778);
String signature58 = "1211ZPwG3hk5evWzXCZi9hMDRpwumWmkENjwWkeTCik9xA5uoYnxzF7rwR5hmHH3kG2RXo7ToCAaRc7dvnynByJt";
byte[] signature = Base58.decode(signature58);
TransactionData transactionData = transactionRepository.fromSignature(signature);
assertNotNull("Transaction data not loaded from repository", transactionData);
assertEquals("Transaction data not PAYMENT type", TransactionType.PAYMENT, transactionData.getType());
assertEquals("QXwu8924WdgPoRmtiWQBUMF6eedmp1Hu2E", PublicKeyAccount.getAddress(transactionData.getCreatorPublicKey()));
PaymentTransactionData paymentTransactionData = (PaymentTransactionData) transactionData;
assertNotNull(paymentTransactionData);
assertEquals("QXwu8924WdgPoRmtiWQBUMF6eedmp1Hu2E", PublicKeyAccount.getAddress(paymentTransactionData.getSenderPublicKey()));
assertEquals("QZsv8vbJ6QfrBNba4LMp5UtHhAzhrxvVUU", paymentTransactionData.getRecipient());
assertEquals(1416209264000L, paymentTransactionData.getTimestamp());
assertEquals("31dC6kHHBeG5vYb8LMaZDjLEmhc9kQB2VUApVd8xWncSRiXu7yMejdprjYFMP2rUnzZxWd4KJhkq6LsV7rQvU1kY",
Base58.encode(paymentTransactionData.getReference()));
}
}
@Test
public void testLoadFactory() throws DataException {
try (final Repository repository = RepositoryManager.getRepository()) {
TransactionRepository transactionRepository = repository.getTransactionRepository();
assertTrue("Migrate from old database to at least block 49778 before running this test", repository.getBlockRepository().getBlockchainHeight() >= 49778);
String signature58 = "1211ZPwG3hk5evWzXCZi9hMDRpwumWmkENjwWkeTCik9xA5uoYnxzF7rwR5hmHH3kG2RXo7ToCAaRc7dvnynByJt";
byte[] signature = Base58.decode(signature58);
while (true) {
TransactionData transactionData = transactionRepository.fromSignature(signature);
if (transactionData == null)
break;
if (transactionData.getType() != TransactionType.PAYMENT)
break;
PaymentTransactionData paymentTransactionData = (PaymentTransactionData) transactionData;
System.out.println(PublicKeyAccount.getAddress(paymentTransactionData.getSenderPublicKey()) + " sent " + paymentTransactionData.getAmount()
+ " QORA to " + paymentTransactionData.getRecipient());
signature = transactionData.getReference();
}
}
}
@Test
public void testLoadNonexistentTransaction() throws DataException {
try (final Repository repository = RepositoryManager.getRepository()) {
TransactionRepository transactionRepository = repository.getTransactionRepository();
String signature58 = "1111222233334444";
byte[] signature = Base58.decode(signature58);
TransactionData transactionData = transactionRepository.fromSignature(signature);
if (transactionData != null) {
PaymentTransactionData paymentTransactionData = (PaymentTransactionData) transactionData;
System.out.println(PublicKeyAccount.getAddress(paymentTransactionData.getSenderPublicKey()) + " sent " + paymentTransactionData.getAmount()
+ " QORA to " + paymentTransactionData.getRecipient());
fail();
}
}
}
}

View File

@ -1,46 +0,0 @@
package org.qora.test;
import org.junit.Test;
import org.qora.data.block.BlockData;
import org.qora.data.transaction.TransactionData;
import org.qora.repository.DataException;
import org.qora.repository.Repository;
import org.qora.repository.RepositoryManager;
import org.qora.repository.TransactionRepository;
import org.qora.test.common.Common;
import org.qora.transaction.Transaction.TransactionType;
import org.qora.utils.Base58;
import static org.junit.Assert.*;
public class NavigationTests extends Common {
@Test
public void testNavigateFromTransactionToBlock() throws DataException {
try (final Repository repository = RepositoryManager.getRepository()) {
TransactionRepository transactionRepository = repository.getTransactionRepository();
assertTrue("Migrate from old database to at least block 49778 before running this test", repository.getBlockRepository().getBlockchainHeight() >= 49778);
String signature58 = "1211ZPwG3hk5evWzXCZi9hMDRpwumWmkENjwWkeTCik9xA5uoYnxzF7rwR5hmHH3kG2RXo7ToCAaRc7dvnynByJt";
byte[] signature = Base58.decode(signature58);
System.out.println("Navigating to Block from transaction " + signature58);
TransactionData transactionData = transactionRepository.fromSignature(signature);
assertNotNull("Transaction data not loaded from repository", transactionData);
assertEquals("Transaction data not PAYMENT type", TransactionType.PAYMENT, transactionData.getType());
int transactionHeight = transactionRepository.getHeightFromSignature(signature);
assertFalse("Transaction not found or transaction's block not found", transactionHeight == 0);
assertEquals("Transaction's block height expected to be 49778", 49778, transactionHeight);
BlockData blockData = repository.getBlockRepository().fromHeight(transactionHeight);
assertNotNull("Block 49778 not loaded from database", blockData);
System.out.println("Block " + blockData.getHeight() + ", signature: " + Base58.encode(blockData.getSignature()));
assertEquals((Integer) 49778, blockData.getHeight());
}
}
}

View File

@ -1,5 +1,6 @@
package org.qora.test;
import org.junit.Before;
import org.junit.Test;
import org.qora.account.Account;
import org.qora.asset.Asset;
@ -19,6 +20,11 @@ public class RepositoryTests extends Common {
private static final Logger LOGGER = LogManager.getLogger(RepositoryTests.class);
@Before
public void beforeTest() throws DataException {
Common.useDefaultSettings();
}
@Test
public void testGetRepository() throws DataException {
try (final Repository repository = RepositoryManager.getRepository()) {
@ -44,7 +50,7 @@ public class RepositoryTests extends Common {
@Test
public void testAccessAfterClose() throws DataException {
try (Repository repository = RepositoryManager.getRepository()) {
try (final Repository repository = RepositoryManager.getRepository()) {
assertNotNull(repository);
repository.close();
@ -61,17 +67,15 @@ public class RepositoryTests extends Common {
@Test
public void testDeadlock() throws DataException {
Common.useDefaultSettings();
// Open connection 1
try (Repository repository1 = RepositoryManager.getRepository()) {
try (final Repository repository1 = RepositoryManager.getRepository()) {
// Do a database 'read'
Account account1 = Common.getTestAccount(repository1, "alice");
account1.getLastReference();
// Open connection 2
try (Repository repository2 = RepositoryManager.getRepository()) {
try (final Repository repository2 = RepositoryManager.getRepository()) {
// Update account in 2
Account account2 = Common.getTestAccount(repository2, "alice");
account2.setConfirmedBalance(Asset.QORA, BigDecimal.valueOf(1234L));

View File

@ -3,17 +3,21 @@ package org.qora.test.common;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import org.qora.account.PrivateKeyAccount;
import org.qora.block.BlockGenerator;
import org.qora.data.transaction.TransactionData;
import org.qora.repository.DataException;
import org.qora.repository.Repository;
import org.qora.transaction.Transaction;
import org.qora.transaction.Transaction.TransactionType;
import org.qora.transaction.Transaction.ValidationResult;
public class TransactionUtils {
public static void signAndForge(Repository repository, TransactionData transactionData, PrivateKeyAccount signingAccount) throws DataException {
public static void signAsUnconfirmed(Repository repository, TransactionData transactionData, PrivateKeyAccount signingAccount) throws DataException {
Transaction transaction = Transaction.fromData(repository, transactionData);
transaction.sign(signingAccount);
@ -32,10 +36,29 @@ public class TransactionUtils {
repository.getTransactionRepository().save(transactionData);
repository.getTransactionRepository().unconfirmTransaction(transactionData);
repository.saveChanges();
}
public static void signAndForge(Repository repository, TransactionData transactionData, PrivateKeyAccount signingAccount) throws DataException {
signAsUnconfirmed(repository, transactionData, signingAccount);
// Generate block
PrivateKeyAccount generatorAccount = Common.getTestAccount(repository, "alice");
BlockGenerator.generateTestingBlock(repository, generatorAccount);
}
public static TransactionData randomTransaction(Repository repository, PrivateKeyAccount account, TransactionType txType, boolean wantValid) throws DataException {
try {
Class <?> clazz = Class.forName(String.join("", org.qora.test.common.transaction.Transaction.class.getPackage().getName(), ".", txType.className, "Transaction"));
try {
Method method = clazz.getDeclaredMethod("randomTransaction", Repository.class, PrivateKeyAccount.class, boolean.class);
return (TransactionData) method.invoke(null, repository, account, wantValid);
} catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
throw new RuntimeException(String.format("Transaction subclass constructor not found for transaction type \"%s\"", txType.name()), e);
}
} catch (ClassNotFoundException e) {
throw new RuntimeException(String.format("Transaction subclass not found for transaction type \"%s\"", txType.name()), e);
}
}
}

View File

@ -0,0 +1,18 @@
package org.qora.test.common.transaction;
import java.math.BigDecimal;
import org.qora.account.PrivateKeyAccount;
import org.qora.data.transaction.PaymentTransactionData;
import org.qora.data.transaction.TransactionData;
import org.qora.group.Group;
import org.qora.repository.Repository;
import org.qora.utils.NTP;
public class PaymentTransaction {
public static TransactionData randomTransaction(Repository repository, PrivateKeyAccount account, boolean wantValid) {
return new PaymentTransactionData(NTP.getTime(), Group.NO_GROUP, new byte[32], account.getPublicKey(), account.getAddress(), BigDecimal.valueOf(123L), BigDecimal.ONE);
}
}

View File

@ -0,0 +1,4 @@
package org.qora.test.common.transaction;
public abstract class Transaction {
}

View File

@ -22,7 +22,6 @@ import org.qora.transaction.Transaction.ValidationResult;
public class GrantForgingTests extends Common {
@Before
public void beforeTest() throws DataException {
Common.useDefaultSettings();