Added tests for each group-approval outcome + fixes

This commit is contained in:
catbref 2019-06-02 17:53:17 +01:00
parent c9f226cf88
commit 4667b768df
5 changed files with 282 additions and 10 deletions

View File

@ -1145,6 +1145,9 @@ public class Block {
for (TransactionData transactionData : approvalExpiringTransactions) {
transactionData.setApprovalStatus(ApprovalStatus.EXPIRED);
transactionRepository.save(transactionData);
// Update group-approval decision height for transaction in repository
transactionRepository.updateApprovalHeight(transactionData.getSignature(), this.blockData.getHeight());
}
// Search for pending transactions within min/max block delay range
@ -1159,7 +1162,7 @@ public class Block {
if (isApproved == null)
continue; // approve/reject threshold not yet met
// Update approval height for transaction in repository
// Update group-approval decision height for transaction in repository
transactionRepository.updateApprovalHeight(transactionData.getSignature(), this.blockData.getHeight());
if (!isApproved) {
@ -1321,15 +1324,16 @@ public class Block {
List<TransactionData> transactions = transactionRepository.getApprovalTransactionDecidedAtHeight(this.blockData.getHeight());
for (TransactionData transactionData : transactions) {
// Orphan/un-process transaction
// Orphan/un-process transaction (if approved)
Transaction transaction = Transaction.fromData(repository, transactionData);
transaction.orphan();
if (transactionData.getApprovalStatus() == ApprovalStatus.APPROVED)
transaction.orphan();
// Revert back to PENDING
transactionData.setApprovalStatus(ApprovalStatus.PENDING);
transactionRepository.save(transactionData);
// Undo approval decision height
// Remove group-approval decision height
transactionRepository.updateApprovalHeight(transactionData.getSignature(), null);
}
}

View File

@ -679,7 +679,7 @@ public class HSQLDBTransactionRepository implements TransactionRepository {
String sql = "SELECT signature FROM Transactions "
+ "JOIN Groups on Groups.group_id = Transactions.tx_group_id "
+ "WHERE Transactions.approval_status = ? "
+ "AND Transactions.block_height >= ? - Groups.min_block_delay";
+ "AND Transactions.block_height < ? - Groups.min_block_delay";
List<TransactionData> transactions = new ArrayList<TransactionData>();

View File

@ -63,4 +63,8 @@ public class GroupUtils {
return repository.getTransactionRepository().fromSignature(signature).getApprovalStatus();
}
public static Integer getApprovalHeight(Repository repository, byte[] signature) throws DataException {
return repository.getTransactionRepository().fromSignature(signature).getApprovalHeight();
}
}

View File

@ -17,6 +17,7 @@ import org.qora.transaction.Transaction.ValidationResult;
public class TransactionUtils {
/** Signs transaction using given account and imports into unconfirmed pile. */
public static void signAsUnconfirmed(Repository repository, TransactionData transactionData, PrivateKeyAccount signingAccount) throws DataException {
Transaction transaction = Transaction.fromData(repository, transactionData);
transaction.sign(signingAccount);
@ -36,6 +37,7 @@ public class TransactionUtils {
transaction.importAsUnconfirmed();
}
/** Signs transaction using given account and forges a new block, using "alice" account. */
public static void signAndForge(Repository repository, TransactionData transactionData, PrivateKeyAccount signingAccount) throws DataException {
signAsUnconfirmed(repository, transactionData, signingAccount);

View File

@ -32,6 +32,9 @@ public class GroupApprovalTests extends Common {
private static final BigDecimal amount = BigDecimal.valueOf(5000L).setScale(8);
private static final BigDecimal fee = BigDecimal.ONE.setScale(8);
private static final int minBlockDelay = 5;
private static final int maxBlockDelay = 10;
@Before
public void beforeTest() throws DataException {
@ -57,12 +60,26 @@ public class GroupApprovalTests extends Common {
}
}
@Test
/** Check that a transaction type that does need approval, auto-approves if created by group admin */
public void testAutoApprove() throws DataException {
try (final Repository repository = RepositoryManager.getRepository()) {
PrivateKeyAccount aliceAccount = Common.getTestAccount(repository, "alice");
int groupId = GroupUtils.createGroup(repository, "alice", "test", true, ApprovalThreshold.ONE, minBlockDelay, maxBlockDelay);
Transaction transaction = buildIssueAssetTransaction(repository, "alice", groupId);
TransactionUtils.signAndForge(repository, transaction.getTransactionData(), aliceAccount);
// Confirm transaction doesn't need approval
ApprovalStatus approvalStatus = GroupUtils.getApprovalStatus(repository, transaction.getTransactionData().getSignature());
assertEquals("incorrect transaction approval status", ApprovalStatus.NOT_REQUIRED, approvalStatus);
}
}
@Test
/** Check that a transaction, that requires approval, updates references and fees properly. */
public void testReferencesAndFees() throws DataException {
final int minBlockDelay = 5;
final int maxBlockDelay = 20;
try (final Repository repository = RepositoryManager.getRepository()) {
PrivateKeyAccount aliceAccount = Common.getTestAccount(repository, "alice");
@ -98,8 +115,7 @@ public class GroupApprovalTests extends Common {
// Have Bob do a non-approval transaction to change his last-reference
Transaction bobPaymentTransaction = buildPaymentTransaction(repository, "bob", "chloe", amount, Group.NO_GROUP);
TransactionUtils.signAsUnconfirmed(repository, bobPaymentTransaction.getTransactionData(), bobAccount);
BlockGenerator.generateTestingBlock(repository, aliceAccount);
TransactionUtils.signAndForge(repository, bobPaymentTransaction.getTransactionData(), bobAccount);
byte[] bobPostPaymentReference = bobAccount.getLastReference();
assertFalse("reference should have changed", Arrays.equals(bobPostAssetReference, bobPostPaymentReference));
@ -156,6 +172,252 @@ public class GroupApprovalTests extends Common {
}
}
@Test
/** Test generic approval. */
public void testApproval() throws DataException {
try (final Repository repository = RepositoryManager.getRepository()) {
PrivateKeyAccount aliceAccount = Common.getTestAccount(repository, "alice");
int groupId = GroupUtils.createGroup(repository, "alice", "test", true, ApprovalThreshold.ONE, minBlockDelay, maxBlockDelay);
PrivateKeyAccount bobAccount = Common.getTestAccount(repository, "bob");
GroupUtils.joinGroup(repository, "bob", groupId);
// Bob's issue-asset transaction needs group-approval
Transaction bobAssetTransaction = buildIssueAssetTransaction(repository, "bob", groupId);
TransactionUtils.signAndForge(repository, bobAssetTransaction.getTransactionData(), bobAccount);
// Confirm transaction needs approval, and hasn't been approved
ApprovalStatus approvalStatus = GroupUtils.getApprovalStatus(repository, bobAssetTransaction.getTransactionData().getSignature());
assertEquals("incorrect transaction approval status", ApprovalStatus.PENDING, approvalStatus);
// Confirm transaction has no group-approval decision height
Integer approvalHeight = GroupUtils.getApprovalHeight(repository, bobAssetTransaction.getTransactionData().getSignature());
assertNull("group-approval decision height should be null", approvalHeight);
// Have Alice approve Bob's approval-needed transaction
GroupUtils.approveTransaction(repository, "alice", bobAssetTransaction.getTransactionData().getSignature(), true);
// Now forge a few blocks so transaction is approved
for (int blockCount = 0; blockCount < minBlockDelay; ++blockCount)
BlockGenerator.generateTestingBlock(repository, aliceAccount);
// Confirm transaction now approved
approvalStatus = GroupUtils.getApprovalStatus(repository, bobAssetTransaction.getTransactionData().getSignature());
assertEquals("incorrect transaction approval status", ApprovalStatus.APPROVED, approvalStatus);
// Confirm transaction now has a group-approval decision height
approvalHeight = GroupUtils.getApprovalHeight(repository, bobAssetTransaction.getTransactionData().getSignature());
assertNotNull("group-approval decision height should not be null", approvalHeight);
// Orphan blocks that decided approval
for (int blockCount = 0; blockCount < minBlockDelay; ++blockCount)
BlockUtils.orphanLastBlock(repository);
// Confirm transaction no longer approved
approvalStatus = GroupUtils.getApprovalStatus(repository, bobAssetTransaction.getTransactionData().getSignature());
assertEquals("incorrect transaction approval status", ApprovalStatus.PENDING, approvalStatus);
// Confirm transaction no longer has group-approval decision height
approvalHeight = GroupUtils.getApprovalHeight(repository, bobAssetTransaction.getTransactionData().getSignature());
assertNull("group-approval decision height should be null", approvalHeight);
// Orphan block containing Alice's group-approval transaction
BlockUtils.orphanLastBlock(repository);
// Confirm transaction no longer approved
approvalStatus = GroupUtils.getApprovalStatus(repository, bobAssetTransaction.getTransactionData().getSignature());
assertEquals("incorrect transaction approval status", ApprovalStatus.PENDING, approvalStatus);
// Confirm transaction no longer has group-approval decision height
approvalHeight = GroupUtils.getApprovalHeight(repository, bobAssetTransaction.getTransactionData().getSignature());
assertNull("group-approval decision height should be null", approvalHeight);
}
}
@Test
/** Test generic rejection. */
public void testRejection() throws DataException {
try (final Repository repository = RepositoryManager.getRepository()) {
PrivateKeyAccount aliceAccount = Common.getTestAccount(repository, "alice");
int groupId = GroupUtils.createGroup(repository, "alice", "test", true, ApprovalThreshold.ONE, minBlockDelay, maxBlockDelay);
PrivateKeyAccount bobAccount = Common.getTestAccount(repository, "bob");
GroupUtils.joinGroup(repository, "bob", groupId);
// Bob's issue-asset transaction needs group-approval
Transaction bobAssetTransaction = buildIssueAssetTransaction(repository, "bob", groupId);
TransactionUtils.signAndForge(repository, bobAssetTransaction.getTransactionData(), bobAccount);
// Confirm transaction needs approval, and hasn't been approved
ApprovalStatus approvalStatus = GroupUtils.getApprovalStatus(repository, bobAssetTransaction.getTransactionData().getSignature());
assertEquals("incorrect transaction approval status", ApprovalStatus.PENDING, approvalStatus);
// Confirm transaction has no group-approval decision height
Integer approvalHeight = GroupUtils.getApprovalHeight(repository, bobAssetTransaction.getTransactionData().getSignature());
assertNull("group-approval decision height should be null", approvalHeight);
// Have Alice reject Bob's approval-needed transaction
GroupUtils.approveTransaction(repository, "alice", bobAssetTransaction.getTransactionData().getSignature(), false);
// Now forge a few blocks so transaction is approved
for (int blockCount = 0; blockCount < minBlockDelay; ++blockCount)
BlockGenerator.generateTestingBlock(repository, aliceAccount);
// Confirm transaction now rejected
approvalStatus = GroupUtils.getApprovalStatus(repository, bobAssetTransaction.getTransactionData().getSignature());
assertEquals("incorrect transaction approval status", ApprovalStatus.REJECTED, approvalStatus);
// Confirm transaction now has a group-approval decision height
approvalHeight = GroupUtils.getApprovalHeight(repository, bobAssetTransaction.getTransactionData().getSignature());
assertNotNull("group-approval decision height should not be null", approvalHeight);
// Orphan blocks that decided rejection
for (int blockCount = 0; blockCount < minBlockDelay; ++blockCount)
BlockUtils.orphanLastBlock(repository);
// Confirm transaction no longer rejected
approvalStatus = GroupUtils.getApprovalStatus(repository, bobAssetTransaction.getTransactionData().getSignature());
assertEquals("incorrect transaction approval status", ApprovalStatus.PENDING, approvalStatus);
// Confirm transaction no longer has group-approval decision height
approvalHeight = GroupUtils.getApprovalHeight(repository, bobAssetTransaction.getTransactionData().getSignature());
assertNull("group-approval decision height should be null", approvalHeight);
// Orphan block containing Alice's group-approval transaction
BlockUtils.orphanLastBlock(repository);
// Confirm transaction no longer rejected
approvalStatus = GroupUtils.getApprovalStatus(repository, bobAssetTransaction.getTransactionData().getSignature());
assertEquals("incorrect transaction approval status", ApprovalStatus.PENDING, approvalStatus);
// Confirm transaction no longer has group-approval decision height
approvalHeight = GroupUtils.getApprovalHeight(repository, bobAssetTransaction.getTransactionData().getSignature());
assertNull("group-approval decision height should be null", approvalHeight);
}
}
@Test
/** Test generic expiry. */
public void testExpiry() throws DataException {
try (final Repository repository = RepositoryManager.getRepository()) {
PrivateKeyAccount aliceAccount = Common.getTestAccount(repository, "alice");
int groupId = GroupUtils.createGroup(repository, "alice", "test", true, ApprovalThreshold.ONE, minBlockDelay, maxBlockDelay);
PrivateKeyAccount bobAccount = Common.getTestAccount(repository, "bob");
GroupUtils.joinGroup(repository, "bob", groupId);
// Bob's issue-asset transaction needs group-approval
Transaction bobAssetTransaction = buildIssueAssetTransaction(repository, "bob", groupId);
TransactionUtils.signAndForge(repository, bobAssetTransaction.getTransactionData(), bobAccount);
// Confirm transaction needs approval, and hasn't been approved
ApprovalStatus approvalStatus = GroupUtils.getApprovalStatus(repository, bobAssetTransaction.getTransactionData().getSignature());
assertEquals("incorrect transaction approval status", ApprovalStatus.PENDING, approvalStatus);
// Confirm transaction has no group-approval decision height
Integer approvalHeight = GroupUtils.getApprovalHeight(repository, bobAssetTransaction.getTransactionData().getSignature());
assertNull("group-approval decision height should be null", approvalHeight);
// Now forge a few blocks so group-approval for transaction expires
for (int blockCount = 0; blockCount <= maxBlockDelay; ++blockCount)
BlockGenerator.generateTestingBlock(repository, aliceAccount);
// Confirm transaction now expired
approvalStatus = GroupUtils.getApprovalStatus(repository, bobAssetTransaction.getTransactionData().getSignature());
assertEquals("incorrect transaction approval status", ApprovalStatus.EXPIRED, approvalStatus);
// Confirm transaction now has a group-approval decision height
approvalHeight = GroupUtils.getApprovalHeight(repository, bobAssetTransaction.getTransactionData().getSignature());
assertNotNull("group-approval decision height should not be null", approvalHeight);
// Orphan blocks that decided expiry
for (int blockCount = 0; blockCount <= maxBlockDelay; ++blockCount)
BlockUtils.orphanLastBlock(repository);
// Confirm transaction no longer expired
approvalStatus = GroupUtils.getApprovalStatus(repository, bobAssetTransaction.getTransactionData().getSignature());
assertEquals("incorrect transaction approval status", ApprovalStatus.PENDING, approvalStatus);
// Confirm transaction no longer has group-approval decision height
approvalHeight = GroupUtils.getApprovalHeight(repository, bobAssetTransaction.getTransactionData().getSignature());
assertNull("group-approval decision height should be null", approvalHeight);
}
}
@Test
/** Test generic invalid. */
public void testInvalid() throws DataException {
try (final Repository repository = RepositoryManager.getRepository()) {
PrivateKeyAccount aliceAccount = Common.getTestAccount(repository, "alice");
int groupId = GroupUtils.createGroup(repository, "alice", "test", true, ApprovalThreshold.ONE, minBlockDelay, maxBlockDelay);
PrivateKeyAccount bobAccount = Common.getTestAccount(repository, "bob");
GroupUtils.joinGroup(repository, "bob", groupId);
// Bob's issue-asset transaction needs group-approval
Transaction bobAssetTransaction = buildIssueAssetTransaction(repository, "bob", groupId);
TransactionUtils.signAndForge(repository, bobAssetTransaction.getTransactionData(), bobAccount);
// Confirm transaction needs approval, and hasn't been approved
ApprovalStatus approvalStatus = GroupUtils.getApprovalStatus(repository, bobAssetTransaction.getTransactionData().getSignature());
assertEquals("incorrect transaction approval status", ApprovalStatus.PENDING, approvalStatus);
// Confirm transaction has no group-approval decision height
Integer approvalHeight = GroupUtils.getApprovalHeight(repository, bobAssetTransaction.getTransactionData().getSignature());
assertNull("group-approval decision height should be null", approvalHeight);
// Have Alice approve Bob's approval-needed transaction
GroupUtils.approveTransaction(repository, "alice", bobAssetTransaction.getTransactionData().getSignature(), true);
// But wait! Alice issues an asset with the same name before Bob's asset is issued!
// This transaction will be auto-approved as Alice is the group owner (and admin)
Transaction aliceAssetTransaction = buildIssueAssetTransaction(repository, "alice", groupId);
TransactionUtils.signAndForge(repository, aliceAssetTransaction.getTransactionData(), aliceAccount);
// Confirm Alice's transaction auto-approved
approvalStatus = GroupUtils.getApprovalStatus(repository, aliceAssetTransaction.getTransactionData().getSignature());
assertEquals("incorrect transaction approval status", ApprovalStatus.NOT_REQUIRED, approvalStatus);
// Now forge a few blocks so transaction is approved
for (int blockCount = 0; blockCount < minBlockDelay; ++blockCount)
BlockGenerator.generateTestingBlock(repository, aliceAccount);
// Confirm Bob's transaction now invalid
approvalStatus = GroupUtils.getApprovalStatus(repository, bobAssetTransaction.getTransactionData().getSignature());
assertEquals("incorrect transaction approval status", ApprovalStatus.INVALID, approvalStatus);
// Confirm transaction now has a group-approval decision height
approvalHeight = GroupUtils.getApprovalHeight(repository, bobAssetTransaction.getTransactionData().getSignature());
assertNotNull("group-approval decision height should not be null", approvalHeight);
// Orphan blocks that decided group-approval
for (int blockCount = 0; blockCount < minBlockDelay; ++blockCount)
BlockUtils.orphanLastBlock(repository);
// Confirm transaction no longer invalid
approvalStatus = GroupUtils.getApprovalStatus(repository, bobAssetTransaction.getTransactionData().getSignature());
assertEquals("incorrect transaction approval status", ApprovalStatus.PENDING, approvalStatus);
// Confirm transaction no longer has group-approval decision height
approvalHeight = GroupUtils.getApprovalHeight(repository, bobAssetTransaction.getTransactionData().getSignature());
assertNull("group-approval decision height should be null", approvalHeight);
// Orphan block containing Alice's issue-asset transaction
BlockUtils.orphanLastBlock(repository);
// Orphan block containing Alice's group-approval transaction
BlockUtils.orphanLastBlock(repository);
// Confirm transaction no longer approved
approvalStatus = GroupUtils.getApprovalStatus(repository, bobAssetTransaction.getTransactionData().getSignature());
assertEquals("incorrect transaction approval status", ApprovalStatus.PENDING, approvalStatus);
// Confirm transaction no longer has group-approval decision height
approvalHeight = GroupUtils.getApprovalHeight(repository, bobAssetTransaction.getTransactionData().getSignature());
assertNull("group-approval decision height should be null", approvalHeight);
}
}
private Transaction buildPaymentTransaction(Repository repository, String sender, String recipient, BigDecimal amount, int txGroupId) throws DataException {
PrivateKeyAccount sendingAccount = Common.getTestAccount(repository, sender);
PrivateKeyAccount recipientAccount = Common.getTestAccount(repository, recipient);