forked from Qortal/qortal
group approval tests and fixes
This commit is contained in:
parent
8af761c1c3
commit
c9f226cf88
@ -1090,7 +1090,7 @@ public class Block {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected void processBlockRewards() throws DataException {
|
protected void processBlockRewards() throws DataException {
|
||||||
BigDecimal reward = getRewardAtHeight(this.blockData.getHeight());
|
BigDecimal reward = Block.getRewardAtHeight(this.blockData.getHeight());
|
||||||
|
|
||||||
// No reward for our height?
|
// No reward for our height?
|
||||||
if (reward == null)
|
if (reward == null)
|
||||||
@ -1335,7 +1335,7 @@ public class Block {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected void orphanBlockRewards() throws DataException {
|
protected void orphanBlockRewards() throws DataException {
|
||||||
BigDecimal reward = getRewardAtHeight(this.blockData.getHeight());
|
BigDecimal reward = Block.getRewardAtHeight(this.blockData.getHeight());
|
||||||
|
|
||||||
// No reward for our height?
|
// No reward for our height?
|
||||||
if (reward == null)
|
if (reward == null)
|
||||||
@ -1397,7 +1397,7 @@ public class Block {
|
|||||||
atRepository.deleteATStates(this.blockData.getHeight());
|
atRepository.deleteATStates(this.blockData.getHeight());
|
||||||
}
|
}
|
||||||
|
|
||||||
protected BigDecimal getRewardAtHeight(int ourHeight) {
|
public static BigDecimal getRewardAtHeight(int ourHeight) {
|
||||||
List<RewardByHeight> rewardsByHeight = BlockChain.getInstance().getBlockRewardsByHeight();
|
List<RewardByHeight> rewardsByHeight = BlockChain.getInstance().getBlockRewardsByHeight();
|
||||||
|
|
||||||
// No rewards configured?
|
// No rewards configured?
|
||||||
|
@ -68,9 +68,13 @@ public class GroupApprovalTransaction extends Transaction {
|
|||||||
if (pendingTransactionData == null)
|
if (pendingTransactionData == null)
|
||||||
return ValidationResult.TRANSACTION_UNKNOWN;
|
return ValidationResult.TRANSACTION_UNKNOWN;
|
||||||
|
|
||||||
// Check pending transaction is not already in a block
|
// Check pending transaction is actually needs group approval
|
||||||
if (this.repository.getTransactionRepository().getHeightFromSignature(groupApprovalTransactionData.getPendingSignature()) != 0)
|
if (pendingTransactionData.getApprovalStatus() == ApprovalStatus.NOT_REQUIRED)
|
||||||
return ValidationResult.TRANSACTION_ALREADY_CONFIRMED;
|
return ValidationResult.GROUP_APPROVAL_NOT_REQUIRED;
|
||||||
|
|
||||||
|
// Check pending transaction is actually pending
|
||||||
|
if (pendingTransactionData.getApprovalStatus() != ApprovalStatus.PENDING)
|
||||||
|
return ValidationResult.GROUP_APPROVAL_DECIDED;
|
||||||
|
|
||||||
Account admin = getAdmin();
|
Account admin = getAdmin();
|
||||||
|
|
||||||
|
@ -230,6 +230,8 @@ public abstract class Transaction {
|
|||||||
INVALID_PUBLIC_KEY(79),
|
INVALID_PUBLIC_KEY(79),
|
||||||
AT_UNKNOWN(80),
|
AT_UNKNOWN(80),
|
||||||
AT_ALREADY_EXISTS(81),
|
AT_ALREADY_EXISTS(81),
|
||||||
|
GROUP_APPROVAL_NOT_REQUIRED(82),
|
||||||
|
GROUP_APPROVAL_DECIDED(83),
|
||||||
NOT_YET_RELEASED(1000);
|
NOT_YET_RELEASED(1000);
|
||||||
|
|
||||||
public final int value;
|
public final int value;
|
||||||
|
@ -1,101 +0,0 @@
|
|||||||
package org.qora.test;
|
|
||||||
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.qora.account.PrivateKeyAccount;
|
|
||||||
import org.qora.block.BlockChain;
|
|
||||||
import org.qora.block.BlockGenerator;
|
|
||||||
import org.qora.data.transaction.BaseTransactionData;
|
|
||||||
import org.qora.data.transaction.CreateGroupTransactionData;
|
|
||||||
import org.qora.data.transaction.PaymentTransactionData;
|
|
||||||
import org.qora.data.transaction.TransactionData;
|
|
||||||
import org.qora.group.Group;
|
|
||||||
import org.qora.group.Group.ApprovalThreshold;
|
|
||||||
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.CreateGroupTransaction;
|
|
||||||
import org.qora.transaction.PaymentTransaction;
|
|
||||||
import org.qora.transaction.Transaction;
|
|
||||||
import org.qora.transaction.Transaction.ValidationResult;
|
|
||||||
|
|
||||||
import static org.junit.Assert.*;
|
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
|
||||||
|
|
||||||
public class GroupApprovalTests extends Common {
|
|
||||||
|
|
||||||
/** Check that a tx type that doesn't need approval doesn't accept txGroupId apart from NO_GROUP */
|
|
||||||
@Test
|
|
||||||
public void testNonApprovalTxGroupId() throws DataException {
|
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
|
||||||
BlockChain.validate();
|
|
||||||
|
|
||||||
TransactionData transactionData = buildPayment(repository, Group.NO_GROUP);
|
|
||||||
Transaction transaction = new PaymentTransaction(repository, transactionData);
|
|
||||||
assertEquals(ValidationResult.OK, transaction.isValidUnconfirmed());
|
|
||||||
|
|
||||||
int groupId = createGroup(repository);
|
|
||||||
|
|
||||||
transactionData = buildPayment(repository, groupId);
|
|
||||||
transaction = new PaymentTransaction(repository, transactionData);
|
|
||||||
assertEquals(ValidationResult.INVALID_TX_GROUP_ID, transaction.isValidUnconfirmed());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private PaymentTransactionData buildPayment(Repository repository, int txGroupId) throws DataException {
|
|
||||||
long timestamp = System.currentTimeMillis() - 1000L;
|
|
||||||
byte[] reference = repository.getAccountRepository().getLastReference(v2testAddress);
|
|
||||||
byte[] senderPublicKey = v2testPublicKey;
|
|
||||||
String recipient = v2testAddress;
|
|
||||||
BigDecimal amount = BigDecimal.ONE.setScale(8);
|
|
||||||
BigDecimal fee = BigDecimal.ONE.setScale(8);
|
|
||||||
|
|
||||||
BaseTransactionData baseTransactionData = new BaseTransactionData(timestamp, txGroupId, reference, senderPublicKey, fee, null);
|
|
||||||
return new PaymentTransactionData(baseTransactionData, recipient, amount);
|
|
||||||
}
|
|
||||||
|
|
||||||
private int createGroup(Repository repository) throws DataException {
|
|
||||||
long timestamp = System.currentTimeMillis() - 1000L;
|
|
||||||
int txGroupId = Group.NO_GROUP;
|
|
||||||
byte[] reference = repository.getAccountRepository().getLastReference(v2testAddress);
|
|
||||||
byte[] creatorPublicKey = v2testPublicKey;
|
|
||||||
String owner = v2testAddress;
|
|
||||||
String groupName = "test-group";
|
|
||||||
String description = "test group description";
|
|
||||||
boolean isOpen = false;
|
|
||||||
ApprovalThreshold approvalThreshold = ApprovalThreshold.ONE;
|
|
||||||
int minimumBlockDelay = 0;
|
|
||||||
int maximumBlockDelay = 1440;
|
|
||||||
BigDecimal fee = BigDecimal.ONE.setScale(8);
|
|
||||||
|
|
||||||
BaseTransactionData baseTransactionData = new BaseTransactionData(timestamp, txGroupId, reference, creatorPublicKey, fee, null);
|
|
||||||
TransactionData transactionData = new CreateGroupTransactionData(baseTransactionData, owner, groupName, description,
|
|
||||||
isOpen, approvalThreshold, minimumBlockDelay, maximumBlockDelay);
|
|
||||||
Transaction transaction = new CreateGroupTransaction(repository, transactionData);
|
|
||||||
|
|
||||||
// Sign transaction
|
|
||||||
PrivateKeyAccount signer = new PrivateKeyAccount(repository, v2testPrivateKey);
|
|
||||||
transaction.sign(signer);
|
|
||||||
|
|
||||||
// Add to unconfirmed
|
|
||||||
if (!transaction.isSignatureValid())
|
|
||||||
throw new RuntimeException("CREATE_GROUP transaction's signature invalid");
|
|
||||||
|
|
||||||
ValidationResult result = transaction.isValidUnconfirmed();
|
|
||||||
if (result != ValidationResult.OK)
|
|
||||||
throw new RuntimeException(String.format("CREATE_GROUP transaction invalid: %s", result.name()));
|
|
||||||
|
|
||||||
repository.getTransactionRepository().save(transactionData);
|
|
||||||
repository.getTransactionRepository().unconfirmTransaction(transactionData);
|
|
||||||
repository.saveChanges();
|
|
||||||
|
|
||||||
// Generate block
|
|
||||||
BlockGenerator.generateTestingBlock(repository, signer);
|
|
||||||
|
|
||||||
// Return assigned groupId
|
|
||||||
transactionData = repository.getTransactionRepository().fromSignature(transactionData.getSignature());
|
|
||||||
return ((CreateGroupTransactionData) transactionData).getGroupId();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
25
src/test/java/org/qora/test/common/BlockUtils.java
Normal file
25
src/test/java/org/qora/test/common/BlockUtils.java
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
package org.qora.test.common;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
|
||||||
|
import org.qora.block.Block;
|
||||||
|
import org.qora.data.block.BlockData;
|
||||||
|
import org.qora.repository.DataException;
|
||||||
|
import org.qora.repository.Repository;
|
||||||
|
|
||||||
|
public class BlockUtils {
|
||||||
|
|
||||||
|
public static BigDecimal getNextBlockReward(Repository repository) throws DataException {
|
||||||
|
int currentHeight = repository.getBlockRepository().getBlockchainHeight();
|
||||||
|
|
||||||
|
return Block.getRewardAtHeight(currentHeight + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void orphanLastBlock(Repository repository) throws DataException {
|
||||||
|
BlockData blockData = repository.getBlockRepository().getLastBlock();
|
||||||
|
Block block = new Block(repository, blockData);
|
||||||
|
block.orphan();
|
||||||
|
repository.saveChanges();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
66
src/test/java/org/qora/test/common/GroupUtils.java
Normal file
66
src/test/java/org/qora/test/common/GroupUtils.java
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
package org.qora.test.common;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
|
||||||
|
import org.qora.account.PrivateKeyAccount;
|
||||||
|
import org.qora.data.transaction.BaseTransactionData;
|
||||||
|
import org.qora.data.transaction.CreateGroupTransactionData;
|
||||||
|
import org.qora.data.transaction.GroupApprovalTransactionData;
|
||||||
|
import org.qora.data.transaction.JoinGroupTransactionData;
|
||||||
|
import org.qora.data.transaction.TransactionData;
|
||||||
|
import org.qora.group.Group;
|
||||||
|
import org.qora.group.Group.ApprovalThreshold;
|
||||||
|
import org.qora.repository.DataException;
|
||||||
|
import org.qora.repository.Repository;
|
||||||
|
import org.qora.transaction.Transaction.ApprovalStatus;
|
||||||
|
|
||||||
|
public class GroupUtils {
|
||||||
|
|
||||||
|
public static final int txGroupId = Group.NO_GROUP;
|
||||||
|
public static final BigDecimal fee = BigDecimal.ONE.setScale(8);
|
||||||
|
|
||||||
|
public static int createGroup(Repository repository, String creatorAccountName, String groupName, boolean isOpen, ApprovalThreshold approvalThreshold,
|
||||||
|
int minimumBlockDelay, int maximumBlockDelay) throws DataException {
|
||||||
|
PrivateKeyAccount account = Common.getTestAccount(repository, creatorAccountName);
|
||||||
|
|
||||||
|
byte[] reference = account.getLastReference();
|
||||||
|
long timestamp = repository.getTransactionRepository().fromSignature(reference).getTimestamp() + 1;
|
||||||
|
String groupDescription = groupName + " (test group)";
|
||||||
|
|
||||||
|
BaseTransactionData baseTransactionData = new BaseTransactionData(timestamp, Group.NO_GROUP, reference, account.getPublicKey(), GroupUtils.fee, null);
|
||||||
|
TransactionData transactionData = new CreateGroupTransactionData(baseTransactionData, account.getAddress(), groupName, groupDescription, isOpen, approvalThreshold, minimumBlockDelay, maximumBlockDelay);
|
||||||
|
|
||||||
|
TransactionUtils.signAndForge(repository, transactionData, account);
|
||||||
|
|
||||||
|
return repository.getGroupRepository().fromGroupName(groupName).getGroupId();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void joinGroup(Repository repository, String joinerAccountName, int groupId) throws DataException {
|
||||||
|
PrivateKeyAccount account = Common.getTestAccount(repository, joinerAccountName);
|
||||||
|
|
||||||
|
byte[] reference = account.getLastReference();
|
||||||
|
long timestamp = repository.getTransactionRepository().fromSignature(reference).getTimestamp() + 1;
|
||||||
|
|
||||||
|
BaseTransactionData baseTransactionData = new BaseTransactionData(timestamp, Group.NO_GROUP, reference, account.getPublicKey(), GroupUtils.fee, null);
|
||||||
|
TransactionData transactionData = new JoinGroupTransactionData(baseTransactionData, groupId);
|
||||||
|
|
||||||
|
TransactionUtils.signAndForge(repository, transactionData, account);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void approveTransaction(Repository repository, String accountName, byte[] pendingSignature, boolean decision) throws DataException {
|
||||||
|
PrivateKeyAccount account = Common.getTestAccount(repository, accountName);
|
||||||
|
|
||||||
|
byte[] reference = account.getLastReference();
|
||||||
|
long timestamp = repository.getTransactionRepository().fromSignature(reference).getTimestamp() + 1;
|
||||||
|
|
||||||
|
BaseTransactionData baseTransactionData = new BaseTransactionData(timestamp, Group.NO_GROUP, reference, account.getPublicKey(), GroupUtils.fee, null);
|
||||||
|
TransactionData transactionData = new GroupApprovalTransactionData(baseTransactionData, pendingSignature, decision);
|
||||||
|
|
||||||
|
TransactionUtils.signAndForge(repository, transactionData, account);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ApprovalStatus getApprovalStatus(Repository repository, byte[] signature) throws DataException {
|
||||||
|
return repository.getTransactionRepository().fromSignature(signature).getApprovalStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -5,6 +5,7 @@ import org.qora.repository.Repository;
|
|||||||
import org.qora.utils.Base58;
|
import org.qora.utils.Base58;
|
||||||
|
|
||||||
public class TestAccount extends PrivateKeyAccount {
|
public class TestAccount extends PrivateKeyAccount {
|
||||||
|
|
||||||
public final String accountName;
|
public final String accountName;
|
||||||
|
|
||||||
public TestAccount(Repository repository, String accountName, byte[] privateKey) {
|
public TestAccount(Repository repository, String accountName, byte[] privateKey) {
|
||||||
@ -16,4 +17,5 @@ public class TestAccount extends PrivateKeyAccount {
|
|||||||
public TestAccount(Repository repository, String accountName, String privateKey) {
|
public TestAccount(Repository repository, String accountName, String privateKey) {
|
||||||
this(repository, accountName, Base58.decode(privateKey));
|
this(repository, accountName, Base58.decode(privateKey));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,7 @@ import org.qora.repository.DataException;
|
|||||||
import org.qora.repository.Repository;
|
import org.qora.repository.Repository;
|
||||||
import org.qora.repository.RepositoryManager;
|
import org.qora.repository.RepositoryManager;
|
||||||
import org.qora.test.common.AccountUtils;
|
import org.qora.test.common.AccountUtils;
|
||||||
|
import org.qora.test.common.BlockUtils;
|
||||||
import org.qora.test.common.Common;
|
import org.qora.test.common.Common;
|
||||||
|
|
||||||
public class RewardTests extends Common {
|
public class RewardTests extends Common {
|
||||||
@ -38,11 +39,11 @@ public class RewardTests extends Common {
|
|||||||
|
|
||||||
PrivateKeyAccount forgingAccount = Common.getTestAccount(repository, "alice");
|
PrivateKeyAccount forgingAccount = Common.getTestAccount(repository, "alice");
|
||||||
|
|
||||||
BigDecimal firstReward = BlockChain.getInstance().getBlockRewardsByHeight().get(0).reward;
|
BigDecimal blockReward = BlockUtils.getNextBlockReward(repository);
|
||||||
|
|
||||||
BlockGenerator.generateTestingBlock(repository, forgingAccount);
|
BlockGenerator.generateTestingBlock(repository, forgingAccount);
|
||||||
|
|
||||||
BigDecimal expectedBalance = initialBalances.get("alice").get(Asset.QORA).add(firstReward);
|
BigDecimal expectedBalance = initialBalances.get("alice").get(Asset.QORA).add(blockReward);
|
||||||
AccountUtils.assertBalance(repository, "alice", Asset.QORA, expectedBalance);
|
AccountUtils.assertBalance(repository, "alice", Asset.QORA, expectedBalance);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -84,16 +85,15 @@ public class RewardTests extends Common {
|
|||||||
PrivateKeyAccount proxyAccount = new PrivateKeyAccount(repository, proxyPrivateKey);
|
PrivateKeyAccount proxyAccount = new PrivateKeyAccount(repository, proxyPrivateKey);
|
||||||
|
|
||||||
Map<String, Map<Long, BigDecimal>> initialBalances = AccountUtils.getBalances(repository, Asset.QORA);
|
Map<String, Map<Long, BigDecimal>> initialBalances = AccountUtils.getBalances(repository, Asset.QORA);
|
||||||
|
BigDecimal blockReward = BlockUtils.getNextBlockReward(repository);
|
||||||
BlockGenerator.generateTestingBlock(repository, proxyAccount);
|
BlockGenerator.generateTestingBlock(repository, proxyAccount);
|
||||||
|
|
||||||
// We're expected reward * 12.8% to Bob, the rest to Alice
|
// We're expecting reward * 12.8% to Bob, the rest to Alice
|
||||||
// (first reward is good for first 10 blocks)
|
|
||||||
BigDecimal firstReward = BlockChain.getInstance().getBlockRewardsByHeight().get(0).reward;
|
|
||||||
|
|
||||||
BigDecimal bobShare = firstReward.multiply(share.movePointLeft(2)).setScale(8, RoundingMode.DOWN);
|
BigDecimal bobShare = blockReward.multiply(share.movePointLeft(2)).setScale(8, RoundingMode.DOWN);
|
||||||
AccountUtils.assertBalance(repository, "bob", Asset.QORA, initialBalances.get("bob").get(Asset.QORA).add(bobShare));
|
AccountUtils.assertBalance(repository, "bob", Asset.QORA, initialBalances.get("bob").get(Asset.QORA).add(bobShare));
|
||||||
|
|
||||||
BigDecimal aliceShare = firstReward.subtract(bobShare);
|
BigDecimal aliceShare = blockReward.subtract(bobShare);
|
||||||
AccountUtils.assertBalance(repository, "alice", Asset.QORA, initialBalances.get("alice").get(Asset.QORA).add(aliceShare));
|
AccountUtils.assertBalance(repository, "alice", Asset.QORA, initialBalances.get("alice").get(Asset.QORA).add(aliceShare));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
184
src/test/java/org/qora/test/group/GroupApprovalTests.java
Normal file
184
src/test/java/org/qora/test/group/GroupApprovalTests.java
Normal file
@ -0,0 +1,184 @@
|
|||||||
|
package org.qora.test.group;
|
||||||
|
|
||||||
|
import org.junit.After;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.qora.account.PrivateKeyAccount;
|
||||||
|
import org.qora.asset.Asset;
|
||||||
|
import org.qora.block.BlockGenerator;
|
||||||
|
import org.qora.data.transaction.BaseTransactionData;
|
||||||
|
import org.qora.data.transaction.IssueAssetTransactionData;
|
||||||
|
import org.qora.data.transaction.PaymentTransactionData;
|
||||||
|
import org.qora.data.transaction.TransactionData;
|
||||||
|
import org.qora.group.Group;
|
||||||
|
import org.qora.group.Group.ApprovalThreshold;
|
||||||
|
import org.qora.repository.DataException;
|
||||||
|
import org.qora.repository.Repository;
|
||||||
|
import org.qora.repository.RepositoryManager;
|
||||||
|
import org.qora.test.common.BlockUtils;
|
||||||
|
import org.qora.test.common.Common;
|
||||||
|
import org.qora.test.common.GroupUtils;
|
||||||
|
import org.qora.test.common.TransactionUtils;
|
||||||
|
import org.qora.transaction.Transaction;
|
||||||
|
import org.qora.transaction.Transaction.ApprovalStatus;
|
||||||
|
import org.qora.transaction.Transaction.ValidationResult;
|
||||||
|
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
public class GroupApprovalTests extends Common {
|
||||||
|
|
||||||
|
private static final BigDecimal amount = BigDecimal.valueOf(5000L).setScale(8);
|
||||||
|
private static final BigDecimal fee = BigDecimal.ONE.setScale(8);
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void beforeTest() throws DataException {
|
||||||
|
Common.useDefaultSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
public void afterTest() throws DataException {
|
||||||
|
Common.orphanCheck();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
/** Check that a transaction type that doesn't need approval doesn't accept txGroupId apart from NO_GROUP */
|
||||||
|
public void testNonApprovalTxGroupId() throws DataException {
|
||||||
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
|
Transaction transaction = buildPaymentTransaction(repository, "alice", "bob", amount, Group.NO_GROUP);
|
||||||
|
assertEquals(ValidationResult.OK, transaction.isValidUnconfirmed());
|
||||||
|
|
||||||
|
int groupId = GroupUtils.createGroup(repository, "alice", "test", true, ApprovalThreshold.NONE, 0, 10);
|
||||||
|
|
||||||
|
transaction = buildPaymentTransaction(repository, "alice", "bob", amount, groupId);
|
||||||
|
assertEquals(ValidationResult.INVALID_TX_GROUP_ID, transaction.isValidUnconfirmed());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@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");
|
||||||
|
|
||||||
|
int groupId = GroupUtils.createGroup(repository, "alice", "test", true, ApprovalThreshold.ONE, minBlockDelay, maxBlockDelay);
|
||||||
|
|
||||||
|
GroupUtils.joinGroup(repository, "bob", groupId);
|
||||||
|
|
||||||
|
PrivateKeyAccount bobAccount = Common.getTestAccount(repository, "bob");
|
||||||
|
byte[] bobOriginalReference = bobAccount.getLastReference();
|
||||||
|
|
||||||
|
BigDecimal aliceOriginalBalance = aliceAccount.getConfirmedBalance(Asset.QORA);
|
||||||
|
BigDecimal bobOriginalBalance = bobAccount.getConfirmedBalance(Asset.QORA);
|
||||||
|
|
||||||
|
BigDecimal blockReward = BlockUtils.getNextBlockReward(repository);
|
||||||
|
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);
|
||||||
|
|
||||||
|
// Bob's last-reference should have changed, even though the transaction itself hasn't been approved yet
|
||||||
|
byte[] bobPostAssetReference = bobAccount.getLastReference();
|
||||||
|
assertFalse("reference should have changed", Arrays.equals(bobOriginalReference, bobPostAssetReference));
|
||||||
|
|
||||||
|
// Bob's balance should have the fee removed, even though the transaction itself hasn't been approved yet
|
||||||
|
BigDecimal bobPostAssetBalance = bobAccount.getConfirmedBalance(Asset.QORA);
|
||||||
|
Common.assertEqualBigDecimals("approval-pending transaction creator's balance incorrect", bobOriginalBalance.subtract(fee), bobPostAssetBalance);
|
||||||
|
|
||||||
|
// Transaction fee should have ended up in forging account
|
||||||
|
BigDecimal alicePostAssetBalance = aliceAccount.getConfirmedBalance(Asset.QORA);
|
||||||
|
Common.assertEqualBigDecimals("block forger's balance incorrect", aliceOriginalBalance.add(blockReward).add(fee), alicePostAssetBalance);
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
|
||||||
|
byte[] bobPostPaymentReference = bobAccount.getLastReference();
|
||||||
|
assertFalse("reference should have changed", Arrays.equals(bobPostAssetReference, bobPostPaymentReference));
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
|
||||||
|
// Check Bob's last reference hasn't been changed by transaction approval
|
||||||
|
byte[] bobPostApprovalReference = bobAccount.getLastReference();
|
||||||
|
assertTrue("reference should be unchanged", Arrays.equals(bobPostPaymentReference, bobPostApprovalReference));
|
||||||
|
|
||||||
|
// Ok, now unwind/orphan all the above to double-check
|
||||||
|
|
||||||
|
// Orphan blocks that decided transaction approval
|
||||||
|
for (int blockCount = 0; blockCount < minBlockDelay; ++blockCount)
|
||||||
|
BlockUtils.orphanLastBlock(repository);
|
||||||
|
|
||||||
|
// Check Bob's last reference is still correct
|
||||||
|
byte[] bobReference = bobAccount.getLastReference();
|
||||||
|
assertTrue("reference should be unchanged", Arrays.equals(bobPostPaymentReference, bobReference));
|
||||||
|
|
||||||
|
// Orphan block containing Alice's group-approval transaction
|
||||||
|
BlockUtils.orphanLastBlock(repository);
|
||||||
|
|
||||||
|
// Check Bob's last reference is still correct
|
||||||
|
bobReference = bobAccount.getLastReference();
|
||||||
|
assertTrue("reference should be unchanged", Arrays.equals(bobPostPaymentReference, bobReference));
|
||||||
|
|
||||||
|
// Orphan block containing Bob's non-approval payment transaction
|
||||||
|
BlockUtils.orphanLastBlock(repository);
|
||||||
|
|
||||||
|
// Check Bob's last reference has reverted to pre-payment value
|
||||||
|
bobReference = bobAccount.getLastReference();
|
||||||
|
assertTrue("reference should be pre-payment", Arrays.equals(bobPostAssetReference, bobReference));
|
||||||
|
|
||||||
|
// Orphan block containing Bob's issue-asset approval-needed transaction
|
||||||
|
BlockUtils.orphanLastBlock(repository);
|
||||||
|
|
||||||
|
// Check Bob's last reference has reverted to original value
|
||||||
|
bobReference = bobAccount.getLastReference();
|
||||||
|
assertTrue("reference should be pre-payment", Arrays.equals(bobOriginalReference, bobReference));
|
||||||
|
|
||||||
|
// Also check Bob's balance is back to original value
|
||||||
|
BigDecimal bobBalance = bobAccount.getConfirmedBalance(Asset.QORA);
|
||||||
|
Common.assertEqualBigDecimals("reverted balance doesn't match original", bobOriginalBalance, bobBalance);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
byte[] reference = sendingAccount.getLastReference();
|
||||||
|
long timestamp = repository.getTransactionRepository().fromSignature(reference).getTimestamp() + 1;
|
||||||
|
|
||||||
|
BaseTransactionData baseTransactionData = new BaseTransactionData(timestamp, txGroupId, reference, sendingAccount.getPublicKey(), fee, null);
|
||||||
|
PaymentTransactionData transactionData = new PaymentTransactionData(baseTransactionData, recipientAccount.getAddress(), amount);
|
||||||
|
|
||||||
|
return Transaction.fromData(repository, transactionData);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Transaction buildIssueAssetTransaction(Repository repository, String testAccountName, int txGroupId) throws DataException {
|
||||||
|
PrivateKeyAccount account = Common.getTestAccount(repository, testAccountName);
|
||||||
|
|
||||||
|
byte[] reference = account.getLastReference();
|
||||||
|
long timestamp = repository.getTransactionRepository().fromSignature(reference).getTimestamp() + 1;
|
||||||
|
|
||||||
|
BaseTransactionData baseTransactionData = new BaseTransactionData(timestamp, txGroupId, reference, account.getPublicKey(), fee, null);
|
||||||
|
TransactionData transactionData = new IssueAssetTransactionData(baseTransactionData, account.getAddress(), "test asset", "test asset desc", 1000L, true, "{}");
|
||||||
|
|
||||||
|
return Transaction.fromData(repository, transactionData);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user