Interim work on TRANSFER_PRIVS transaction

Converted AccountData's initialLevel to blocksMintedAdjustment.

Corresponding changes to AccountLevelTransaction so that level
set in genesis block is converted to blocksMintedAdjustment,
via cumulativeBlocksByLevel.

Ditto changes to HSQLDBAccountRepository, HSQLDBDatabaseUpdates,
[HSQLDB]TransactionRepository, etc.

Changes to API call POST /admin/mintingaccounts to check passed
reward-share private key maps to a reward-share with minting
account that still has privilege to mint. It's possible for
a TRANSFER_PRIVS transaction to transfer away minting privileges
from a minting account referenced by in a reward-share.

Change to RewardShareTransaction to allow users to cancel a
reward-share even if minting-account component no longer has
minting privs. This should allow users to clean up more after
a privs transfer.

Re-order processing/orphaning in Block.process()/Block.orphan()
to be more consistent and also to take in account changes that
might have been caused by TRANSFER_PRIVS transactions which affect
who might actually receive block rewards/tx fees.

Founders now gain blocksMinted & levels as part of minting blocks.
(Needed to make TRANSFER_PRIVS from a founder account to work).

BlockMinter now has added checks to make sure that the reward-shares
it might use to mint blocks still have valid minting-accounts.
i.e. that the minting-account component of reward-share hasn't had
minting privs transferred away by TRANSFER_PRIVS tx.

Controller now rejects online-accounts from peers that no longer
have minting privs (e.g. transferred away by TRANSFER_PRIVS)
Corresponding, Controller no longer SENDS online-accounts that no
longer have minting privs to other peers.

Added some tests - more tests needed, e.g. for multiple transfers
into the same account, or a test for minting post transfer for both
sender & recipient.
This commit is contained in:
catbref
2020-01-13 15:45:48 +00:00
parent 5cd35e07d0
commit 1f7827b51f
20 changed files with 913 additions and 73 deletions

View File

@@ -0,0 +1,218 @@
package org.qora.test;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.qora.block.BlockChain;
import org.qora.data.account.AccountData;
import org.qora.data.transaction.BaseTransactionData;
import org.qora.data.transaction.TransactionData;
import org.qora.data.transaction.TransferPrivsTransactionData;
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.TestAccount;
import org.qora.test.common.TransactionUtils;
import static org.junit.Assert.*;
import java.math.BigDecimal;
import java.util.List;
public class TransferPrivsTests extends Common {
@Before
public void beforeTest() throws DataException {
Common.useDefaultSettings();
}
@After
public void afterTest() throws DataException {
Common.orphanCheck();
}
@Test
public void testAliceIntoDilbertTransferPrivs() throws DataException {
final List<Integer> cumulativeBlocksByLevel = BlockChain.getInstance().getCumulativeBlocksByLevel();
final int maximumLevel = cumulativeBlocksByLevel.size() - 1;
try (final Repository repository = RepositoryManager.getRepository()) {
TestAccount alice = Common.getTestAccount(repository, "alice");
AccountData initialAliceData = repository.getAccountRepository().getAccount(alice.getAddress());
TestAccount dilbert = Common.getTestAccount(repository, "dilbert");
AccountData initialDilbertData = repository.getAccountRepository().getAccount(dilbert.getAddress());
// Blocks needed by Alice to get Dilbert to next level post-combine
final int expectedPostCombineLevel = initialDilbertData.getLevel() + 1;
final int blocksNeeded = cumulativeBlocksByLevel.get(expectedPostCombineLevel) - initialDilbertData.getBlocksMinted() - initialDilbertData.getBlocksMintedAdjustment();
// Level we expect Alice to reach after minting above blocks
int expectedLevel = 0;
for (int newLevel = maximumLevel; newLevel > 0; --newLevel)
if (blocksNeeded >= cumulativeBlocksByLevel.get(newLevel)) {
expectedLevel = newLevel;
break;
}
// Mint enough blocks to bump recipient level when we combine accounts
for (int bc = 0; bc < blocksNeeded; ++bc)
BlockUtils.mintBlock(repository);
// Check minting account has gained level
assertEquals("minter level incorrect", expectedLevel, (int) alice.getLevel());
// Grab pre-combine versions of Alice and Dilbert data
AccountData preCombineAliceData = repository.getAccountRepository().getAccount(alice.getAddress());
AccountData preCombineDilbertData = repository.getAccountRepository().getAccount(dilbert.getAddress());
assertEquals(expectedLevel, preCombineAliceData.getLevel());
// Combine Alice into Dilbert
byte[] reference = alice.getLastReference();
long timestamp = repository.getTransactionRepository().fromSignature(reference).getTimestamp() + 1;
int txGroupId = 0;
BigDecimal fee = BigDecimal.ONE.setScale(8);
BaseTransactionData baseTransactionData = new BaseTransactionData(timestamp, txGroupId, reference, alice.getPublicKey(), fee, null);
TransactionData transactionData = new TransferPrivsTransactionData(baseTransactionData, dilbert.getAddress());
TransactionUtils.signAndMint(repository, transactionData, alice);
AccountData newAliceData = repository.getAccountRepository().getAccount(alice.getAddress());
AccountData newDilbertData = repository.getAccountRepository().getAccount(dilbert.getAddress());
checkSenderCleared(newAliceData);
// Confirm recipient has bumped level
assertEquals("recipient's level incorrect", expectedPostCombineLevel, newDilbertData.getLevel());
// Confirm recipient has gained sender's flags
assertEquals("recipient's flags should be changed", initialAliceData.getFlags() | initialDilbertData.getFlags(), (int) newDilbertData.getFlags());
// Confirm recipient has increased minted block count
assertEquals("recipient minted block count incorrect", initialDilbertData.getBlocksMinted() + initialAliceData.getBlocksMinted() + blocksNeeded + 1, newDilbertData.getBlocksMinted());
// Confirm recipient has increased minted block adjustment
assertEquals("recipient minted block adjustment incorrect", initialDilbertData.getBlocksMintedAdjustment() + initialAliceData.getBlocksMintedAdjustment(), newDilbertData.getBlocksMintedAdjustment());
// Orphan previous block
BlockUtils.orphanLastBlock(repository);
// Sender checks...
AccountData orphanedAliceData = repository.getAccountRepository().getAccount(alice.getAddress());
checkAccountDataRestored("sender", preCombineAliceData, orphanedAliceData);
// Recipient checks...
AccountData orphanedDilbertData = repository.getAccountRepository().getAccount(dilbert.getAddress());
checkAccountDataRestored("recipient", preCombineDilbertData, orphanedDilbertData);
}
}
@Test
public void testDilbertIntoAliceTransferPrivs() throws DataException {
final List<Integer> cumulativeBlocksByLevel = BlockChain.getInstance().getCumulativeBlocksByLevel();
final int maximumLevel = cumulativeBlocksByLevel.size() - 1;
try (final Repository repository = RepositoryManager.getRepository()) {
TestAccount alice = Common.getTestAccount(repository, "alice");
AccountData initialAliceData = repository.getAccountRepository().getAccount(alice.getAddress());
TestAccount dilbert = Common.getTestAccount(repository, "dilbert");
AccountData initialDilbertData = repository.getAccountRepository().getAccount(dilbert.getAddress());
// Blocks needed by Alice to get Alice to next level post-combine
final int expectedPostCombineLevel = initialDilbertData.getLevel() + 1;
final int blocksNeeded = cumulativeBlocksByLevel.get(expectedPostCombineLevel) - initialDilbertData.getBlocksMinted() - initialDilbertData.getBlocksMintedAdjustment();
// Level we expect Alice to reach after minting above blocks
int expectedLevel = 0;
for (int newLevel = maximumLevel; newLevel > 0; --newLevel)
if (blocksNeeded >= cumulativeBlocksByLevel.get(newLevel)) {
expectedLevel = newLevel;
break;
}
// Mint enough blocks to bump recipient level when we combine accounts
for (int bc = 0; bc < blocksNeeded; ++bc)
BlockUtils.mintBlock(repository);
// Check minting account has gained level
assertEquals("minter level incorrect", expectedLevel, (int) alice.getLevel());
// Grab pre-combine versions of Alice and Dilbert data
AccountData preCombineAliceData = repository.getAccountRepository().getAccount(alice.getAddress());
AccountData preCombineDilbertData = repository.getAccountRepository().getAccount(dilbert.getAddress());
assertEquals(expectedLevel, preCombineAliceData.getLevel());
// Combine Dilbert into Alice
byte[] reference = dilbert.getLastReference();
long timestamp = repository.getTransactionRepository().fromSignature(reference).getTimestamp() + 1;
int txGroupId = 0;
BigDecimal fee = BigDecimal.ONE.setScale(8);
BaseTransactionData baseTransactionData = new BaseTransactionData(timestamp, txGroupId, reference, dilbert.getPublicKey(), fee, null);
TransactionData transactionData = new TransferPrivsTransactionData(baseTransactionData, alice.getAddress());
TransactionUtils.signAndMint(repository, transactionData, dilbert);
AccountData newAliceData = repository.getAccountRepository().getAccount(alice.getAddress());
AccountData newDilbertData = repository.getAccountRepository().getAccount(dilbert.getAddress());
checkSenderCleared(newDilbertData);
// Confirm recipient has bumped level
assertEquals("recipient's level incorrect", expectedPostCombineLevel, newAliceData.getLevel());
// Confirm recipient has gained sender's flags
assertEquals("recipient's flags should be changed", initialAliceData.getFlags() | initialDilbertData.getFlags(), (int) newAliceData.getFlags());
// Confirm recipient has increased minted block count
assertEquals("recipient minted block count incorrect", initialDilbertData.getBlocksMinted() + initialAliceData.getBlocksMinted() + blocksNeeded + 1, newAliceData.getBlocksMinted());
// Confirm recipient has increased minted block adjustment
assertEquals("recipient minted block adjustment incorrect", initialDilbertData.getBlocksMintedAdjustment() + initialAliceData.getBlocksMintedAdjustment(), newAliceData.getBlocksMintedAdjustment());
// Orphan previous block
BlockUtils.orphanLastBlock(repository);
// Sender checks...
AccountData orphanedDilbertData = repository.getAccountRepository().getAccount(dilbert.getAddress());
checkAccountDataRestored("sender", preCombineDilbertData, orphanedDilbertData);
// Recipient checks...
AccountData orphanedAliceData = repository.getAccountRepository().getAccount(alice.getAddress());
checkAccountDataRestored("recipient", preCombineAliceData, orphanedAliceData);
}
}
private void checkSenderCleared(AccountData senderAccountData) {
// Confirm sender has zeroed flags
assertEquals("sender's flags should be zeroed", 0, (int) senderAccountData.getFlags());
// Confirm sender has zeroed level
assertEquals("sender's level should be zeroed", 0, (int) senderAccountData.getLevel());
// Confirm sender has zeroed minted block count
assertEquals("sender's minted block count should be zeroed", 0, (int) senderAccountData.getBlocksMinted());
// Confirm sender has zeroed minted block adjustment
assertEquals("sender's minted block adjustment should be zeroed", 0, (int) senderAccountData.getBlocksMintedAdjustment());
}
private void checkAccountDataRestored(String accountName, AccountData expectedAccountData, AccountData actualAccountData) {
// Confirm flags have been restored
assertEquals(accountName + "'s flags weren't restored", expectedAccountData.getFlags(), actualAccountData.getFlags());
// Confirm minted blocks count
assertEquals(accountName + "'s minted block count wasn't restored", expectedAccountData.getBlocksMinted(), actualAccountData.getBlocksMinted());
// Confirm minted block adjustment
assertEquals(accountName + "'s minted block adjustment wasn't restored", expectedAccountData.getBlocksMintedAdjustment(), actualAccountData.getBlocksMintedAdjustment());
// Confirm level has been restored
assertEquals(accountName + "'s level wasn't restored", expectedAccountData.getLevel(), actualAccountData.getLevel());
}
}

View File

@@ -160,10 +160,10 @@ public class Common {
AccountBalanceData initialBalance = initialBalances.get(i);
AccountBalanceData remainingBalance = remainingBalances.get(i);
assertEquals("Remaining balance's asset differs", initialBalance.getAssetId(), remainingBalance.getAssetId());
assertEquals("Remaining balance's address differs", initialBalance.getAddress(), remainingBalance.getAddress());
assertEquals(initialBalance.getAddress() + " remaining balance's asset differs", initialBalance.getAssetId(), remainingBalance.getAssetId());
assertEqualBigDecimals("Remaining balance differs", initialBalance.getBalance(), remainingBalance.getBalance());
assertEqualBigDecimals(initialBalance.getAddress() + " remaining balance differs", initialBalance.getBalance(), remainingBalance.getBalance());
}
}
}

View File

@@ -0,0 +1,17 @@
package org.qora.test.common.transaction;
import org.qora.account.PrivateKeyAccount;
import org.qora.data.transaction.TransferPrivsTransactionData;
import org.qora.data.transaction.TransactionData;
import org.qora.repository.DataException;
import org.qora.repository.Repository;
public class TransferPrivsTestTransaction extends TestTransaction {
public static TransactionData randomTransaction(Repository repository, PrivateKeyAccount account, boolean wantValid) throws DataException {
String recipient = account.getAddress();
return new TransferPrivsTransactionData(generateBase(account), recipient);
}
}