mirror of
https://github.com/Qortal/qortal.git
synced 2025-07-23 04:36:50 +00:00
Merge remote-tracking branch 'qortal/master'
# Conflicts: # .gitignore # pom.xml # src/main/java/org/qortal/controller/Controller.java # src/main/java/org/qortal/gui/SysTray.java # src/main/java/org/qortal/settings/Settings.java # src/main/resources/i18n/ApiError_en.properties # src/test/java/org/qortal/test/CryptoTests.java # src/test/resources/test-settings-v2.json
This commit is contained in:
705
src/test/java/org/qortal/test/BlockArchiveTests.java
Normal file
705
src/test/java/org/qortal/test/BlockArchiveTests.java
Normal file
@@ -0,0 +1,705 @@
|
||||
package org.qortal.test;
|
||||
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.qortal.account.PrivateKeyAccount;
|
||||
import org.qortal.controller.BlockMinter;
|
||||
import org.qortal.data.at.ATStateData;
|
||||
import org.qortal.data.block.BlockData;
|
||||
import org.qortal.data.transaction.TransactionData;
|
||||
import org.qortal.repository.*;
|
||||
import org.qortal.repository.hsqldb.HSQLDBDatabaseArchiving;
|
||||
import org.qortal.repository.hsqldb.HSQLDBDatabasePruning;
|
||||
import org.qortal.repository.hsqldb.HSQLDBRepository;
|
||||
import org.qortal.settings.Settings;
|
||||
import org.qortal.test.common.AtUtils;
|
||||
import org.qortal.test.common.BlockUtils;
|
||||
import org.qortal.test.common.Common;
|
||||
import org.qortal.transaction.DeployAtTransaction;
|
||||
import org.qortal.transaction.Transaction;
|
||||
import org.qortal.transform.TransformationException;
|
||||
import org.qortal.utils.BlockArchiveUtils;
|
||||
import org.qortal.utils.NTP;
|
||||
import org.qortal.utils.Triple;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.sql.SQLException;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
public class BlockArchiveTests extends Common {
|
||||
|
||||
@Before
|
||||
public void beforeTest() throws DataException {
|
||||
Common.useSettings("test-settings-v2-block-archive.json");
|
||||
NTP.setFixedOffset(Settings.getInstance().getTestNtpOffset());
|
||||
this.deleteArchiveDirectory();
|
||||
}
|
||||
|
||||
@After
|
||||
public void afterTest() throws DataException {
|
||||
this.deleteArchiveDirectory();
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testWriter() throws DataException, InterruptedException, TransformationException, IOException {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
|
||||
// Mint some blocks so that we are able to archive them later
|
||||
for (int i = 0; i < 1000; i++) {
|
||||
BlockMinter.mintTestingBlock(repository, Common.getTestAccount(repository, "alice-reward-share"));
|
||||
}
|
||||
|
||||
// 900 blocks are trimmed (this specifies the first untrimmed height)
|
||||
repository.getBlockRepository().setOnlineAccountsSignaturesTrimHeight(901);
|
||||
repository.getATRepository().setAtTrimHeight(901);
|
||||
|
||||
// Check the max archive height - this should be one less than the first untrimmed height
|
||||
final int maximumArchiveHeight = BlockArchiveWriter.getMaxArchiveHeight(repository);
|
||||
assertEquals(900, maximumArchiveHeight);
|
||||
|
||||
// Write blocks 2-900 to the archive
|
||||
BlockArchiveWriter writer = new BlockArchiveWriter(0, maximumArchiveHeight, repository);
|
||||
writer.setShouldEnforceFileSizeTarget(false); // To avoid the need to pre-calculate file sizes
|
||||
BlockArchiveWriter.BlockArchiveWriteResult result = writer.write();
|
||||
assertEquals(BlockArchiveWriter.BlockArchiveWriteResult.OK, result);
|
||||
|
||||
// Make sure that the archive contains the correct number of blocks
|
||||
assertEquals(900 - 1, writer.getWrittenCount());
|
||||
|
||||
// Increment block archive height
|
||||
repository.getBlockArchiveRepository().setBlockArchiveHeight(writer.getWrittenCount());
|
||||
repository.saveChanges();
|
||||
assertEquals(900 - 1, repository.getBlockArchiveRepository().getBlockArchiveHeight());
|
||||
|
||||
// Ensure the file exists
|
||||
File outputFile = writer.getOutputPath().toFile();
|
||||
assertTrue(outputFile.exists());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWriterAndReader() throws DataException, InterruptedException, TransformationException, IOException {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
|
||||
// Mint some blocks so that we are able to archive them later
|
||||
for (int i = 0; i < 1000; i++) {
|
||||
BlockMinter.mintTestingBlock(repository, Common.getTestAccount(repository, "alice-reward-share"));
|
||||
}
|
||||
|
||||
// 900 blocks are trimmed (this specifies the first untrimmed height)
|
||||
repository.getBlockRepository().setOnlineAccountsSignaturesTrimHeight(901);
|
||||
repository.getATRepository().setAtTrimHeight(901);
|
||||
|
||||
// Check the max archive height - this should be one less than the first untrimmed height
|
||||
final int maximumArchiveHeight = BlockArchiveWriter.getMaxArchiveHeight(repository);
|
||||
assertEquals(900, maximumArchiveHeight);
|
||||
|
||||
// Write blocks 2-900 to the archive
|
||||
BlockArchiveWriter writer = new BlockArchiveWriter(0, maximumArchiveHeight, repository);
|
||||
writer.setShouldEnforceFileSizeTarget(false); // To avoid the need to pre-calculate file sizes
|
||||
BlockArchiveWriter.BlockArchiveWriteResult result = writer.write();
|
||||
assertEquals(BlockArchiveWriter.BlockArchiveWriteResult.OK, result);
|
||||
|
||||
// Make sure that the archive contains the correct number of blocks
|
||||
assertEquals(900 - 1, writer.getWrittenCount());
|
||||
|
||||
// Increment block archive height
|
||||
repository.getBlockArchiveRepository().setBlockArchiveHeight(writer.getWrittenCount());
|
||||
repository.saveChanges();
|
||||
assertEquals(900 - 1, repository.getBlockArchiveRepository().getBlockArchiveHeight());
|
||||
|
||||
// Ensure the file exists
|
||||
File outputFile = writer.getOutputPath().toFile();
|
||||
assertTrue(outputFile.exists());
|
||||
|
||||
// Read block 2 from the archive
|
||||
BlockArchiveReader reader = BlockArchiveReader.getInstance();
|
||||
Triple<BlockData, List<TransactionData>, List<ATStateData>> block2Info = reader.fetchBlockAtHeight(2);
|
||||
BlockData block2ArchiveData = block2Info.getA();
|
||||
|
||||
// Read block 2 from the repository
|
||||
BlockData block2RepositoryData = repository.getBlockRepository().fromHeight(2);
|
||||
|
||||
// Ensure the values match
|
||||
assertEquals(block2ArchiveData.getHeight(), block2RepositoryData.getHeight());
|
||||
assertArrayEquals(block2ArchiveData.getSignature(), block2RepositoryData.getSignature());
|
||||
|
||||
// Test some values in the archive
|
||||
assertEquals(1, block2ArchiveData.getOnlineAccountsCount());
|
||||
|
||||
// Read block 900 from the archive
|
||||
Triple<BlockData, List<TransactionData>, List<ATStateData>> block900Info = reader.fetchBlockAtHeight(900);
|
||||
BlockData block900ArchiveData = block900Info.getA();
|
||||
|
||||
// Read block 900 from the repository
|
||||
BlockData block900RepositoryData = repository.getBlockRepository().fromHeight(900);
|
||||
|
||||
// Ensure the values match
|
||||
assertEquals(block900ArchiveData.getHeight(), block900RepositoryData.getHeight());
|
||||
assertArrayEquals(block900ArchiveData.getSignature(), block900RepositoryData.getSignature());
|
||||
|
||||
// Test some values in the archive
|
||||
assertEquals(1, block900ArchiveData.getOnlineAccountsCount());
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testArchivedAtStates() throws DataException, InterruptedException, TransformationException, IOException {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
|
||||
// Deploy an AT so that we have AT state data
|
||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "alice");
|
||||
byte[] creationBytes = AtUtils.buildSimpleAT();
|
||||
long fundingAmount = 1_00000000L;
|
||||
DeployAtTransaction deployAtTransaction = AtUtils.doDeployAT(repository, deployer, creationBytes, fundingAmount);
|
||||
String atAddress = deployAtTransaction.getATAccount().getAddress();
|
||||
|
||||
// Mint some blocks so that we are able to archive them later
|
||||
for (int i = 0; i < 1000; i++) {
|
||||
BlockMinter.mintTestingBlock(repository, Common.getTestAccount(repository, "alice-reward-share"));
|
||||
}
|
||||
|
||||
// 9 blocks are trimmed (this specifies the first untrimmed height)
|
||||
repository.getBlockRepository().setOnlineAccountsSignaturesTrimHeight(10);
|
||||
repository.getATRepository().setAtTrimHeight(10);
|
||||
|
||||
// Check the max archive height
|
||||
final int maximumArchiveHeight = BlockArchiveWriter.getMaxArchiveHeight(repository);
|
||||
assertEquals(9, maximumArchiveHeight);
|
||||
|
||||
// Write blocks 2-9 to the archive
|
||||
BlockArchiveWriter writer = new BlockArchiveWriter(0, maximumArchiveHeight, repository);
|
||||
writer.setShouldEnforceFileSizeTarget(false); // To avoid the need to pre-calculate file sizes
|
||||
BlockArchiveWriter.BlockArchiveWriteResult result = writer.write();
|
||||
assertEquals(BlockArchiveWriter.BlockArchiveWriteResult.OK, result);
|
||||
|
||||
// Make sure that the archive contains the correct number of blocks
|
||||
assertEquals(9 - 1, writer.getWrittenCount());
|
||||
|
||||
// Increment block archive height
|
||||
repository.getBlockArchiveRepository().setBlockArchiveHeight(writer.getWrittenCount());
|
||||
repository.saveChanges();
|
||||
assertEquals(9 - 1, repository.getBlockArchiveRepository().getBlockArchiveHeight());
|
||||
|
||||
// Ensure the file exists
|
||||
File outputFile = writer.getOutputPath().toFile();
|
||||
assertTrue(outputFile.exists());
|
||||
|
||||
// Check blocks 3-9
|
||||
for (Integer testHeight = 2; testHeight <= 9; testHeight++) {
|
||||
|
||||
// Read a block from the archive
|
||||
BlockArchiveReader reader = BlockArchiveReader.getInstance();
|
||||
Triple<BlockData, List<TransactionData>, List<ATStateData>> blockInfo = reader.fetchBlockAtHeight(testHeight);
|
||||
BlockData archivedBlockData = blockInfo.getA();
|
||||
ATStateData archivedAtStateData = blockInfo.getC().isEmpty() ? null : blockInfo.getC().get(0);
|
||||
List<TransactionData> archivedTransactions = blockInfo.getB();
|
||||
|
||||
// Read the same block from the repository
|
||||
BlockData repositoryBlockData = repository.getBlockRepository().fromHeight(testHeight);
|
||||
ATStateData repositoryAtStateData = repository.getATRepository().getATStateAtHeight(atAddress, testHeight);
|
||||
|
||||
// Ensure the repository has full AT state data
|
||||
assertNotNull(repositoryAtStateData.getStateHash());
|
||||
assertNotNull(repositoryAtStateData.getStateData());
|
||||
|
||||
// Check the archived AT state
|
||||
if (testHeight == 2) {
|
||||
// Block 2 won't have an AT state hash because it's initial (and has the DEPLOY_AT in the same block)
|
||||
assertNull(archivedAtStateData);
|
||||
|
||||
assertEquals(1, archivedTransactions.size());
|
||||
assertEquals(Transaction.TransactionType.DEPLOY_AT, archivedTransactions.get(0).getType());
|
||||
}
|
||||
else {
|
||||
// For blocks 3+, ensure the archive has the AT state data, but not the hashes
|
||||
assertNotNull(archivedAtStateData.getStateHash());
|
||||
assertNull(archivedAtStateData.getStateData());
|
||||
|
||||
// They also shouldn't have any transactions
|
||||
assertTrue(archivedTransactions.isEmpty());
|
||||
}
|
||||
|
||||
// Also check the online accounts count and height
|
||||
assertEquals(1, archivedBlockData.getOnlineAccountsCount());
|
||||
assertEquals(testHeight, archivedBlockData.getHeight());
|
||||
|
||||
// Ensure the values match
|
||||
assertEquals(archivedBlockData.getHeight(), repositoryBlockData.getHeight());
|
||||
assertArrayEquals(archivedBlockData.getSignature(), repositoryBlockData.getSignature());
|
||||
assertEquals(archivedBlockData.getOnlineAccountsCount(), repositoryBlockData.getOnlineAccountsCount());
|
||||
assertArrayEquals(archivedBlockData.getMinterSignature(), repositoryBlockData.getMinterSignature());
|
||||
assertEquals(archivedBlockData.getATCount(), repositoryBlockData.getATCount());
|
||||
assertEquals(archivedBlockData.getOnlineAccountsCount(), repositoryBlockData.getOnlineAccountsCount());
|
||||
assertArrayEquals(archivedBlockData.getReference(), repositoryBlockData.getReference());
|
||||
assertEquals(archivedBlockData.getTimestamp(), repositoryBlockData.getTimestamp());
|
||||
assertEquals(archivedBlockData.getATFees(), repositoryBlockData.getATFees());
|
||||
assertEquals(archivedBlockData.getTotalFees(), repositoryBlockData.getTotalFees());
|
||||
assertEquals(archivedBlockData.getTransactionCount(), repositoryBlockData.getTransactionCount());
|
||||
assertArrayEquals(archivedBlockData.getTransactionsSignature(), repositoryBlockData.getTransactionsSignature());
|
||||
|
||||
if (testHeight != 2) {
|
||||
assertArrayEquals(archivedAtStateData.getStateHash(), repositoryAtStateData.getStateHash());
|
||||
}
|
||||
}
|
||||
|
||||
// Check block 10 (unarchived)
|
||||
BlockArchiveReader reader = BlockArchiveReader.getInstance();
|
||||
Triple<BlockData, List<TransactionData>, List<ATStateData>> blockInfo = reader.fetchBlockAtHeight(10);
|
||||
assertNull(blockInfo);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testArchiveAndPrune() throws DataException, InterruptedException, TransformationException, IOException {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
|
||||
// Deploy an AT so that we have AT state data
|
||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "alice");
|
||||
byte[] creationBytes = AtUtils.buildSimpleAT();
|
||||
long fundingAmount = 1_00000000L;
|
||||
AtUtils.doDeployAT(repository, deployer, creationBytes, fundingAmount);
|
||||
|
||||
// Mint some blocks so that we are able to archive them later
|
||||
for (int i = 0; i < 1000; i++) {
|
||||
BlockMinter.mintTestingBlock(repository, Common.getTestAccount(repository, "alice-reward-share"));
|
||||
}
|
||||
|
||||
// Assume 900 blocks are trimmed (this specifies the first untrimmed height)
|
||||
repository.getBlockRepository().setOnlineAccountsSignaturesTrimHeight(901);
|
||||
repository.getATRepository().setAtTrimHeight(901);
|
||||
|
||||
// Check the max archive height - this should be one less than the first untrimmed height
|
||||
final int maximumArchiveHeight = BlockArchiveWriter.getMaxArchiveHeight(repository);
|
||||
assertEquals(900, maximumArchiveHeight);
|
||||
|
||||
// Write blocks 2-900 to the archive
|
||||
BlockArchiveWriter writer = new BlockArchiveWriter(0, maximumArchiveHeight, repository);
|
||||
writer.setShouldEnforceFileSizeTarget(false); // To avoid the need to pre-calculate file sizes
|
||||
BlockArchiveWriter.BlockArchiveWriteResult result = writer.write();
|
||||
assertEquals(BlockArchiveWriter.BlockArchiveWriteResult.OK, result);
|
||||
|
||||
// Make sure that the archive contains the correct number of blocks
|
||||
assertEquals(900 - 1, writer.getWrittenCount());
|
||||
|
||||
// Increment block archive height
|
||||
repository.getBlockArchiveRepository().setBlockArchiveHeight(901);
|
||||
repository.saveChanges();
|
||||
assertEquals(901, repository.getBlockArchiveRepository().getBlockArchiveHeight());
|
||||
|
||||
// Ensure the file exists
|
||||
File outputFile = writer.getOutputPath().toFile();
|
||||
assertTrue(outputFile.exists());
|
||||
|
||||
// Ensure the SQL repository contains blocks 2 and 900...
|
||||
assertNotNull(repository.getBlockRepository().fromHeight(2));
|
||||
assertNotNull(repository.getBlockRepository().fromHeight(900));
|
||||
|
||||
// Prune all the archived blocks
|
||||
int numBlocksPruned = repository.getBlockRepository().pruneBlocks(0, 900);
|
||||
assertEquals(900-1, numBlocksPruned);
|
||||
repository.getBlockRepository().setBlockPruneHeight(901);
|
||||
|
||||
// Prune the AT states for the archived blocks
|
||||
repository.getATRepository().rebuildLatestAtStates();
|
||||
int numATStatesPruned = repository.getATRepository().pruneAtStates(0, 900);
|
||||
assertEquals(900-1, numATStatesPruned);
|
||||
repository.getATRepository().setAtPruneHeight(901);
|
||||
|
||||
// Now ensure the SQL repository is missing blocks 2 and 900...
|
||||
assertNull(repository.getBlockRepository().fromHeight(2));
|
||||
assertNull(repository.getBlockRepository().fromHeight(900));
|
||||
|
||||
// ... but it's not missing blocks 1 and 901 (we don't prune the genesis block)
|
||||
assertNotNull(repository.getBlockRepository().fromHeight(1));
|
||||
assertNotNull(repository.getBlockRepository().fromHeight(901));
|
||||
|
||||
// Validate the latest block height in the repository
|
||||
assertEquals(1002, (int) repository.getBlockRepository().getLastBlock().getHeight());
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBulkArchiveAndPrune() throws DataException, SQLException {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
HSQLDBRepository hsqldb = (HSQLDBRepository) repository;
|
||||
|
||||
// Deploy an AT so that we have AT state data
|
||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "alice");
|
||||
byte[] creationBytes = AtUtils.buildSimpleAT();
|
||||
long fundingAmount = 1_00000000L;
|
||||
AtUtils.doDeployAT(repository, deployer, creationBytes, fundingAmount);
|
||||
|
||||
// Mint some blocks so that we are able to archive them later
|
||||
for (int i = 0; i < 1000; i++) {
|
||||
BlockMinter.mintTestingBlock(repository, Common.getTestAccount(repository, "alice-reward-share"));
|
||||
}
|
||||
|
||||
// Assume 900 blocks are trimmed (this specifies the first untrimmed height)
|
||||
repository.getBlockRepository().setOnlineAccountsSignaturesTrimHeight(901);
|
||||
repository.getATRepository().setAtTrimHeight(901);
|
||||
|
||||
// Check the max archive height - this should be one less than the first untrimmed height
|
||||
final int maximumArchiveHeight = BlockArchiveWriter.getMaxArchiveHeight(repository);
|
||||
assertEquals(900, maximumArchiveHeight);
|
||||
|
||||
// Check the current archive height
|
||||
assertEquals(0, repository.getBlockArchiveRepository().getBlockArchiveHeight());
|
||||
|
||||
// Write blocks 2-900 to the archive (using bulk method)
|
||||
int fileSizeTarget = 425000; // Pre-calculated size of 900 blocks
|
||||
assertTrue(HSQLDBDatabaseArchiving.buildBlockArchive(repository, fileSizeTarget));
|
||||
|
||||
// Ensure the block archive height has increased
|
||||
assertEquals(901, repository.getBlockArchiveRepository().getBlockArchiveHeight());
|
||||
|
||||
// Ensure the SQL repository contains blocks 2 and 900...
|
||||
assertNotNull(repository.getBlockRepository().fromHeight(2));
|
||||
assertNotNull(repository.getBlockRepository().fromHeight(900));
|
||||
|
||||
// Check the current prune heights
|
||||
assertEquals(0, repository.getBlockRepository().getBlockPruneHeight());
|
||||
assertEquals(0, repository.getATRepository().getAtPruneHeight());
|
||||
|
||||
// Prior to archiving or pruning, ensure blocks 2 to 1002 and their AT states are available in the db
|
||||
for (int i=2; i<=1002; i++) {
|
||||
assertNotNull(repository.getBlockRepository().fromHeight(i));
|
||||
List<ATStateData> atStates = repository.getATRepository().getBlockATStatesAtHeight(i);
|
||||
assertNotNull(atStates);
|
||||
assertEquals(1, atStates.size());
|
||||
}
|
||||
|
||||
// Prune all the archived blocks and AT states (using bulk method)
|
||||
assertTrue(HSQLDBDatabasePruning.pruneBlocks(hsqldb));
|
||||
assertTrue(HSQLDBDatabasePruning.pruneATStates(hsqldb));
|
||||
|
||||
// Ensure the current prune heights have increased
|
||||
assertEquals(901, repository.getBlockRepository().getBlockPruneHeight());
|
||||
assertEquals(901, repository.getATRepository().getAtPruneHeight());
|
||||
|
||||
// Now ensure the SQL repository is missing blocks 2 and 900...
|
||||
assertNull(repository.getBlockRepository().fromHeight(2));
|
||||
assertNull(repository.getBlockRepository().fromHeight(900));
|
||||
|
||||
// ... but it's not missing blocks 1 and 901 (we don't prune the genesis block)
|
||||
assertNotNull(repository.getBlockRepository().fromHeight(1));
|
||||
assertNotNull(repository.getBlockRepository().fromHeight(901));
|
||||
|
||||
// Validate the latest block height in the repository
|
||||
assertEquals(1002, (int) repository.getBlockRepository().getLastBlock().getHeight());
|
||||
|
||||
// Ensure blocks 2-900 are all available in the archive
|
||||
for (int i=2; i<=900; i++) {
|
||||
assertNotNull(repository.getBlockArchiveRepository().fromHeight(i));
|
||||
}
|
||||
|
||||
// Ensure blocks 2-900 are NOT available in the db
|
||||
for (int i=2; i<=900; i++) {
|
||||
assertNull(repository.getBlockRepository().fromHeight(i));
|
||||
}
|
||||
|
||||
// Ensure blocks 901 to 1002 and their AT states are available in the db
|
||||
for (int i=901; i<=1002; i++) {
|
||||
assertNotNull(repository.getBlockRepository().fromHeight(i));
|
||||
List<ATStateData> atStates = repository.getATRepository().getBlockATStatesAtHeight(i);
|
||||
assertNotNull(atStates);
|
||||
assertEquals(1, atStates.size());
|
||||
}
|
||||
|
||||
// Ensure blocks 901 to 1002 are not available in the archive
|
||||
for (int i=901; i<=1002; i++) {
|
||||
assertNull(repository.getBlockArchiveRepository().fromHeight(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBulkArchiveAndPruneMultipleFiles() throws DataException, SQLException {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
HSQLDBRepository hsqldb = (HSQLDBRepository) repository;
|
||||
|
||||
// Deploy an AT so that we have AT state data
|
||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "alice");
|
||||
byte[] creationBytes = AtUtils.buildSimpleAT();
|
||||
long fundingAmount = 1_00000000L;
|
||||
AtUtils.doDeployAT(repository, deployer, creationBytes, fundingAmount);
|
||||
|
||||
// Mint some blocks so that we are able to archive them later
|
||||
for (int i = 0; i < 1000; i++) {
|
||||
BlockMinter.mintTestingBlock(repository, Common.getTestAccount(repository, "alice-reward-share"));
|
||||
}
|
||||
|
||||
// Assume 900 blocks are trimmed (this specifies the first untrimmed height)
|
||||
repository.getBlockRepository().setOnlineAccountsSignaturesTrimHeight(901);
|
||||
repository.getATRepository().setAtTrimHeight(901);
|
||||
|
||||
// Check the max archive height - this should be one less than the first untrimmed height
|
||||
final int maximumArchiveHeight = BlockArchiveWriter.getMaxArchiveHeight(repository);
|
||||
assertEquals(900, maximumArchiveHeight);
|
||||
|
||||
// Check the current archive height
|
||||
assertEquals(0, repository.getBlockArchiveRepository().getBlockArchiveHeight());
|
||||
|
||||
// Write blocks 2-900 to the archive (using bulk method)
|
||||
int fileSizeTarget = 42000; // Pre-calculated size of approx 90 blocks
|
||||
assertTrue(HSQLDBDatabaseArchiving.buildBlockArchive(repository, fileSizeTarget));
|
||||
|
||||
// Ensure 10 archive files have been created
|
||||
Path archivePath = Paths.get(Settings.getInstance().getRepositoryPath(), "archive");
|
||||
assertEquals(10, new File(archivePath.toString()).list().length);
|
||||
|
||||
// Check the files exist
|
||||
assertTrue(Files.exists(Paths.get(archivePath.toString(), "2-90.dat")));
|
||||
assertTrue(Files.exists(Paths.get(archivePath.toString(), "91-179.dat")));
|
||||
assertTrue(Files.exists(Paths.get(archivePath.toString(), "180-268.dat")));
|
||||
assertTrue(Files.exists(Paths.get(archivePath.toString(), "269-357.dat")));
|
||||
assertTrue(Files.exists(Paths.get(archivePath.toString(), "358-446.dat")));
|
||||
assertTrue(Files.exists(Paths.get(archivePath.toString(), "447-535.dat")));
|
||||
assertTrue(Files.exists(Paths.get(archivePath.toString(), "536-624.dat")));
|
||||
assertTrue(Files.exists(Paths.get(archivePath.toString(), "625-713.dat")));
|
||||
assertTrue(Files.exists(Paths.get(archivePath.toString(), "714-802.dat")));
|
||||
assertTrue(Files.exists(Paths.get(archivePath.toString(), "803-891.dat")));
|
||||
|
||||
// Ensure the block archive height has increased
|
||||
// It won't be as high as 901, because blocks 892-901 were too small to reach the file size
|
||||
// target of the 11th file
|
||||
assertEquals(892, repository.getBlockArchiveRepository().getBlockArchiveHeight());
|
||||
|
||||
// Ensure the SQL repository contains blocks 2 and 891...
|
||||
assertNotNull(repository.getBlockRepository().fromHeight(2));
|
||||
assertNotNull(repository.getBlockRepository().fromHeight(891));
|
||||
|
||||
// Check the current prune heights
|
||||
assertEquals(0, repository.getBlockRepository().getBlockPruneHeight());
|
||||
assertEquals(0, repository.getATRepository().getAtPruneHeight());
|
||||
|
||||
// Prior to archiving or pruning, ensure blocks 2 to 1002 and their AT states are available in the db
|
||||
for (int i=2; i<=1002; i++) {
|
||||
assertNotNull(repository.getBlockRepository().fromHeight(i));
|
||||
List<ATStateData> atStates = repository.getATRepository().getBlockATStatesAtHeight(i);
|
||||
assertNotNull(atStates);
|
||||
assertEquals(1, atStates.size());
|
||||
}
|
||||
|
||||
// Prune all the archived blocks and AT states (using bulk method)
|
||||
assertTrue(HSQLDBDatabasePruning.pruneBlocks(hsqldb));
|
||||
assertTrue(HSQLDBDatabasePruning.pruneATStates(hsqldb));
|
||||
|
||||
// Ensure the current prune heights have increased
|
||||
assertEquals(892, repository.getBlockRepository().getBlockPruneHeight());
|
||||
assertEquals(892, repository.getATRepository().getAtPruneHeight());
|
||||
|
||||
// Now ensure the SQL repository is missing blocks 2 and 891...
|
||||
assertNull(repository.getBlockRepository().fromHeight(2));
|
||||
assertNull(repository.getBlockRepository().fromHeight(891));
|
||||
|
||||
// ... but it's not missing blocks 1 and 901 (we don't prune the genesis block)
|
||||
assertNotNull(repository.getBlockRepository().fromHeight(1));
|
||||
assertNotNull(repository.getBlockRepository().fromHeight(892));
|
||||
|
||||
// Validate the latest block height in the repository
|
||||
assertEquals(1002, (int) repository.getBlockRepository().getLastBlock().getHeight());
|
||||
|
||||
// Ensure blocks 2-891 are all available in the archive
|
||||
for (int i=2; i<=891; i++) {
|
||||
assertNotNull(repository.getBlockArchiveRepository().fromHeight(i));
|
||||
}
|
||||
|
||||
// Ensure blocks 2-891 are NOT available in the db
|
||||
for (int i=2; i<=891; i++) {
|
||||
assertNull(repository.getBlockRepository().fromHeight(i));
|
||||
}
|
||||
|
||||
// Ensure blocks 892 to 1002 and their AT states are available in the db
|
||||
for (int i=892; i<=1002; i++) {
|
||||
assertNotNull(repository.getBlockRepository().fromHeight(i));
|
||||
List<ATStateData> atStates = repository.getATRepository().getBlockATStatesAtHeight(i);
|
||||
assertNotNull(atStates);
|
||||
assertEquals(1, atStates.size());
|
||||
}
|
||||
|
||||
// Ensure blocks 892 to 1002 are not available in the archive
|
||||
for (int i=892; i<=1002; i++) {
|
||||
assertNull(repository.getBlockArchiveRepository().fromHeight(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTrimArchivePruneAndOrphan() throws DataException, InterruptedException, TransformationException, IOException {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
|
||||
// Deploy an AT so that we have AT state data
|
||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "alice");
|
||||
byte[] creationBytes = AtUtils.buildSimpleAT();
|
||||
long fundingAmount = 1_00000000L;
|
||||
AtUtils.doDeployAT(repository, deployer, creationBytes, fundingAmount);
|
||||
|
||||
// Mint some blocks so that we are able to archive them later
|
||||
for (int i = 0; i < 1000; i++) {
|
||||
BlockMinter.mintTestingBlock(repository, Common.getTestAccount(repository, "alice-reward-share"));
|
||||
}
|
||||
|
||||
// Make sure that block 500 has full AT state data and data hash
|
||||
List<ATStateData> block500AtStatesData = repository.getATRepository().getBlockATStatesAtHeight(500);
|
||||
ATStateData atStatesData = repository.getATRepository().getATStateAtHeight(block500AtStatesData.get(0).getATAddress(), 500);
|
||||
assertNotNull(atStatesData.getStateHash());
|
||||
assertNotNull(atStatesData.getStateData());
|
||||
|
||||
// Trim the first 500 blocks
|
||||
repository.getBlockRepository().trimOldOnlineAccountsSignatures(0, 500);
|
||||
repository.getBlockRepository().setOnlineAccountsSignaturesTrimHeight(501);
|
||||
repository.getATRepository().trimAtStates(0, 500, 1000);
|
||||
repository.getATRepository().setAtTrimHeight(501);
|
||||
|
||||
// Now block 500 should only have the AT state data hash
|
||||
block500AtStatesData = repository.getATRepository().getBlockATStatesAtHeight(500);
|
||||
atStatesData = repository.getATRepository().getATStateAtHeight(block500AtStatesData.get(0).getATAddress(), 500);
|
||||
assertNotNull(atStatesData.getStateHash());
|
||||
assertNull(atStatesData.getStateData());
|
||||
|
||||
// ... but block 501 should have the full data
|
||||
List<ATStateData> block501AtStatesData = repository.getATRepository().getBlockATStatesAtHeight(501);
|
||||
atStatesData = repository.getATRepository().getATStateAtHeight(block501AtStatesData.get(0).getATAddress(), 501);
|
||||
assertNotNull(atStatesData.getStateHash());
|
||||
assertNotNull(atStatesData.getStateData());
|
||||
|
||||
// Check the max archive height - this should be one less than the first untrimmed height
|
||||
final int maximumArchiveHeight = BlockArchiveWriter.getMaxArchiveHeight(repository);
|
||||
assertEquals(500, maximumArchiveHeight);
|
||||
|
||||
BlockData block3DataPreArchive = repository.getBlockRepository().fromHeight(3);
|
||||
|
||||
// Write blocks 2-500 to the archive
|
||||
BlockArchiveWriter writer = new BlockArchiveWriter(0, maximumArchiveHeight, repository);
|
||||
writer.setShouldEnforceFileSizeTarget(false); // To avoid the need to pre-calculate file sizes
|
||||
BlockArchiveWriter.BlockArchiveWriteResult result = writer.write();
|
||||
assertEquals(BlockArchiveWriter.BlockArchiveWriteResult.OK, result);
|
||||
|
||||
// Make sure that the archive contains the correct number of blocks
|
||||
assertEquals(500 - 1, writer.getWrittenCount()); // -1 for the genesis block
|
||||
|
||||
// Increment block archive height
|
||||
repository.getBlockArchiveRepository().setBlockArchiveHeight(writer.getWrittenCount());
|
||||
repository.saveChanges();
|
||||
assertEquals(500 - 1, repository.getBlockArchiveRepository().getBlockArchiveHeight());
|
||||
|
||||
// Ensure the file exists
|
||||
File outputFile = writer.getOutputPath().toFile();
|
||||
assertTrue(outputFile.exists());
|
||||
|
||||
// Ensure the SQL repository contains blocks 2 and 500...
|
||||
assertNotNull(repository.getBlockRepository().fromHeight(2));
|
||||
assertNotNull(repository.getBlockRepository().fromHeight(500));
|
||||
|
||||
// Prune all the archived blocks
|
||||
int numBlocksPruned = repository.getBlockRepository().pruneBlocks(0, 500);
|
||||
assertEquals(500-1, numBlocksPruned);
|
||||
repository.getBlockRepository().setBlockPruneHeight(501);
|
||||
|
||||
// Prune the AT states for the archived blocks
|
||||
repository.getATRepository().rebuildLatestAtStates();
|
||||
int numATStatesPruned = repository.getATRepository().pruneAtStates(2, 500);
|
||||
assertEquals(499, numATStatesPruned);
|
||||
repository.getATRepository().setAtPruneHeight(501);
|
||||
|
||||
// Now ensure the SQL repository is missing blocks 2 and 500...
|
||||
assertNull(repository.getBlockRepository().fromHeight(2));
|
||||
assertNull(repository.getBlockRepository().fromHeight(500));
|
||||
|
||||
// ... but it's not missing blocks 1 and 501 (we don't prune the genesis block)
|
||||
assertNotNull(repository.getBlockRepository().fromHeight(1));
|
||||
assertNotNull(repository.getBlockRepository().fromHeight(501));
|
||||
|
||||
// Validate the latest block height in the repository
|
||||
assertEquals(1002, (int) repository.getBlockRepository().getLastBlock().getHeight());
|
||||
|
||||
// Now orphan some unarchived blocks.
|
||||
BlockUtils.orphanBlocks(repository, 500);
|
||||
assertEquals(502, (int) repository.getBlockRepository().getLastBlock().getHeight());
|
||||
|
||||
// We're close to the lower limit of the SQL database now, so
|
||||
// we need to import some blocks from the archive
|
||||
BlockArchiveUtils.importFromArchive(401, 500, repository);
|
||||
|
||||
// Ensure the SQL repository now contains block 401 but not 400...
|
||||
assertNotNull(repository.getBlockRepository().fromHeight(401));
|
||||
assertNull(repository.getBlockRepository().fromHeight(400));
|
||||
|
||||
// Import the remaining 399 blocks
|
||||
BlockArchiveUtils.importFromArchive(2, 400, repository);
|
||||
|
||||
// Verify that block 3 matches the original
|
||||
BlockData block3DataPostArchive = repository.getBlockRepository().fromHeight(3);
|
||||
assertArrayEquals(block3DataPreArchive.getSignature(), block3DataPostArchive.getSignature());
|
||||
assertEquals(block3DataPreArchive.getHeight(), block3DataPostArchive.getHeight());
|
||||
|
||||
// Orphan 1 more block, which should be the last one that is possible to be orphaned
|
||||
BlockUtils.orphanBlocks(repository, 1);
|
||||
|
||||
// Orphan another block, which should fail
|
||||
Exception exception = null;
|
||||
try {
|
||||
BlockUtils.orphanBlocks(repository, 1);
|
||||
} catch (DataException e) {
|
||||
exception = e;
|
||||
}
|
||||
|
||||
// Ensure that a DataException is thrown because there is no more AT states data available
|
||||
assertNotNull(exception);
|
||||
assertEquals(DataException.class, exception.getClass());
|
||||
|
||||
// FUTURE: we may be able to retain unique AT states when trimming, to avoid this exception
|
||||
// and allow orphaning back through blocks with trimmed AT states.
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Many nodes are missing an ATStatesHeightIndex due to an earlier bug
|
||||
* In these cases we disable archiving and pruning as this index is a
|
||||
* very essential component in these processes.
|
||||
*/
|
||||
@Test
|
||||
public void testMissingAtStatesHeightIndex() throws DataException, SQLException {
|
||||
try (final HSQLDBRepository repository = (HSQLDBRepository) RepositoryManager.getRepository()) {
|
||||
|
||||
// Firstly check that we're able to prune or archive when the index exists
|
||||
assertTrue(repository.getATRepository().hasAtStatesHeightIndex());
|
||||
assertTrue(RepositoryManager.canArchiveOrPrune());
|
||||
|
||||
// Delete the index
|
||||
repository.prepareStatement("DROP INDEX ATSTATESHEIGHTINDEX").execute();
|
||||
|
||||
// Ensure check that we're unable to prune or archive when the index doesn't exist
|
||||
assertFalse(repository.getATRepository().hasAtStatesHeightIndex());
|
||||
assertFalse(RepositoryManager.canArchiveOrPrune());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void deleteArchiveDirectory() {
|
||||
// Delete archive directory if exists
|
||||
Path archivePath = Paths.get(Settings.getInstance().getRepositoryPath(), "archive").toAbsolutePath();
|
||||
try {
|
||||
FileUtils.deleteDirectory(archivePath.toFile());
|
||||
} catch (IOException e) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
245
src/test/java/org/qortal/test/BootstrapTests.java
Normal file
245
src/test/java/org/qortal/test/BootstrapTests.java
Normal file
@@ -0,0 +1,245 @@
|
||||
package org.qortal.test;
|
||||
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.qortal.account.PrivateKeyAccount;
|
||||
import org.qortal.controller.BlockMinter;
|
||||
import org.qortal.controller.Controller;
|
||||
import org.qortal.data.block.BlockData;
|
||||
import org.qortal.repository.*;
|
||||
import org.qortal.settings.Settings;
|
||||
import org.qortal.test.common.AtUtils;
|
||||
import org.qortal.test.common.Common;
|
||||
import org.qortal.transform.TransformationException;
|
||||
import org.qortal.utils.NTP;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.NoSuchFileException;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
public class BootstrapTests extends Common {
|
||||
|
||||
@Before
|
||||
public void beforeTest() throws DataException, IOException {
|
||||
Common.useSettingsAndDb(Common.testSettingsFilename, false);
|
||||
NTP.setFixedOffset(Settings.getInstance().getTestNtpOffset());
|
||||
this.deleteBootstraps();
|
||||
}
|
||||
|
||||
@After
|
||||
public void afterTest() throws DataException, IOException {
|
||||
this.deleteBootstraps();
|
||||
this.deleteExportDirectory();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCheckRepositoryState() throws DataException, InterruptedException, TransformationException, IOException {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
this.buildDummyBlockchain(repository);
|
||||
|
||||
Bootstrap bootstrap = new Bootstrap(repository);
|
||||
assertTrue(bootstrap.checkRepositoryState());
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidateBlockchain() throws DataException, InterruptedException, TransformationException, IOException {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
this.buildDummyBlockchain(repository);
|
||||
|
||||
Bootstrap bootstrap = new Bootstrap(repository);
|
||||
assertTrue(bootstrap.validateBlockchain());
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testCreateAndImportBootstrap() throws DataException, InterruptedException, TransformationException, IOException {
|
||||
|
||||
Path archivePath = Paths.get(Settings.getInstance().getRepositoryPath(), "archive", "2-900.dat");
|
||||
BlockData block1000;
|
||||
byte[] originalArchiveContents;
|
||||
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
this.buildDummyBlockchain(repository);
|
||||
|
||||
Bootstrap bootstrap = new Bootstrap(repository);
|
||||
Path bootstrapPath = bootstrap.getBootstrapOutputPath();
|
||||
|
||||
// Ensure the compressed bootstrap doesn't exist
|
||||
assertFalse(Files.exists(bootstrapPath));
|
||||
|
||||
// Create bootstrap
|
||||
bootstrap.create();
|
||||
|
||||
// Ensure the compressed bootstrap exists
|
||||
assertTrue(Files.exists(bootstrapPath));
|
||||
|
||||
// Ensure the original block archive file exists
|
||||
assertTrue(Files.exists(archivePath));
|
||||
originalArchiveContents = Files.readAllBytes(archivePath);
|
||||
|
||||
// Ensure block 1000 exists in the repository
|
||||
block1000 = repository.getBlockRepository().fromHeight(1000);
|
||||
assertNotNull(block1000);
|
||||
|
||||
// Ensure we can retrieve block 10 from the archive
|
||||
assertNotNull(repository.getBlockArchiveRepository().fromHeight(10));
|
||||
|
||||
// Now delete block 1000
|
||||
repository.getBlockRepository().delete(block1000);
|
||||
assertNull(repository.getBlockRepository().fromHeight(1000));
|
||||
|
||||
// Overwrite the archive with dummy data, and verify it
|
||||
try (PrintWriter out = new PrintWriter(archivePath.toFile())) {
|
||||
out.println("testdata");
|
||||
}
|
||||
String newline = System.getProperty("line.separator");
|
||||
assertEquals("testdata", Files.readString(archivePath).replace(newline, ""));
|
||||
|
||||
// Ensure we can no longer retrieve block 10 from the archive
|
||||
assertNull(repository.getBlockArchiveRepository().fromHeight(10));
|
||||
|
||||
// Import the bootstrap back in
|
||||
bootstrap.importFromPath(bootstrapPath);
|
||||
}
|
||||
|
||||
// We need a new connection because we have switched to a new repository
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
|
||||
// Ensure the block archive file exists
|
||||
assertTrue(Files.exists(archivePath));
|
||||
|
||||
// and that its contents match the original
|
||||
assertArrayEquals(originalArchiveContents, Files.readAllBytes(archivePath));
|
||||
|
||||
// Make sure that block 1000 exists again
|
||||
BlockData newBlock1000 = repository.getBlockRepository().fromHeight(1000);
|
||||
assertNotNull(newBlock1000);
|
||||
|
||||
// and ensure that the signatures match
|
||||
assertArrayEquals(block1000.getSignature(), newBlock1000.getSignature());
|
||||
|
||||
// Ensure we can retrieve block 10 from the archive
|
||||
assertNotNull(repository.getBlockArchiveRepository().fromHeight(10));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void buildDummyBlockchain(Repository repository) throws DataException, InterruptedException, TransformationException, IOException {
|
||||
// Alice self share online
|
||||
List<PrivateKeyAccount> mintingAndOnlineAccounts = new ArrayList<>();
|
||||
PrivateKeyAccount aliceSelfShare = Common.getTestAccount(repository, "alice-reward-share");
|
||||
mintingAndOnlineAccounts.add(aliceSelfShare);
|
||||
|
||||
// Deploy an AT so that we have AT state data
|
||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "alice");
|
||||
byte[] creationBytes = AtUtils.buildSimpleAT();
|
||||
long fundingAmount = 1_00000000L;
|
||||
AtUtils.doDeployAT(repository, deployer, creationBytes, fundingAmount);
|
||||
|
||||
// Mint some blocks so that we are able to archive them later
|
||||
for (int i = 0; i < 1000; i++)
|
||||
BlockMinter.mintTestingBlock(repository, mintingAndOnlineAccounts.toArray(new PrivateKeyAccount[0]));
|
||||
|
||||
// Assume 900 blocks are trimmed (this specifies the first untrimmed height)
|
||||
repository.getBlockRepository().setOnlineAccountsSignaturesTrimHeight(901);
|
||||
repository.getATRepository().setAtTrimHeight(901);
|
||||
|
||||
// Check the max archive height - this should be one less than the first untrimmed height
|
||||
final int maximumArchiveHeight = BlockArchiveWriter.getMaxArchiveHeight(repository);
|
||||
|
||||
// Write blocks 2-900 to the archive
|
||||
BlockArchiveWriter writer = new BlockArchiveWriter(0, maximumArchiveHeight, repository);
|
||||
writer.setShouldEnforceFileSizeTarget(false); // To avoid the need to pre-calculate file sizes
|
||||
BlockArchiveWriter.BlockArchiveWriteResult result = writer.write();
|
||||
|
||||
// Increment block archive height
|
||||
repository.getBlockArchiveRepository().setBlockArchiveHeight(901);
|
||||
|
||||
// Prune all the archived blocks
|
||||
repository.getBlockRepository().pruneBlocks(0, 900);
|
||||
repository.getBlockRepository().setBlockPruneHeight(901);
|
||||
|
||||
// Prune the AT states for the archived blocks
|
||||
repository.getATRepository().rebuildLatestAtStates();
|
||||
repository.getATRepository().pruneAtStates(0, 900);
|
||||
repository.getATRepository().setAtPruneHeight(901);
|
||||
|
||||
// Refill cache, used by Controller.getMinimumLatestBlockTimestamp() and other methods
|
||||
Controller.getInstance().refillLatestBlocksCache();
|
||||
|
||||
repository.saveChanges();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetRandomHost() {
|
||||
String[] bootstrapHosts = Settings.getInstance().getBootstrapHosts();
|
||||
List<String> uniqueHosts = new ArrayList<>();
|
||||
|
||||
for (int i=0; i<1000; i++) {
|
||||
Bootstrap bootstrap = new Bootstrap();
|
||||
String randomHost = bootstrap.getRandomHost();
|
||||
assertNotNull(randomHost);
|
||||
|
||||
if (!uniqueHosts.contains(randomHost)){
|
||||
uniqueHosts.add(randomHost);
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure we have more than one bootstrap host in the settings
|
||||
assertTrue(Arrays.asList(bootstrapHosts).size() > 1);
|
||||
|
||||
// Ensure that all have been given the opportunity to be used
|
||||
assertEquals(uniqueHosts.size(), Arrays.asList(bootstrapHosts).size());
|
||||
}
|
||||
|
||||
private void deleteBootstraps() throws IOException {
|
||||
try {
|
||||
Path path = Paths.get(String.format("%s%s", Settings.getInstance().getBootstrapFilenamePrefix(), "bootstrap-archive.7z"));
|
||||
Files.delete(path);
|
||||
|
||||
} catch (NoSuchFileException e) {
|
||||
// Nothing to delete
|
||||
}
|
||||
|
||||
try {
|
||||
Path path = Paths.get(String.format("%s%s", Settings.getInstance().getBootstrapFilenamePrefix(), "bootstrap-toponly.7z"));
|
||||
Files.delete(path);
|
||||
|
||||
} catch (NoSuchFileException e) {
|
||||
// Nothing to delete
|
||||
}
|
||||
|
||||
try {
|
||||
Path path = Paths.get(String.format("%s%s", Settings.getInstance().getBootstrapFilenamePrefix(), "bootstrap-full.7z"));
|
||||
Files.delete(path);
|
||||
|
||||
} catch (NoSuchFileException e) {
|
||||
// Nothing to delete
|
||||
}
|
||||
}
|
||||
|
||||
private void deleteExportDirectory() {
|
||||
// Delete archive directory if exists
|
||||
Path archivePath = Paths.get(Settings.getInstance().getExportPath());
|
||||
try {
|
||||
FileUtils.deleteDirectory(archivePath.toFile());
|
||||
} catch (IOException e) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -15,7 +15,9 @@ import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
@@ -56,6 +58,37 @@ public class CryptoTests extends Common {
|
||||
assertArrayEquals(expected, digest);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFileDigest() throws IOException {
|
||||
byte[] input = HashCode.fromString("00").asBytes();
|
||||
|
||||
Path tempPath = Files.createTempFile("", ".tmp");
|
||||
Files.write(tempPath, input, StandardOpenOption.CREATE);
|
||||
|
||||
byte[] digest = Crypto.digest(tempPath.toFile());
|
||||
byte[] expected = HashCode.fromString("6e340b9cffb37a989ca544e6bb780a2c78901d3fb33738768511a30617afa01d").asBytes();
|
||||
|
||||
assertArrayEquals(expected, digest);
|
||||
|
||||
Files.delete(tempPath);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFileDigestWithRandomData() throws IOException {
|
||||
byte[] input = new byte[128];
|
||||
new Random().nextBytes(input);
|
||||
|
||||
Path tempPath = Files.createTempFile("", ".tmp");
|
||||
Files.write(tempPath, input, StandardOpenOption.CREATE);
|
||||
|
||||
byte[] fileDigest = Crypto.digest(tempPath.toFile());
|
||||
byte[] memoryDigest = Crypto.digest(input);
|
||||
|
||||
assertArrayEquals(fileDigest, memoryDigest);
|
||||
|
||||
Files.delete(tempPath);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPublicKeyToAddress() {
|
||||
byte[] publicKey = HashCode.fromString("775ada64a48a30b3bfc4f1db16bca512d4088704975a62bde78781ce0cba90d6").asBytes();
|
||||
|
390
src/test/java/org/qortal/test/ImportExportTests.java
Normal file
390
src/test/java/org/qortal/test/ImportExportTests.java
Normal file
@@ -0,0 +1,390 @@
|
||||
package org.qortal.test;
|
||||
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.bitcoinj.core.Address;
|
||||
import org.bitcoinj.core.AddressFormatException;
|
||||
import org.bitcoinj.core.ECKey;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.qortal.account.PublicKeyAccount;
|
||||
import org.qortal.controller.tradebot.LitecoinACCTv1TradeBot;
|
||||
import org.qortal.controller.tradebot.TradeBot;
|
||||
import org.qortal.crosschain.Litecoin;
|
||||
import org.qortal.crosschain.LitecoinACCTv1;
|
||||
import org.qortal.crosschain.SupportedBlockchain;
|
||||
import org.qortal.crypto.Crypto;
|
||||
import org.qortal.data.account.MintingAccountData;
|
||||
import org.qortal.data.crosschain.TradeBotData;
|
||||
import org.qortal.repository.DataException;
|
||||
import org.qortal.repository.Repository;
|
||||
import org.qortal.repository.RepositoryManager;
|
||||
import org.qortal.repository.hsqldb.HSQLDBImportExport;
|
||||
import org.qortal.settings.Settings;
|
||||
import org.qortal.test.common.Common;
|
||||
import org.qortal.utils.NTP;
|
||||
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
public class ImportExportTests extends Common {
|
||||
|
||||
@Before
|
||||
public void beforeTest() throws DataException {
|
||||
Common.useDefaultSettings();
|
||||
this.deleteExportDirectory();
|
||||
}
|
||||
|
||||
@After
|
||||
public void afterTest() throws DataException {
|
||||
this.deleteExportDirectory();
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testExportAndImportTradeBotStates() throws DataException, IOException {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
|
||||
// Ensure no trade bots exist
|
||||
assertTrue(repository.getCrossChainRepository().getAllTradeBotData().isEmpty());
|
||||
|
||||
// Create some trade bots
|
||||
List<TradeBotData> tradeBots = new ArrayList<>();
|
||||
for (int i=0; i<10; i++) {
|
||||
TradeBotData tradeBotData = this.createTradeBotData(repository);
|
||||
repository.getCrossChainRepository().save(tradeBotData);
|
||||
tradeBots.add(tradeBotData);
|
||||
}
|
||||
|
||||
// Ensure they have been added
|
||||
assertEquals(10, repository.getCrossChainRepository().getAllTradeBotData().size());
|
||||
|
||||
// Export them
|
||||
HSQLDBImportExport.backupTradeBotStates(repository);
|
||||
|
||||
// Delete them from the repository
|
||||
for (TradeBotData tradeBotData : tradeBots) {
|
||||
repository.getCrossChainRepository().delete(tradeBotData.getTradePrivateKey());
|
||||
}
|
||||
|
||||
// Ensure they have been deleted
|
||||
assertTrue(repository.getCrossChainRepository().getAllTradeBotData().isEmpty());
|
||||
|
||||
// Import them
|
||||
Path exportPath = HSQLDBImportExport.getExportDirectory(false);
|
||||
Path filePath = Paths.get(exportPath.toString(), "TradeBotStates.json");
|
||||
HSQLDBImportExport.importDataFromFile(filePath.toString(), repository);
|
||||
|
||||
// Ensure they have been imported
|
||||
assertEquals(10, repository.getCrossChainRepository().getAllTradeBotData().size());
|
||||
|
||||
// Ensure all the data matches
|
||||
for (TradeBotData tradeBotData : tradeBots) {
|
||||
byte[] tradePrivateKey = tradeBotData.getTradePrivateKey();
|
||||
TradeBotData repositoryTradeBotData = repository.getCrossChainRepository().getTradeBotData(tradePrivateKey);
|
||||
assertNotNull(repositoryTradeBotData);
|
||||
assertEquals(tradeBotData.toJson().toString(), repositoryTradeBotData.toJson().toString());
|
||||
}
|
||||
|
||||
repository.saveChanges();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExportAndImportCurrentTradeBotStates() throws DataException, IOException {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
|
||||
// Ensure no trade bots exist
|
||||
assertTrue(repository.getCrossChainRepository().getAllTradeBotData().isEmpty());
|
||||
|
||||
// Create some trade bots
|
||||
List<TradeBotData> tradeBots = new ArrayList<>();
|
||||
for (int i=0; i<10; i++) {
|
||||
TradeBotData tradeBotData = this.createTradeBotData(repository);
|
||||
repository.getCrossChainRepository().save(tradeBotData);
|
||||
tradeBots.add(tradeBotData);
|
||||
}
|
||||
|
||||
// Ensure they have been added
|
||||
assertEquals(10, repository.getCrossChainRepository().getAllTradeBotData().size());
|
||||
|
||||
// Export them
|
||||
HSQLDBImportExport.backupTradeBotStates(repository);
|
||||
|
||||
// Delete them from the repository
|
||||
for (TradeBotData tradeBotData : tradeBots) {
|
||||
repository.getCrossChainRepository().delete(tradeBotData.getTradePrivateKey());
|
||||
}
|
||||
|
||||
// Ensure they have been deleted
|
||||
assertTrue(repository.getCrossChainRepository().getAllTradeBotData().isEmpty());
|
||||
|
||||
// Add some more trade bots
|
||||
List<TradeBotData> additionalTradeBots = new ArrayList<>();
|
||||
for (int i=0; i<5; i++) {
|
||||
TradeBotData tradeBotData = this.createTradeBotData(repository);
|
||||
repository.getCrossChainRepository().save(tradeBotData);
|
||||
additionalTradeBots.add(tradeBotData);
|
||||
}
|
||||
|
||||
// Export again
|
||||
HSQLDBImportExport.backupTradeBotStates(repository);
|
||||
|
||||
// Import current states only
|
||||
Path exportPath = HSQLDBImportExport.getExportDirectory(false);
|
||||
Path filePath = Paths.get(exportPath.toString(), "TradeBotStates.json");
|
||||
HSQLDBImportExport.importDataFromFile(filePath.toString(), repository);
|
||||
|
||||
// Ensure they have been imported
|
||||
assertEquals(5, repository.getCrossChainRepository().getAllTradeBotData().size());
|
||||
|
||||
// Ensure that only the additional trade bots have been imported and that the data matches
|
||||
for (TradeBotData tradeBotData : additionalTradeBots) {
|
||||
byte[] tradePrivateKey = tradeBotData.getTradePrivateKey();
|
||||
TradeBotData repositoryTradeBotData = repository.getCrossChainRepository().getTradeBotData(tradePrivateKey);
|
||||
assertNotNull(repositoryTradeBotData);
|
||||
assertEquals(tradeBotData.toJson().toString(), repositoryTradeBotData.toJson().toString());
|
||||
}
|
||||
|
||||
// None of the original trade bots should exist in the repository
|
||||
for (TradeBotData tradeBotData : tradeBots) {
|
||||
byte[] tradePrivateKey = tradeBotData.getTradePrivateKey();
|
||||
TradeBotData repositoryTradeBotData = repository.getCrossChainRepository().getTradeBotData(tradePrivateKey);
|
||||
assertNull(repositoryTradeBotData);
|
||||
}
|
||||
|
||||
repository.saveChanges();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExportAndImportAllTradeBotStates() throws DataException, IOException {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
|
||||
// Ensure no trade bots exist
|
||||
assertTrue(repository.getCrossChainRepository().getAllTradeBotData().isEmpty());
|
||||
|
||||
// Create some trade bots
|
||||
List<TradeBotData> tradeBots = new ArrayList<>();
|
||||
for (int i=0; i<10; i++) {
|
||||
TradeBotData tradeBotData = this.createTradeBotData(repository);
|
||||
repository.getCrossChainRepository().save(tradeBotData);
|
||||
tradeBots.add(tradeBotData);
|
||||
}
|
||||
|
||||
// Ensure they have been added
|
||||
assertEquals(10, repository.getCrossChainRepository().getAllTradeBotData().size());
|
||||
|
||||
// Export them
|
||||
HSQLDBImportExport.backupTradeBotStates(repository);
|
||||
|
||||
// Delete them from the repository
|
||||
for (TradeBotData tradeBotData : tradeBots) {
|
||||
repository.getCrossChainRepository().delete(tradeBotData.getTradePrivateKey());
|
||||
}
|
||||
|
||||
// Ensure they have been deleted
|
||||
assertTrue(repository.getCrossChainRepository().getAllTradeBotData().isEmpty());
|
||||
|
||||
// Add some more trade bots
|
||||
List<TradeBotData> additionalTradeBots = new ArrayList<>();
|
||||
for (int i=0; i<5; i++) {
|
||||
TradeBotData tradeBotData = this.createTradeBotData(repository);
|
||||
repository.getCrossChainRepository().save(tradeBotData);
|
||||
additionalTradeBots.add(tradeBotData);
|
||||
}
|
||||
|
||||
// Export again
|
||||
HSQLDBImportExport.backupTradeBotStates(repository);
|
||||
|
||||
// Import all states from the archive
|
||||
Path exportPath = HSQLDBImportExport.getExportDirectory(false);
|
||||
Path filePath = Paths.get(exportPath.toString(), "TradeBotStatesArchive.json");
|
||||
HSQLDBImportExport.importDataFromFile(filePath.toString(), repository);
|
||||
|
||||
// Ensure they have been imported
|
||||
assertEquals(15, repository.getCrossChainRepository().getAllTradeBotData().size());
|
||||
|
||||
// Ensure that all known trade bots have been imported and that the data matches
|
||||
tradeBots.addAll(additionalTradeBots);
|
||||
|
||||
for (TradeBotData tradeBotData : tradeBots) {
|
||||
byte[] tradePrivateKey = tradeBotData.getTradePrivateKey();
|
||||
TradeBotData repositoryTradeBotData = repository.getCrossChainRepository().getTradeBotData(tradePrivateKey);
|
||||
assertNotNull(repositoryTradeBotData);
|
||||
assertEquals(tradeBotData.toJson().toString(), repositoryTradeBotData.toJson().toString());
|
||||
}
|
||||
|
||||
repository.saveChanges();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExportAndImportLegacyTradeBotStates() throws DataException, IOException {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
|
||||
// Create some trade bots, but don't save them in the repository
|
||||
List<TradeBotData> tradeBots = new ArrayList<>();
|
||||
for (int i=0; i<10; i++) {
|
||||
TradeBotData tradeBotData = this.createTradeBotData(repository);
|
||||
tradeBots.add(tradeBotData);
|
||||
}
|
||||
|
||||
// Create a legacy format TradeBotStates.json backup file
|
||||
this.exportLegacyTradeBotStatesJson(tradeBots);
|
||||
|
||||
// Ensure no trade bots exist in repository
|
||||
assertTrue(repository.getCrossChainRepository().getAllTradeBotData().isEmpty());
|
||||
|
||||
// Import the legacy format file
|
||||
Path exportPath = HSQLDBImportExport.getExportDirectory(false);
|
||||
Path filePath = Paths.get(exportPath.toString(), "TradeBotStates.json");
|
||||
HSQLDBImportExport.importDataFromFile(filePath.toString(), repository);
|
||||
|
||||
// Ensure they have been imported
|
||||
assertEquals(10, repository.getCrossChainRepository().getAllTradeBotData().size());
|
||||
|
||||
for (TradeBotData tradeBotData : tradeBots) {
|
||||
byte[] tradePrivateKey = tradeBotData.getTradePrivateKey();
|
||||
TradeBotData repositoryTradeBotData = repository.getCrossChainRepository().getTradeBotData(tradePrivateKey);
|
||||
assertNotNull(repositoryTradeBotData);
|
||||
assertEquals(tradeBotData.toJson().toString(), repositoryTradeBotData.toJson().toString());
|
||||
}
|
||||
|
||||
repository.saveChanges();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExportAndImportMintingAccountData() throws DataException, IOException {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
|
||||
// Ensure no minting accounts exist
|
||||
assertTrue(repository.getAccountRepository().getMintingAccounts().isEmpty());
|
||||
|
||||
// Create some minting accounts
|
||||
List<MintingAccountData> mintingAccounts = new ArrayList<>();
|
||||
for (int i=0; i<10; i++) {
|
||||
MintingAccountData mintingAccountData = this.createMintingAccountData();
|
||||
repository.getAccountRepository().save(mintingAccountData);
|
||||
mintingAccounts.add(mintingAccountData);
|
||||
}
|
||||
|
||||
// Ensure they have been added
|
||||
assertEquals(10, repository.getAccountRepository().getMintingAccounts().size());
|
||||
|
||||
// Export them
|
||||
HSQLDBImportExport.backupMintingAccounts(repository);
|
||||
|
||||
// Delete them from the repository
|
||||
for (MintingAccountData mintingAccountData : mintingAccounts) {
|
||||
repository.getAccountRepository().delete(mintingAccountData.getPrivateKey());
|
||||
}
|
||||
|
||||
// Ensure they have been deleted
|
||||
assertTrue(repository.getAccountRepository().getMintingAccounts().isEmpty());
|
||||
|
||||
// Import them
|
||||
Path exportPath = HSQLDBImportExport.getExportDirectory(false);
|
||||
Path filePath = Paths.get(exportPath.toString(), "MintingAccounts.json");
|
||||
HSQLDBImportExport.importDataFromFile(filePath.toString(), repository);
|
||||
|
||||
// Ensure they have been imported
|
||||
assertEquals(10, repository.getAccountRepository().getMintingAccounts().size());
|
||||
|
||||
// Ensure all the data matches
|
||||
for (MintingAccountData mintingAccountData : mintingAccounts) {
|
||||
byte[] privateKey = mintingAccountData.getPrivateKey();
|
||||
MintingAccountData repositoryMintingAccountData = repository.getAccountRepository().getMintingAccount(privateKey);
|
||||
assertNotNull(repositoryMintingAccountData);
|
||||
assertEquals(mintingAccountData.toJson().toString(), repositoryMintingAccountData.toJson().toString());
|
||||
}
|
||||
|
||||
repository.saveChanges();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private TradeBotData createTradeBotData(Repository repository) throws DataException {
|
||||
byte[] tradePrivateKey = TradeBot.generateTradePrivateKey();
|
||||
|
||||
byte[] tradeNativePublicKey = TradeBot.deriveTradeNativePublicKey(tradePrivateKey);
|
||||
byte[] tradeNativePublicKeyHash = Crypto.hash160(tradeNativePublicKey);
|
||||
String tradeNativeAddress = Crypto.toAddress(tradeNativePublicKey);
|
||||
|
||||
byte[] tradeForeignPublicKey = TradeBot.deriveTradeForeignPublicKey(tradePrivateKey);
|
||||
byte[] tradeForeignPublicKeyHash = Crypto.hash160(tradeForeignPublicKey);
|
||||
|
||||
String receivingAddress = "2N8WCg52ULCtDSMjkgVTm5mtPdCsUptkHWE";
|
||||
|
||||
// Convert Litecoin receiving address into public key hash (we only support P2PKH at this time)
|
||||
Address litecoinReceivingAddress;
|
||||
try {
|
||||
litecoinReceivingAddress = Address.fromString(Litecoin.getInstance().getNetworkParameters(), receivingAddress);
|
||||
} catch (AddressFormatException e) {
|
||||
throw new DataException("Unsupported Litecoin receiving address: " + receivingAddress);
|
||||
}
|
||||
|
||||
byte[] litecoinReceivingAccountInfo = litecoinReceivingAddress.getHash();
|
||||
|
||||
byte[] creatorPublicKey = new byte[32];
|
||||
PublicKeyAccount creator = new PublicKeyAccount(repository, creatorPublicKey);
|
||||
|
||||
long timestamp = NTP.getTime();
|
||||
String atAddress = "AT_ADDRESS";
|
||||
long foreignAmount = 1234;
|
||||
long qortAmount= 5678;
|
||||
|
||||
TradeBotData tradeBotData = new TradeBotData(tradePrivateKey, LitecoinACCTv1.NAME,
|
||||
LitecoinACCTv1TradeBot.State.BOB_WAITING_FOR_AT_CONFIRM.name(), LitecoinACCTv1TradeBot.State.BOB_WAITING_FOR_AT_CONFIRM.value,
|
||||
creator.getAddress(), atAddress, timestamp, qortAmount,
|
||||
tradeNativePublicKey, tradeNativePublicKeyHash, tradeNativeAddress,
|
||||
null, null,
|
||||
SupportedBlockchain.LITECOIN.name(),
|
||||
tradeForeignPublicKey, tradeForeignPublicKeyHash,
|
||||
foreignAmount, null, null, null, litecoinReceivingAccountInfo);
|
||||
|
||||
return tradeBotData;
|
||||
}
|
||||
|
||||
private MintingAccountData createMintingAccountData() {
|
||||
// These don't need to be valid keys - just 32 byte strings for the purposes of testing
|
||||
byte[] privateKey = new ECKey().getPrivKeyBytes();
|
||||
byte[] publicKey = new ECKey().getPrivKeyBytes();
|
||||
|
||||
return new MintingAccountData(privateKey, publicKey);
|
||||
}
|
||||
|
||||
private void exportLegacyTradeBotStatesJson(List<TradeBotData> allTradeBotData) throws IOException, DataException {
|
||||
JSONArray allTradeBotDataJson = new JSONArray();
|
||||
for (TradeBotData tradeBotData : allTradeBotData) {
|
||||
JSONObject tradeBotDataJson = tradeBotData.toJson();
|
||||
allTradeBotDataJson.put(tradeBotDataJson);
|
||||
}
|
||||
|
||||
Path backupDirectory = HSQLDBImportExport.getExportDirectory(true);
|
||||
String fileName = Paths.get(backupDirectory.toString(), "TradeBotStates.json").toString();
|
||||
FileWriter writer = new FileWriter(fileName);
|
||||
writer.write(allTradeBotDataJson.toString());
|
||||
writer.close();
|
||||
}
|
||||
|
||||
private void deleteExportDirectory() {
|
||||
// Delete archive directory if exists
|
||||
Path archivePath = Paths.get(Settings.getInstance().getExportPath());
|
||||
try {
|
||||
FileUtils.deleteDirectory(archivePath.toFile());
|
||||
} catch (IOException e) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
91
src/test/java/org/qortal/test/PruneTests.java
Normal file
91
src/test/java/org/qortal/test/PruneTests.java
Normal file
@@ -0,0 +1,91 @@
|
||||
package org.qortal.test;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.qortal.account.PrivateKeyAccount;
|
||||
import org.qortal.controller.BlockMinter;
|
||||
import org.qortal.data.at.ATStateData;
|
||||
import org.qortal.data.block.BlockData;
|
||||
import org.qortal.repository.DataException;
|
||||
import org.qortal.repository.Repository;
|
||||
import org.qortal.repository.RepositoryManager;
|
||||
import org.qortal.test.common.AtUtils;
|
||||
import org.qortal.test.common.Common;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
public class PruneTests extends Common {
|
||||
|
||||
@Before
|
||||
public void beforeTest() throws DataException {
|
||||
Common.useDefaultSettings();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPruning() throws DataException {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
|
||||
// Alice self share online
|
||||
List<PrivateKeyAccount> mintingAndOnlineAccounts = new ArrayList<>();
|
||||
PrivateKeyAccount aliceSelfShare = Common.getTestAccount(repository, "alice-reward-share");
|
||||
mintingAndOnlineAccounts.add(aliceSelfShare);
|
||||
|
||||
// Deploy an AT so that we have AT state data
|
||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "alice");
|
||||
byte[] creationBytes = AtUtils.buildSimpleAT();
|
||||
long fundingAmount = 1_00000000L;
|
||||
AtUtils.doDeployAT(repository, deployer, creationBytes, fundingAmount);
|
||||
|
||||
// Mint some blocks
|
||||
for (int i = 2; i <= 10; i++)
|
||||
BlockMinter.mintTestingBlock(repository, mintingAndOnlineAccounts.toArray(new PrivateKeyAccount[0]));
|
||||
|
||||
// Make sure that all blocks have full AT state data and data hash
|
||||
for (Integer i=2; i <= 10; i++) {
|
||||
BlockData blockData = repository.getBlockRepository().fromHeight(i);
|
||||
assertNotNull(blockData.getSignature());
|
||||
assertEquals(i, blockData.getHeight());
|
||||
List<ATStateData> atStatesDataList = repository.getATRepository().getBlockATStatesAtHeight(i);
|
||||
assertNotNull(atStatesDataList);
|
||||
assertFalse(atStatesDataList.isEmpty());
|
||||
ATStateData atStatesData = repository.getATRepository().getATStateAtHeight(atStatesDataList.get(0).getATAddress(), i);
|
||||
assertNotNull(atStatesData.getStateHash());
|
||||
assertNotNull(atStatesData.getStateData());
|
||||
}
|
||||
|
||||
// Prune blocks 2-5
|
||||
int numBlocksPruned = repository.getBlockRepository().pruneBlocks(0, 5);
|
||||
assertEquals(4, numBlocksPruned);
|
||||
repository.getBlockRepository().setBlockPruneHeight(6);
|
||||
|
||||
// Prune AT states for blocks 2-5
|
||||
int numATStatesPruned = repository.getATRepository().pruneAtStates(0, 5);
|
||||
assertEquals(4, numATStatesPruned);
|
||||
repository.getATRepository().setAtPruneHeight(6);
|
||||
|
||||
// Make sure that blocks 2-5 are now missing block data and AT states data
|
||||
for (Integer i=2; i <= 5; i++) {
|
||||
BlockData blockData = repository.getBlockRepository().fromHeight(i);
|
||||
assertNull(blockData);
|
||||
List<ATStateData> atStatesDataList = repository.getATRepository().getBlockATStatesAtHeight(i);
|
||||
assertTrue(atStatesDataList.isEmpty());
|
||||
}
|
||||
|
||||
// ... but blocks 6-10 have block data and full AT states data
|
||||
for (Integer i=6; i <= 10; i++) {
|
||||
BlockData blockData = repository.getBlockRepository().fromHeight(i);
|
||||
assertNotNull(blockData.getSignature());
|
||||
List<ATStateData> atStatesDataList = repository.getATRepository().getBlockATStatesAtHeight(i);
|
||||
assertNotNull(atStatesDataList);
|
||||
assertFalse(atStatesDataList.isEmpty());
|
||||
ATStateData atStatesData = repository.getATRepository().getATStateAtHeight(atStatesDataList.get(0).getATAddress(), i);
|
||||
assertNotNull(atStatesData.getStateHash());
|
||||
assertNotNull(atStatesData.getStateData());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -3,9 +3,12 @@ package org.qortal.test;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.qortal.account.Account;
|
||||
import org.qortal.account.PublicKeyAccount;
|
||||
import org.qortal.asset.Asset;
|
||||
import org.qortal.crosschain.BitcoinACCTv1;
|
||||
import org.qortal.crypto.Crypto;
|
||||
import org.qortal.data.account.AccountBalanceData;
|
||||
import org.qortal.data.account.AccountData;
|
||||
import org.qortal.repository.DataException;
|
||||
import org.qortal.repository.Repository;
|
||||
import org.qortal.repository.RepositoryManager;
|
||||
@@ -22,13 +25,8 @@ import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.*;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
@@ -440,6 +438,119 @@ public class RepositoryTests extends Common {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDefrag() throws DataException, TimeoutException {
|
||||
try (final HSQLDBRepository hsqldb = (HSQLDBRepository) RepositoryManager.getRepository()) {
|
||||
|
||||
this.populateWithRandomData(hsqldb);
|
||||
|
||||
hsqldb.performPeriodicMaintenance(10 * 1000L);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDefragOnDisk() throws DataException, TimeoutException {
|
||||
Common.useSettingsAndDb(testSettingsFilename, false);
|
||||
|
||||
try (final HSQLDBRepository hsqldb = (HSQLDBRepository) RepositoryManager.getRepository()) {
|
||||
|
||||
this.populateWithRandomData(hsqldb);
|
||||
|
||||
hsqldb.performPeriodicMaintenance(10 * 1000L);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMultipleDefrags() throws DataException, TimeoutException {
|
||||
// Mint some more blocks to populate the database
|
||||
try (final HSQLDBRepository hsqldb = (HSQLDBRepository) RepositoryManager.getRepository()) {
|
||||
|
||||
this.populateWithRandomData(hsqldb);
|
||||
|
||||
for (int i = 0; i < 10; i++) {
|
||||
hsqldb.performPeriodicMaintenance(10 * 1000L);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMultipleDefragsOnDisk() throws DataException, TimeoutException {
|
||||
Common.useSettingsAndDb(testSettingsFilename, false);
|
||||
|
||||
// Mint some more blocks to populate the database
|
||||
try (final HSQLDBRepository hsqldb = (HSQLDBRepository) RepositoryManager.getRepository()) {
|
||||
|
||||
this.populateWithRandomData(hsqldb);
|
||||
|
||||
for (int i = 0; i < 10; i++) {
|
||||
hsqldb.performPeriodicMaintenance(10 * 1000L);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMultipleDefragsWithDifferentData() throws DataException, TimeoutException {
|
||||
for (int i=0; i<10; i++) {
|
||||
try (final HSQLDBRepository hsqldb = (HSQLDBRepository) RepositoryManager.getRepository()) {
|
||||
|
||||
this.populateWithRandomData(hsqldb);
|
||||
hsqldb.performPeriodicMaintenance(10 * 1000L);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMultipleDefragsOnDiskWithDifferentData() throws DataException, TimeoutException {
|
||||
Common.useSettingsAndDb(testSettingsFilename, false);
|
||||
|
||||
for (int i=0; i<10; i++) {
|
||||
try (final HSQLDBRepository hsqldb = (HSQLDBRepository) RepositoryManager.getRepository()) {
|
||||
|
||||
this.populateWithRandomData(hsqldb);
|
||||
hsqldb.performPeriodicMaintenance(10 * 1000L);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void populateWithRandomData(HSQLDBRepository repository) throws DataException {
|
||||
Random random = new Random();
|
||||
|
||||
System.out.println("Creating random accounts...");
|
||||
|
||||
// Generate some random accounts
|
||||
List<Account> accounts = new ArrayList<>();
|
||||
for (int ai = 0; ai < 20; ++ai) {
|
||||
byte[] publicKey = new byte[32];
|
||||
random.nextBytes(publicKey);
|
||||
|
||||
PublicKeyAccount account = new PublicKeyAccount(repository, publicKey);
|
||||
accounts.add(account);
|
||||
|
||||
AccountData accountData = new AccountData(account.getAddress());
|
||||
repository.getAccountRepository().ensureAccount(accountData);
|
||||
}
|
||||
repository.saveChanges();
|
||||
|
||||
System.out.println("Creating random balances...");
|
||||
|
||||
// Fill with lots of random balances
|
||||
for (int i = 0; i < 100000; ++i) {
|
||||
Account account = accounts.get(random.nextInt(accounts.size()));
|
||||
int assetId = random.nextInt(2);
|
||||
long balance = random.nextInt(100000);
|
||||
|
||||
AccountBalanceData accountBalanceData = new AccountBalanceData(account.getAddress(), assetId, balance);
|
||||
repository.getAccountRepository().save(accountBalanceData);
|
||||
|
||||
// Maybe mint a block to change height
|
||||
if (i > 0 && (i % 1000) == 0)
|
||||
BlockUtils.mintBlock(repository);
|
||||
}
|
||||
repository.saveChanges();
|
||||
}
|
||||
|
||||
public static void hsqldbSleep(int millis) throws SQLException {
|
||||
System.out.println(String.format("HSQLDB sleep() thread ID: %s", Thread.currentThread().getId()));
|
||||
|
||||
|
@@ -14,9 +14,9 @@ public class CheckTranslations {
|
||||
|
||||
private static final String[] SUPPORTED_LANGS = new String[] { "en", "de", "zh", "ru" };
|
||||
private static final Set<String> SYSTRAY_KEYS = Set.of("AUTO_UPDATE", "APPLYING_UPDATE_AND_RESTARTING", "BLOCK_HEIGHT",
|
||||
"CHECK_TIME_ACCURACY", "CONNECTING", "CONNECTION", "CONNECTIONS", "CREATING_BACKUP_OF_DB_FILES", "DB_BACKUP", "EXIT",
|
||||
"MINTING_DISABLED", "MINTING_ENABLED", "NTP_NAG_CAPTION", "NTP_NAG_TEXT_UNIX", "NTP_NAG_TEXT_WINDOWS",
|
||||
"OPEN_UI", "SYNCHRONIZE_CLOCK", "SYNCHRONIZING_BLOCKCHAIN", "SYNCHRONIZING_CLOCK");
|
||||
"BUILD_VERSION", "CHECK_TIME_ACCURACY", "CONNECTING", "CONNECTION", "CONNECTIONS", "CREATING_BACKUP_OF_DB_FILES",
|
||||
"DB_BACKUP", "DB_CHECKPOINT", "EXIT", "MINTING_DISABLED", "MINTING_ENABLED", "OPEN_UI", "PERFORMING_DB_CHECKPOINT",
|
||||
"SYNCHRONIZE_CLOCK", "SYNCHRONIZING_BLOCKCHAIN", "SYNCHRONIZING_CLOCK");
|
||||
|
||||
private static String failurePrefix;
|
||||
|
||||
|
@@ -21,6 +21,7 @@ import org.qortal.group.Group;
|
||||
import org.qortal.repository.DataException;
|
||||
import org.qortal.repository.Repository;
|
||||
import org.qortal.repository.RepositoryManager;
|
||||
import org.qortal.test.common.AtUtils;
|
||||
import org.qortal.test.common.BlockUtils;
|
||||
import org.qortal.test.common.Common;
|
||||
import org.qortal.test.common.TransactionUtils;
|
||||
@@ -35,13 +36,13 @@ public class AtRepositoryTests extends Common {
|
||||
|
||||
@Test
|
||||
public void testGetATStateAtHeightWithData() throws DataException {
|
||||
byte[] creationBytes = buildSimpleAT();
|
||||
byte[] creationBytes = AtUtils.buildSimpleAT();
|
||||
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "alice");
|
||||
|
||||
long fundingAmount = 1_00000000L;
|
||||
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, creationBytes, fundingAmount);
|
||||
DeployAtTransaction deployAtTransaction = AtUtils.doDeployAT(repository, deployer, creationBytes, fundingAmount);
|
||||
String atAddress = deployAtTransaction.getATAccount().getAddress();
|
||||
|
||||
// Mint a few blocks
|
||||
@@ -58,13 +59,13 @@ public class AtRepositoryTests extends Common {
|
||||
|
||||
@Test
|
||||
public void testGetATStateAtHeightWithoutData() throws DataException {
|
||||
byte[] creationBytes = buildSimpleAT();
|
||||
byte[] creationBytes = AtUtils.buildSimpleAT();
|
||||
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "alice");
|
||||
|
||||
long fundingAmount = 1_00000000L;
|
||||
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, creationBytes, fundingAmount);
|
||||
DeployAtTransaction deployAtTransaction = AtUtils.doDeployAT(repository, deployer, creationBytes, fundingAmount);
|
||||
String atAddress = deployAtTransaction.getATAccount().getAddress();
|
||||
|
||||
// Mint a few blocks
|
||||
@@ -75,7 +76,7 @@ public class AtRepositoryTests extends Common {
|
||||
Integer testHeight = maxHeight - 2;
|
||||
|
||||
// Trim AT state data
|
||||
repository.getATRepository().prepareForAtStateTrimming();
|
||||
repository.getATRepository().rebuildLatestAtStates();
|
||||
repository.getATRepository().trimAtStates(2, maxHeight, 1000);
|
||||
|
||||
ATStateData atStateData = repository.getATRepository().getATStateAtHeight(atAddress, testHeight);
|
||||
@@ -87,13 +88,13 @@ public class AtRepositoryTests extends Common {
|
||||
|
||||
@Test
|
||||
public void testGetLatestATStateWithData() throws DataException {
|
||||
byte[] creationBytes = buildSimpleAT();
|
||||
byte[] creationBytes = AtUtils.buildSimpleAT();
|
||||
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "alice");
|
||||
|
||||
long fundingAmount = 1_00000000L;
|
||||
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, creationBytes, fundingAmount);
|
||||
DeployAtTransaction deployAtTransaction = AtUtils.doDeployAT(repository, deployer, creationBytes, fundingAmount);
|
||||
String atAddress = deployAtTransaction.getATAccount().getAddress();
|
||||
|
||||
// Mint a few blocks
|
||||
@@ -111,13 +112,13 @@ public class AtRepositoryTests extends Common {
|
||||
|
||||
@Test
|
||||
public void testGetLatestATStatePostTrimming() throws DataException {
|
||||
byte[] creationBytes = buildSimpleAT();
|
||||
byte[] creationBytes = AtUtils.buildSimpleAT();
|
||||
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "alice");
|
||||
|
||||
long fundingAmount = 1_00000000L;
|
||||
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, creationBytes, fundingAmount);
|
||||
DeployAtTransaction deployAtTransaction = AtUtils.doDeployAT(repository, deployer, creationBytes, fundingAmount);
|
||||
String atAddress = deployAtTransaction.getATAccount().getAddress();
|
||||
|
||||
// Mint a few blocks
|
||||
@@ -129,7 +130,7 @@ public class AtRepositoryTests extends Common {
|
||||
Integer testHeight = blockchainHeight;
|
||||
|
||||
// Trim AT state data
|
||||
repository.getATRepository().prepareForAtStateTrimming();
|
||||
repository.getATRepository().rebuildLatestAtStates();
|
||||
// COMMIT to check latest AT states persist / TEMPORARY table interaction
|
||||
repository.saveChanges();
|
||||
|
||||
@@ -144,14 +145,66 @@ public class AtRepositoryTests extends Common {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetMatchingFinalATStatesWithoutDataValue() throws DataException {
|
||||
byte[] creationBytes = buildSimpleAT();
|
||||
public void testOrphanTrimmedATStates() throws DataException {
|
||||
byte[] creationBytes = AtUtils.buildSimpleAT();
|
||||
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "alice");
|
||||
|
||||
long fundingAmount = 1_00000000L;
|
||||
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, creationBytes, fundingAmount);
|
||||
DeployAtTransaction deployAtTransaction = AtUtils.doDeployAT(repository, deployer, creationBytes, fundingAmount);
|
||||
String atAddress = deployAtTransaction.getATAccount().getAddress();
|
||||
|
||||
// Mint a few blocks
|
||||
for (int i = 0; i < 10; ++i)
|
||||
BlockUtils.mintBlock(repository);
|
||||
|
||||
int blockchainHeight = repository.getBlockRepository().getBlockchainHeight();
|
||||
int maxTrimHeight = blockchainHeight - 4;
|
||||
Integer testHeight = maxTrimHeight + 1;
|
||||
|
||||
// Trim AT state data
|
||||
repository.getATRepository().rebuildLatestAtStates();
|
||||
repository.saveChanges();
|
||||
repository.getATRepository().trimAtStates(2, maxTrimHeight, 1000);
|
||||
|
||||
// Orphan 3 blocks
|
||||
// This leaves one more untrimmed block, so the latest AT state should be available
|
||||
BlockUtils.orphanBlocks(repository, 3);
|
||||
|
||||
ATStateData atStateData = repository.getATRepository().getLatestATState(atAddress);
|
||||
assertEquals(testHeight, atStateData.getHeight());
|
||||
|
||||
// We should always have the latest AT state data available
|
||||
assertNotNull(atStateData.getStateData());
|
||||
|
||||
// Orphan 1 more block
|
||||
Exception exception = null;
|
||||
try {
|
||||
BlockUtils.orphanBlocks(repository, 1);
|
||||
} catch (DataException e) {
|
||||
exception = e;
|
||||
}
|
||||
|
||||
// Ensure that a DataException is thrown because there is no more AT states data available
|
||||
assertNotNull(exception);
|
||||
assertEquals(DataException.class, exception.getClass());
|
||||
assertEquals(String.format("Can't find previous AT state data for %s", atAddress), exception.getMessage());
|
||||
|
||||
// FUTURE: we may be able to retain unique AT states when trimming, to avoid this exception
|
||||
// and allow orphaning back through blocks with trimmed AT states.
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetMatchingFinalATStatesWithoutDataValue() throws DataException {
|
||||
byte[] creationBytes = AtUtils.buildSimpleAT();
|
||||
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "alice");
|
||||
|
||||
long fundingAmount = 1_00000000L;
|
||||
DeployAtTransaction deployAtTransaction = AtUtils.doDeployAT(repository, deployer, creationBytes, fundingAmount);
|
||||
String atAddress = deployAtTransaction.getATAccount().getAddress();
|
||||
|
||||
// Mint a few blocks
|
||||
@@ -191,13 +244,13 @@ public class AtRepositoryTests extends Common {
|
||||
|
||||
@Test
|
||||
public void testGetMatchingFinalATStatesWithDataValue() throws DataException {
|
||||
byte[] creationBytes = buildSimpleAT();
|
||||
byte[] creationBytes = AtUtils.buildSimpleAT();
|
||||
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "alice");
|
||||
|
||||
long fundingAmount = 1_00000000L;
|
||||
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, creationBytes, fundingAmount);
|
||||
DeployAtTransaction deployAtTransaction = AtUtils.doDeployAT(repository, deployer, creationBytes, fundingAmount);
|
||||
String atAddress = deployAtTransaction.getATAccount().getAddress();
|
||||
|
||||
// Mint a few blocks
|
||||
@@ -237,13 +290,13 @@ public class AtRepositoryTests extends Common {
|
||||
|
||||
@Test
|
||||
public void testGetBlockATStatesAtHeightWithData() throws DataException {
|
||||
byte[] creationBytes = buildSimpleAT();
|
||||
byte[] creationBytes = AtUtils.buildSimpleAT();
|
||||
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "alice");
|
||||
|
||||
long fundingAmount = 1_00000000L;
|
||||
doDeploy(repository, deployer, creationBytes, fundingAmount);
|
||||
AtUtils.doDeployAT(repository, deployer, creationBytes, fundingAmount);
|
||||
|
||||
// Mint a few blocks
|
||||
for (int i = 0; i < 10; ++i)
|
||||
@@ -264,13 +317,13 @@ public class AtRepositoryTests extends Common {
|
||||
|
||||
@Test
|
||||
public void testGetBlockATStatesAtHeightWithoutData() throws DataException {
|
||||
byte[] creationBytes = buildSimpleAT();
|
||||
byte[] creationBytes = AtUtils.buildSimpleAT();
|
||||
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "alice");
|
||||
|
||||
long fundingAmount = 1_00000000L;
|
||||
doDeploy(repository, deployer, creationBytes, fundingAmount);
|
||||
AtUtils.doDeployAT(repository, deployer, creationBytes, fundingAmount);
|
||||
|
||||
// Mint a few blocks
|
||||
for (int i = 0; i < 10; ++i)
|
||||
@@ -280,7 +333,7 @@ public class AtRepositoryTests extends Common {
|
||||
Integer testHeight = maxHeight - 2;
|
||||
|
||||
// Trim AT state data
|
||||
repository.getATRepository().prepareForAtStateTrimming();
|
||||
repository.getATRepository().rebuildLatestAtStates();
|
||||
repository.getATRepository().trimAtStates(2, maxHeight, 1000);
|
||||
|
||||
List<ATStateData> atStates = repository.getATRepository().getBlockATStatesAtHeight(testHeight);
|
||||
@@ -297,13 +350,13 @@ public class AtRepositoryTests extends Common {
|
||||
|
||||
@Test
|
||||
public void testSaveATStateWithData() throws DataException {
|
||||
byte[] creationBytes = buildSimpleAT();
|
||||
byte[] creationBytes = AtUtils.buildSimpleAT();
|
||||
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "alice");
|
||||
|
||||
long fundingAmount = 1_00000000L;
|
||||
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, creationBytes, fundingAmount);
|
||||
DeployAtTransaction deployAtTransaction = AtUtils.doDeployAT(repository, deployer, creationBytes, fundingAmount);
|
||||
String atAddress = deployAtTransaction.getATAccount().getAddress();
|
||||
|
||||
// Mint a few blocks
|
||||
@@ -328,13 +381,13 @@ public class AtRepositoryTests extends Common {
|
||||
|
||||
@Test
|
||||
public void testSaveATStateWithoutData() throws DataException {
|
||||
byte[] creationBytes = buildSimpleAT();
|
||||
byte[] creationBytes = AtUtils.buildSimpleAT();
|
||||
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "alice");
|
||||
|
||||
long fundingAmount = 1_00000000L;
|
||||
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, creationBytes, fundingAmount);
|
||||
DeployAtTransaction deployAtTransaction = AtUtils.doDeployAT(repository, deployer, creationBytes, fundingAmount);
|
||||
String atAddress = deployAtTransaction.getATAccount().getAddress();
|
||||
|
||||
// Mint a few blocks
|
||||
@@ -354,7 +407,8 @@ public class AtRepositoryTests extends Common {
|
||||
/*StateData*/ null,
|
||||
atStateData.getStateHash(),
|
||||
atStateData.getFees(),
|
||||
atStateData.isInitial());
|
||||
atStateData.isInitial(),
|
||||
atStateData.getSleepUntilMessageTimestamp());
|
||||
repository.getATRepository().save(newAtStateData);
|
||||
|
||||
atStateData = repository.getATRepository().getATStateAtHeight(atAddress, testHeight);
|
||||
@@ -363,67 +417,4 @@ public class AtRepositoryTests extends Common {
|
||||
assertNull(atStateData.getStateData());
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] buildSimpleAT() {
|
||||
// Pretend we use 4 values in data segment
|
||||
int addrCounter = 4;
|
||||
|
||||
// Data segment
|
||||
ByteBuffer dataByteBuffer = ByteBuffer.allocate(addrCounter * MachineState.VALUE_SIZE);
|
||||
|
||||
ByteBuffer codeByteBuffer = ByteBuffer.allocate(512);
|
||||
|
||||
// Two-pass version
|
||||
for (int pass = 0; pass < 2; ++pass) {
|
||||
codeByteBuffer.clear();
|
||||
|
||||
try {
|
||||
// Stop and wait for next block
|
||||
codeByteBuffer.put(OpCode.STP_IMD.compile());
|
||||
} catch (CompilationException e) {
|
||||
throw new IllegalStateException("Unable to compile AT?", e);
|
||||
}
|
||||
}
|
||||
|
||||
codeByteBuffer.flip();
|
||||
|
||||
byte[] codeBytes = new byte[codeByteBuffer.limit()];
|
||||
codeByteBuffer.get(codeBytes);
|
||||
|
||||
final short ciyamAtVersion = 2;
|
||||
final short numCallStackPages = 0;
|
||||
final short numUserStackPages = 0;
|
||||
final long minActivationAmount = 0L;
|
||||
|
||||
return MachineState.toCreationBytes(ciyamAtVersion, codeBytes, dataByteBuffer.array(), numCallStackPages, numUserStackPages, minActivationAmount);
|
||||
}
|
||||
|
||||
private DeployAtTransaction doDeploy(Repository repository, PrivateKeyAccount deployer, byte[] creationBytes, long fundingAmount) throws DataException {
|
||||
long txTimestamp = System.currentTimeMillis();
|
||||
byte[] lastReference = deployer.getLastReference();
|
||||
|
||||
if (lastReference == null) {
|
||||
System.err.println(String.format("Qortal account %s has no last reference", deployer.getAddress()));
|
||||
System.exit(2);
|
||||
}
|
||||
|
||||
Long fee = null;
|
||||
String name = "Test AT";
|
||||
String description = "Test AT";
|
||||
String atType = "Test";
|
||||
String tags = "TEST";
|
||||
|
||||
BaseTransactionData baseTransactionData = new BaseTransactionData(txTimestamp, Group.NO_GROUP, lastReference, deployer.getPublicKey(), fee, null);
|
||||
TransactionData deployAtTransactionData = new DeployAtTransactionData(baseTransactionData, name, description, atType, tags, creationBytes, fundingAmount, Asset.QORT);
|
||||
|
||||
DeployAtTransaction deployAtTransaction = new DeployAtTransaction(repository, deployAtTransactionData);
|
||||
|
||||
fee = deployAtTransaction.calcRecommendedFee();
|
||||
deployAtTransactionData.setFee(fee);
|
||||
|
||||
TransactionUtils.signAndMint(repository, deployAtTransactionData, deployer);
|
||||
|
||||
return deployAtTransaction;
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -0,0 +1,365 @@
|
||||
package org.qortal.test.at;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.ciyam.at.CompilationException;
|
||||
import org.ciyam.at.FunctionCode;
|
||||
import org.ciyam.at.MachineState;
|
||||
import org.ciyam.at.OpCode;
|
||||
import org.ciyam.at.Timestamp;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.qortal.account.Account;
|
||||
import org.qortal.account.PrivateKeyAccount;
|
||||
import org.qortal.asset.Asset;
|
||||
import org.qortal.at.QortalFunctionCode;
|
||||
import org.qortal.block.Block;
|
||||
import org.qortal.data.at.ATStateData;
|
||||
import org.qortal.data.block.BlockData;
|
||||
import org.qortal.data.transaction.BaseTransactionData;
|
||||
import org.qortal.data.transaction.DeployAtTransactionData;
|
||||
import org.qortal.data.transaction.MessageTransactionData;
|
||||
import org.qortal.data.transaction.TransactionData;
|
||||
import org.qortal.group.Group;
|
||||
import org.qortal.repository.DataException;
|
||||
import org.qortal.repository.Repository;
|
||||
import org.qortal.repository.RepositoryManager;
|
||||
import org.qortal.test.common.BlockUtils;
|
||||
import org.qortal.test.common.Common;
|
||||
import org.qortal.test.common.TransactionUtils;
|
||||
import org.qortal.transaction.DeployAtTransaction;
|
||||
import org.qortal.transaction.MessageTransaction;
|
||||
import org.qortal.transaction.Transaction;
|
||||
import org.qortal.utils.BitTwiddling;
|
||||
|
||||
public class SleepUntilMessageOrHeightTests extends Common {
|
||||
|
||||
private static final byte[] messageData = new byte[] { 0x44 };
|
||||
private static final byte[] creationBytes = buildSleepUntilMessageOrHeightAT();
|
||||
private static final long fundingAmount = 1_00000000L;
|
||||
private static final long WAKE_HEIGHT = 10L;
|
||||
|
||||
private Repository repository = null;
|
||||
private PrivateKeyAccount deployer;
|
||||
private DeployAtTransaction deployAtTransaction;
|
||||
private Account atAccount;
|
||||
private String atAddress;
|
||||
private byte[] rawNextTimestamp = new byte[32];
|
||||
private Transaction transaction;
|
||||
|
||||
@Before
|
||||
public void before() throws DataException {
|
||||
Common.useDefaultSettings();
|
||||
|
||||
this.repository = RepositoryManager.getRepository();
|
||||
this.deployer = Common.getTestAccount(repository, "alice");
|
||||
|
||||
this.deployAtTransaction = doDeploy(repository, deployer, creationBytes, fundingAmount);
|
||||
this.atAccount = deployAtTransaction.getATAccount();
|
||||
this.atAddress = deployAtTransaction.getATAccount().getAddress();
|
||||
}
|
||||
|
||||
@After
|
||||
public void after() throws DataException {
|
||||
if (this.repository != null)
|
||||
this.repository.close();
|
||||
|
||||
this.repository = null;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeploy() throws DataException {
|
||||
// Confirm initial value is zero
|
||||
extractNextTxTimestamp(repository, atAddress, rawNextTimestamp);
|
||||
assertArrayEquals(new byte[32], rawNextTimestamp);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFeelessSleep() throws DataException {
|
||||
// Mint block to allow AT to initialize and call SLEEP_UNTIL_MESSAGE
|
||||
BlockUtils.mintBlock(repository);
|
||||
|
||||
// Fetch AT's balance for this height
|
||||
long preMintBalance = atAccount.getConfirmedBalance(Asset.QORT);
|
||||
|
||||
// Mint block
|
||||
BlockUtils.mintBlock(repository);
|
||||
|
||||
// Fetch new AT balance
|
||||
long postMintBalance = atAccount.getConfirmedBalance(Asset.QORT);
|
||||
|
||||
assertEquals(preMintBalance, postMintBalance);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFeelessSleep2() throws DataException {
|
||||
// Mint block to allow AT to initialize and call SLEEP_UNTIL_MESSAGE
|
||||
BlockUtils.mintBlock(repository);
|
||||
|
||||
// Fetch AT's balance for this height
|
||||
long preMintBalance = atAccount.getConfirmedBalance(Asset.QORT);
|
||||
|
||||
// Mint several blocks
|
||||
for (int i = 0; i < 5; ++i)
|
||||
BlockUtils.mintBlock(repository);
|
||||
|
||||
// Fetch new AT balance
|
||||
long postMintBalance = atAccount.getConfirmedBalance(Asset.QORT);
|
||||
|
||||
assertEquals(preMintBalance, postMintBalance);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSleepUntilMessage() throws DataException {
|
||||
// Mint block to allow AT to initialize and call SLEEP_UNTIL_MESSAGE_OR_HEIGHT
|
||||
BlockUtils.mintBlock(repository);
|
||||
|
||||
// Send message to AT
|
||||
transaction = sendMessage(repository, deployer, messageData, atAddress);
|
||||
BlockUtils.mintBlock(repository);
|
||||
|
||||
// Mint block so AT executes and finds message
|
||||
BlockUtils.mintBlock(repository);
|
||||
|
||||
// Confirm AT finds message
|
||||
assertTimestamp(repository, atAddress, transaction);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSleepUntilHeight() throws DataException {
|
||||
// AT deployment in block 2
|
||||
|
||||
// Mint block to allow AT to initialize and call SLEEP_UNTIL_MESSAGE_OR_HEIGHT
|
||||
BlockUtils.mintBlock(repository); // height now 3
|
||||
|
||||
// Fetch AT's balance for this height
|
||||
long preMintBalance = atAccount.getConfirmedBalance(Asset.QORT);
|
||||
|
||||
// Mint several blocks
|
||||
for (int i = 3; i < WAKE_HEIGHT; ++i)
|
||||
BlockUtils.mintBlock(repository);
|
||||
|
||||
// We should now be at WAKE_HEIGHT
|
||||
long height = repository.getBlockRepository().getBlockchainHeight();
|
||||
assertEquals(WAKE_HEIGHT, height);
|
||||
|
||||
// AT should have woken and run at this height so balance should have changed
|
||||
|
||||
// Fetch new AT balance
|
||||
long postMintBalance = atAccount.getConfirmedBalance(Asset.QORT);
|
||||
|
||||
assertNotSame(preMintBalance, postMintBalance);
|
||||
|
||||
// Confirm AT has no message
|
||||
extractNextTxTimestamp(repository, atAddress, rawNextTimestamp);
|
||||
assertArrayEquals(new byte[32], rawNextTimestamp);
|
||||
|
||||
// Mint yet another block
|
||||
BlockUtils.mintBlock(repository);
|
||||
|
||||
// AT should also have woken and run at this height so balance should have changed
|
||||
|
||||
// Fetch new AT balance
|
||||
long postMint2Balance = atAccount.getConfirmedBalance(Asset.QORT);
|
||||
|
||||
assertNotSame(postMintBalance, postMint2Balance);
|
||||
|
||||
// Confirm AT still has no message
|
||||
extractNextTxTimestamp(repository, atAddress, rawNextTimestamp);
|
||||
assertArrayEquals(new byte[32], rawNextTimestamp);
|
||||
|
||||
}
|
||||
|
||||
private static byte[] buildSleepUntilMessageOrHeightAT() {
|
||||
// Labels for data segment addresses
|
||||
int addrCounter = 0;
|
||||
|
||||
// Beginning of data segment for easy extraction
|
||||
final int addrNextTx = addrCounter;
|
||||
addrCounter += 4;
|
||||
|
||||
final int addrNextTxIndex = addrCounter++;
|
||||
|
||||
final int addrLastTxTimestamp = addrCounter++;
|
||||
|
||||
final int addrWakeHeight = addrCounter++;
|
||||
|
||||
// Data segment
|
||||
ByteBuffer dataByteBuffer = ByteBuffer.allocate(addrCounter * MachineState.VALUE_SIZE);
|
||||
|
||||
// skip addrNextTx
|
||||
dataByteBuffer.position(dataByteBuffer.position() + 4 * MachineState.VALUE_SIZE);
|
||||
|
||||
// Store pointer to addrNextTx at addrNextTxIndex
|
||||
dataByteBuffer.putLong(addrNextTx);
|
||||
|
||||
// skip addrLastTxTimestamp
|
||||
dataByteBuffer.position(dataByteBuffer.position() + MachineState.VALUE_SIZE);
|
||||
|
||||
// Store fixed wake height (block 10)
|
||||
dataByteBuffer.putLong(WAKE_HEIGHT);
|
||||
|
||||
ByteBuffer codeByteBuffer = ByteBuffer.allocate(512);
|
||||
|
||||
// Two-pass version
|
||||
for (int pass = 0; pass < 2; ++pass) {
|
||||
codeByteBuffer.clear();
|
||||
|
||||
try {
|
||||
/* Initialization */
|
||||
|
||||
// Use AT creation 'timestamp' as starting point for finding transactions sent to AT
|
||||
codeByteBuffer.put(OpCode.EXT_FUN_RET.compile(FunctionCode.GET_CREATION_TIMESTAMP, addrLastTxTimestamp));
|
||||
|
||||
// Set restart position to after this opcode
|
||||
codeByteBuffer.put(OpCode.SET_PCS.compile());
|
||||
|
||||
/* Loop, waiting for message to AT */
|
||||
|
||||
/* Sleep until message arrives */
|
||||
codeByteBuffer.put(OpCode.EXT_FUN_DAT_2.compile(QortalFunctionCode.SLEEP_UNTIL_MESSAGE_OR_HEIGHT.value, addrLastTxTimestamp, addrWakeHeight));
|
||||
|
||||
// Find next transaction to this AT since the last one (if any)
|
||||
codeByteBuffer.put(OpCode.EXT_FUN_DAT.compile(FunctionCode.PUT_TX_AFTER_TIMESTAMP_INTO_A, addrLastTxTimestamp));
|
||||
|
||||
// Copy A to data segment, starting at addrNextTx (as pointed to by addrNextTxIndex)
|
||||
codeByteBuffer.put(OpCode.EXT_FUN_DAT.compile(FunctionCode.GET_A_IND, addrNextTxIndex));
|
||||
|
||||
// Stop if timestamp part of A is zero
|
||||
codeByteBuffer.put(OpCode.STZ_DAT.compile(addrNextTx));
|
||||
|
||||
// Update our 'last found transaction's timestamp' using 'timestamp' from transaction
|
||||
codeByteBuffer.put(OpCode.EXT_FUN_RET.compile(FunctionCode.GET_TIMESTAMP_FROM_TX_IN_A, addrLastTxTimestamp));
|
||||
|
||||
// We're done
|
||||
codeByteBuffer.put(OpCode.FIN_IMD.compile());
|
||||
|
||||
} catch (CompilationException e) {
|
||||
throw new IllegalStateException("Unable to compile AT?", e);
|
||||
}
|
||||
}
|
||||
|
||||
codeByteBuffer.flip();
|
||||
|
||||
byte[] codeBytes = new byte[codeByteBuffer.limit()];
|
||||
codeByteBuffer.get(codeBytes);
|
||||
|
||||
final short ciyamAtVersion = 2;
|
||||
final short numCallStackPages = 0;
|
||||
final short numUserStackPages = 0;
|
||||
final long minActivationAmount = 0L;
|
||||
|
||||
return MachineState.toCreationBytes(ciyamAtVersion, codeBytes, dataByteBuffer.array(), numCallStackPages, numUserStackPages, minActivationAmount);
|
||||
}
|
||||
|
||||
private DeployAtTransaction doDeploy(Repository repository, PrivateKeyAccount deployer, byte[] creationBytes, long fundingAmount) throws DataException {
|
||||
long txTimestamp = System.currentTimeMillis();
|
||||
byte[] lastReference = deployer.getLastReference();
|
||||
|
||||
if (lastReference == null) {
|
||||
System.err.println(String.format("Qortal account %s has no last reference", deployer.getAddress()));
|
||||
System.exit(2);
|
||||
}
|
||||
|
||||
Long fee = null;
|
||||
String name = "Test AT";
|
||||
String description = "Test AT";
|
||||
String atType = "Test";
|
||||
String tags = "TEST";
|
||||
|
||||
BaseTransactionData baseTransactionData = new BaseTransactionData(txTimestamp, Group.NO_GROUP, lastReference, deployer.getPublicKey(), fee, null);
|
||||
TransactionData deployAtTransactionData = new DeployAtTransactionData(baseTransactionData, name, description, atType, tags, creationBytes, fundingAmount, Asset.QORT);
|
||||
|
||||
DeployAtTransaction deployAtTransaction = new DeployAtTransaction(repository, deployAtTransactionData);
|
||||
|
||||
fee = deployAtTransaction.calcRecommendedFee();
|
||||
deployAtTransactionData.setFee(fee);
|
||||
|
||||
TransactionUtils.signAndMint(repository, deployAtTransactionData, deployer);
|
||||
|
||||
return deployAtTransaction;
|
||||
}
|
||||
|
||||
private void extractNextTxTimestamp(Repository repository, String atAddress, byte[] rawNextTimestamp) throws DataException {
|
||||
// Check AT result
|
||||
ATStateData atStateData = repository.getATRepository().getLatestATState(atAddress);
|
||||
byte[] stateData = atStateData.getStateData();
|
||||
|
||||
byte[] dataBytes = MachineState.extractDataBytes(stateData);
|
||||
|
||||
System.arraycopy(dataBytes, 0, rawNextTimestamp, 0, rawNextTimestamp.length);
|
||||
}
|
||||
|
||||
private MessageTransaction sendMessage(Repository repository, PrivateKeyAccount sender, byte[] data, String recipient) throws DataException {
|
||||
long txTimestamp = System.currentTimeMillis();
|
||||
byte[] lastReference = sender.getLastReference();
|
||||
|
||||
if (lastReference == null) {
|
||||
System.err.println(String.format("Qortal account %s has no last reference", sender.getAddress()));
|
||||
System.exit(2);
|
||||
}
|
||||
|
||||
Long fee = null;
|
||||
int version = 4;
|
||||
int nonce = 0;
|
||||
long amount = 0;
|
||||
Long assetId = null; // because amount is zero
|
||||
|
||||
BaseTransactionData baseTransactionData = new BaseTransactionData(txTimestamp, Group.NO_GROUP, lastReference, sender.getPublicKey(), fee, null);
|
||||
TransactionData messageTransactionData = new MessageTransactionData(baseTransactionData, version, nonce, recipient, amount, assetId, data, false, false);
|
||||
|
||||
MessageTransaction messageTransaction = new MessageTransaction(repository, messageTransactionData);
|
||||
|
||||
fee = messageTransaction.calcRecommendedFee();
|
||||
messageTransactionData.setFee(fee);
|
||||
|
||||
TransactionUtils.signAndImportValid(repository, messageTransactionData, sender);
|
||||
|
||||
return messageTransaction;
|
||||
}
|
||||
|
||||
private void assertTimestamp(Repository repository, String atAddress, Transaction transaction) throws DataException {
|
||||
int height = transaction.getHeight();
|
||||
byte[] transactionSignature = transaction.getTransactionData().getSignature();
|
||||
|
||||
BlockData blockData = repository.getBlockRepository().fromHeight(height);
|
||||
assertNotNull(blockData);
|
||||
|
||||
Block block = new Block(repository, blockData);
|
||||
|
||||
List<Transaction> blockTransactions = block.getTransactions();
|
||||
int sequence;
|
||||
for (sequence = blockTransactions.size() - 1; sequence >= 0; --sequence)
|
||||
if (Arrays.equals(blockTransactions.get(sequence).getTransactionData().getSignature(), transactionSignature))
|
||||
break;
|
||||
|
||||
assertNotSame(-1, sequence);
|
||||
|
||||
byte[] rawNextTimestamp = new byte[32];
|
||||
extractNextTxTimestamp(repository, atAddress, rawNextTimestamp);
|
||||
|
||||
Timestamp expectedTimestamp = new Timestamp(height, sequence);
|
||||
Timestamp actualTimestamp = new Timestamp(BitTwiddling.longFromBEBytes(rawNextTimestamp, 0));
|
||||
|
||||
assertEquals(String.format("Expected height %d, seq %d but was height %d, seq %d",
|
||||
height, sequence,
|
||||
actualTimestamp.blockHeight, actualTimestamp.transactionSequence
|
||||
),
|
||||
expectedTimestamp.longValue(),
|
||||
actualTimestamp.longValue());
|
||||
|
||||
byte[] expectedPartialSignature = new byte[24];
|
||||
System.arraycopy(transactionSignature, 8, expectedPartialSignature, 0, expectedPartialSignature.length);
|
||||
|
||||
byte[] actualPartialSignature = new byte[24];
|
||||
System.arraycopy(rawNextTimestamp, 8, actualPartialSignature, 0, actualPartialSignature.length);
|
||||
|
||||
assertArrayEquals(expectedPartialSignature, actualPartialSignature);
|
||||
}
|
||||
|
||||
}
|
311
src/test/java/org/qortal/test/at/SleepUntilMessageTests.java
Normal file
311
src/test/java/org/qortal/test/at/SleepUntilMessageTests.java
Normal file
@@ -0,0 +1,311 @@
|
||||
package org.qortal.test.at;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.ciyam.at.CompilationException;
|
||||
import org.ciyam.at.FunctionCode;
|
||||
import org.ciyam.at.MachineState;
|
||||
import org.ciyam.at.OpCode;
|
||||
import org.ciyam.at.Timestamp;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.qortal.account.Account;
|
||||
import org.qortal.account.PrivateKeyAccount;
|
||||
import org.qortal.asset.Asset;
|
||||
import org.qortal.at.QortalFunctionCode;
|
||||
import org.qortal.block.Block;
|
||||
import org.qortal.data.at.ATStateData;
|
||||
import org.qortal.data.block.BlockData;
|
||||
import org.qortal.data.transaction.BaseTransactionData;
|
||||
import org.qortal.data.transaction.DeployAtTransactionData;
|
||||
import org.qortal.data.transaction.MessageTransactionData;
|
||||
import org.qortal.data.transaction.TransactionData;
|
||||
import org.qortal.group.Group;
|
||||
import org.qortal.repository.DataException;
|
||||
import org.qortal.repository.Repository;
|
||||
import org.qortal.repository.RepositoryManager;
|
||||
import org.qortal.test.common.BlockUtils;
|
||||
import org.qortal.test.common.Common;
|
||||
import org.qortal.test.common.TransactionUtils;
|
||||
import org.qortal.transaction.DeployAtTransaction;
|
||||
import org.qortal.transaction.MessageTransaction;
|
||||
import org.qortal.transaction.Transaction;
|
||||
import org.qortal.utils.BitTwiddling;
|
||||
|
||||
public class SleepUntilMessageTests extends Common {
|
||||
|
||||
private static final byte[] messageData = new byte[] { 0x44 };
|
||||
private static final byte[] creationBytes = buildSleepUntilMessageAT();
|
||||
private static final long fundingAmount = 1_00000000L;
|
||||
|
||||
private Repository repository = null;
|
||||
private PrivateKeyAccount deployer;
|
||||
private DeployAtTransaction deployAtTransaction;
|
||||
private Account atAccount;
|
||||
private String atAddress;
|
||||
private byte[] rawNextTimestamp = new byte[32];
|
||||
private Transaction transaction;
|
||||
|
||||
@Before
|
||||
public void before() throws DataException {
|
||||
Common.useDefaultSettings();
|
||||
|
||||
this.repository = RepositoryManager.getRepository();
|
||||
this.deployer = Common.getTestAccount(repository, "alice");
|
||||
|
||||
this.deployAtTransaction = doDeploy(repository, deployer, creationBytes, fundingAmount);
|
||||
this.atAccount = deployAtTransaction.getATAccount();
|
||||
this.atAddress = deployAtTransaction.getATAccount().getAddress();
|
||||
}
|
||||
|
||||
@After
|
||||
public void after() throws DataException {
|
||||
if (this.repository != null)
|
||||
this.repository.close();
|
||||
|
||||
this.repository = null;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeploy() throws DataException {
|
||||
// Confirm initial value is zero
|
||||
extractNextTxTimestamp(repository, atAddress, rawNextTimestamp);
|
||||
assertArrayEquals(new byte[32], rawNextTimestamp);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFeelessSleep() throws DataException {
|
||||
// Mint block to allow AT to initialize and call SLEEP_UNTIL_MESSAGE
|
||||
BlockUtils.mintBlock(repository);
|
||||
|
||||
// Fetch AT's balance for this height
|
||||
long preMintBalance = atAccount.getConfirmedBalance(Asset.QORT);
|
||||
|
||||
// Mint block
|
||||
BlockUtils.mintBlock(repository);
|
||||
|
||||
// Fetch new AT balance
|
||||
long postMintBalance = atAccount.getConfirmedBalance(Asset.QORT);
|
||||
|
||||
assertEquals(preMintBalance, postMintBalance);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFeelessSleep2() throws DataException {
|
||||
// Mint block to allow AT to initialize and call SLEEP_UNTIL_MESSAGE
|
||||
BlockUtils.mintBlock(repository);
|
||||
|
||||
// Fetch AT's balance for this height
|
||||
long preMintBalance = atAccount.getConfirmedBalance(Asset.QORT);
|
||||
|
||||
// Mint several blocks
|
||||
for (int i = 0; i < 10; ++i)
|
||||
BlockUtils.mintBlock(repository);
|
||||
|
||||
// Fetch new AT balance
|
||||
long postMintBalance = atAccount.getConfirmedBalance(Asset.QORT);
|
||||
|
||||
assertEquals(preMintBalance, postMintBalance);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSleepUntilMessage() throws DataException {
|
||||
// Mint block to allow AT to initialize and call SLEEP_UNTIL_MESSAGE
|
||||
BlockUtils.mintBlock(repository);
|
||||
|
||||
// Send message to AT
|
||||
transaction = sendMessage(repository, deployer, messageData, atAddress);
|
||||
BlockUtils.mintBlock(repository);
|
||||
|
||||
// Mint block so AT executes and finds message
|
||||
BlockUtils.mintBlock(repository);
|
||||
|
||||
// Confirm AT finds message
|
||||
assertTimestamp(repository, atAddress, transaction);
|
||||
}
|
||||
|
||||
private static byte[] buildSleepUntilMessageAT() {
|
||||
// Labels for data segment addresses
|
||||
int addrCounter = 0;
|
||||
|
||||
// Beginning of data segment for easy extraction
|
||||
final int addrNextTx = addrCounter;
|
||||
addrCounter += 4;
|
||||
|
||||
final int addrNextTxIndex = addrCounter++;
|
||||
|
||||
final int addrLastTxTimestamp = addrCounter++;
|
||||
|
||||
// Data segment
|
||||
ByteBuffer dataByteBuffer = ByteBuffer.allocate(addrCounter * MachineState.VALUE_SIZE);
|
||||
|
||||
// skip addrNextTx
|
||||
dataByteBuffer.position(dataByteBuffer.position() + 4 * MachineState.VALUE_SIZE);
|
||||
|
||||
// Store pointer to addrNextTx at addrNextTxIndex
|
||||
dataByteBuffer.putLong(addrNextTx);
|
||||
|
||||
ByteBuffer codeByteBuffer = ByteBuffer.allocate(512);
|
||||
|
||||
// Two-pass version
|
||||
for (int pass = 0; pass < 2; ++pass) {
|
||||
codeByteBuffer.clear();
|
||||
|
||||
try {
|
||||
/* Initialization */
|
||||
|
||||
// Use AT creation 'timestamp' as starting point for finding transactions sent to AT
|
||||
codeByteBuffer.put(OpCode.EXT_FUN_RET.compile(FunctionCode.GET_CREATION_TIMESTAMP, addrLastTxTimestamp));
|
||||
|
||||
// Set restart position to after this opcode
|
||||
codeByteBuffer.put(OpCode.SET_PCS.compile());
|
||||
|
||||
/* Loop, waiting for message to AT */
|
||||
|
||||
/* Sleep until message arrives */
|
||||
codeByteBuffer.put(OpCode.EXT_FUN_DAT.compile(QortalFunctionCode.SLEEP_UNTIL_MESSAGE.value, addrLastTxTimestamp));
|
||||
|
||||
// Find next transaction to this AT since the last one (if any)
|
||||
codeByteBuffer.put(OpCode.EXT_FUN_DAT.compile(FunctionCode.PUT_TX_AFTER_TIMESTAMP_INTO_A, addrLastTxTimestamp));
|
||||
|
||||
// Copy A to data segment, starting at addrNextTx (as pointed to by addrNextTxIndex)
|
||||
codeByteBuffer.put(OpCode.EXT_FUN_DAT.compile(FunctionCode.GET_A_IND, addrNextTxIndex));
|
||||
|
||||
// Stop if timestamp part of A is zero
|
||||
codeByteBuffer.put(OpCode.STZ_DAT.compile(addrNextTx));
|
||||
|
||||
// Update our 'last found transaction's timestamp' using 'timestamp' from transaction
|
||||
codeByteBuffer.put(OpCode.EXT_FUN_RET.compile(FunctionCode.GET_TIMESTAMP_FROM_TX_IN_A, addrLastTxTimestamp));
|
||||
|
||||
// We're done
|
||||
codeByteBuffer.put(OpCode.FIN_IMD.compile());
|
||||
|
||||
} catch (CompilationException e) {
|
||||
throw new IllegalStateException("Unable to compile AT?", e);
|
||||
}
|
||||
}
|
||||
|
||||
codeByteBuffer.flip();
|
||||
|
||||
byte[] codeBytes = new byte[codeByteBuffer.limit()];
|
||||
codeByteBuffer.get(codeBytes);
|
||||
|
||||
final short ciyamAtVersion = 2;
|
||||
final short numCallStackPages = 0;
|
||||
final short numUserStackPages = 0;
|
||||
final long minActivationAmount = 0L;
|
||||
|
||||
return MachineState.toCreationBytes(ciyamAtVersion, codeBytes, dataByteBuffer.array(), numCallStackPages, numUserStackPages, minActivationAmount);
|
||||
}
|
||||
|
||||
private DeployAtTransaction doDeploy(Repository repository, PrivateKeyAccount deployer, byte[] creationBytes, long fundingAmount) throws DataException {
|
||||
long txTimestamp = System.currentTimeMillis();
|
||||
byte[] lastReference = deployer.getLastReference();
|
||||
|
||||
if (lastReference == null) {
|
||||
System.err.println(String.format("Qortal account %s has no last reference", deployer.getAddress()));
|
||||
System.exit(2);
|
||||
}
|
||||
|
||||
Long fee = null;
|
||||
String name = "Test AT";
|
||||
String description = "Test AT";
|
||||
String atType = "Test";
|
||||
String tags = "TEST";
|
||||
|
||||
BaseTransactionData baseTransactionData = new BaseTransactionData(txTimestamp, Group.NO_GROUP, lastReference, deployer.getPublicKey(), fee, null);
|
||||
TransactionData deployAtTransactionData = new DeployAtTransactionData(baseTransactionData, name, description, atType, tags, creationBytes, fundingAmount, Asset.QORT);
|
||||
|
||||
DeployAtTransaction deployAtTransaction = new DeployAtTransaction(repository, deployAtTransactionData);
|
||||
|
||||
fee = deployAtTransaction.calcRecommendedFee();
|
||||
deployAtTransactionData.setFee(fee);
|
||||
|
||||
TransactionUtils.signAndMint(repository, deployAtTransactionData, deployer);
|
||||
|
||||
return deployAtTransaction;
|
||||
}
|
||||
|
||||
private void extractNextTxTimestamp(Repository repository, String atAddress, byte[] rawNextTimestamp) throws DataException {
|
||||
// Check AT result
|
||||
ATStateData atStateData = repository.getATRepository().getLatestATState(atAddress);
|
||||
byte[] stateData = atStateData.getStateData();
|
||||
|
||||
byte[] dataBytes = MachineState.extractDataBytes(stateData);
|
||||
|
||||
System.arraycopy(dataBytes, 0, rawNextTimestamp, 0, rawNextTimestamp.length);
|
||||
}
|
||||
|
||||
private MessageTransaction sendMessage(Repository repository, PrivateKeyAccount sender, byte[] data, String recipient) throws DataException {
|
||||
long txTimestamp = System.currentTimeMillis();
|
||||
byte[] lastReference = sender.getLastReference();
|
||||
|
||||
if (lastReference == null) {
|
||||
System.err.println(String.format("Qortal account %s has no last reference", sender.getAddress()));
|
||||
System.exit(2);
|
||||
}
|
||||
|
||||
Long fee = null;
|
||||
int version = 4;
|
||||
int nonce = 0;
|
||||
long amount = 0;
|
||||
Long assetId = null; // because amount is zero
|
||||
|
||||
BaseTransactionData baseTransactionData = new BaseTransactionData(txTimestamp, Group.NO_GROUP, lastReference, sender.getPublicKey(), fee, null);
|
||||
TransactionData messageTransactionData = new MessageTransactionData(baseTransactionData, version, nonce, recipient, amount, assetId, data, false, false);
|
||||
|
||||
MessageTransaction messageTransaction = new MessageTransaction(repository, messageTransactionData);
|
||||
|
||||
fee = messageTransaction.calcRecommendedFee();
|
||||
messageTransactionData.setFee(fee);
|
||||
|
||||
TransactionUtils.signAndImportValid(repository, messageTransactionData, sender);
|
||||
|
||||
return messageTransaction;
|
||||
}
|
||||
|
||||
private void assertTimestamp(Repository repository, String atAddress, Transaction transaction) throws DataException {
|
||||
int height = transaction.getHeight();
|
||||
byte[] transactionSignature = transaction.getTransactionData().getSignature();
|
||||
|
||||
BlockData blockData = repository.getBlockRepository().fromHeight(height);
|
||||
assertNotNull(blockData);
|
||||
|
||||
Block block = new Block(repository, blockData);
|
||||
|
||||
List<Transaction> blockTransactions = block.getTransactions();
|
||||
int sequence;
|
||||
for (sequence = blockTransactions.size() - 1; sequence >= 0; --sequence)
|
||||
if (Arrays.equals(blockTransactions.get(sequence).getTransactionData().getSignature(), transactionSignature))
|
||||
break;
|
||||
|
||||
assertNotSame(-1, sequence);
|
||||
|
||||
byte[] rawNextTimestamp = new byte[32];
|
||||
extractNextTxTimestamp(repository, atAddress, rawNextTimestamp);
|
||||
|
||||
Timestamp expectedTimestamp = new Timestamp(height, sequence);
|
||||
Timestamp actualTimestamp = new Timestamp(BitTwiddling.longFromBEBytes(rawNextTimestamp, 0));
|
||||
|
||||
assertEquals(String.format("Expected height %d, seq %d but was height %d, seq %d",
|
||||
height, sequence,
|
||||
actualTimestamp.blockHeight, actualTimestamp.transactionSequence
|
||||
),
|
||||
expectedTimestamp.longValue(),
|
||||
actualTimestamp.longValue());
|
||||
|
||||
byte[] expectedPartialSignature = new byte[24];
|
||||
System.arraycopy(transactionSignature, 8, expectedPartialSignature, 0, expectedPartialSignature.length);
|
||||
|
||||
byte[] actualPartialSignature = new byte[24];
|
||||
System.arraycopy(rawNextTimestamp, 8, actualPartialSignature, 0, actualPartialSignature.length);
|
||||
|
||||
assertArrayEquals(expectedPartialSignature, actualPartialSignature);
|
||||
}
|
||||
|
||||
}
|
81
src/test/java/org/qortal/test/common/AtUtils.java
Normal file
81
src/test/java/org/qortal/test/common/AtUtils.java
Normal file
@@ -0,0 +1,81 @@
|
||||
package org.qortal.test.common;
|
||||
|
||||
import org.ciyam.at.CompilationException;
|
||||
import org.ciyam.at.MachineState;
|
||||
import org.ciyam.at.OpCode;
|
||||
import org.qortal.account.PrivateKeyAccount;
|
||||
import org.qortal.asset.Asset;
|
||||
import org.qortal.data.transaction.BaseTransactionData;
|
||||
import org.qortal.data.transaction.DeployAtTransactionData;
|
||||
import org.qortal.data.transaction.TransactionData;
|
||||
import org.qortal.group.Group;
|
||||
import org.qortal.repository.DataException;
|
||||
import org.qortal.repository.Repository;
|
||||
import org.qortal.transaction.DeployAtTransaction;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public class AtUtils {
|
||||
|
||||
public static byte[] buildSimpleAT() {
|
||||
// Pretend we use 4 values in data segment
|
||||
int addrCounter = 4;
|
||||
|
||||
// Data segment
|
||||
ByteBuffer dataByteBuffer = ByteBuffer.allocate(addrCounter * MachineState.VALUE_SIZE);
|
||||
|
||||
ByteBuffer codeByteBuffer = ByteBuffer.allocate(512);
|
||||
|
||||
// Two-pass version
|
||||
for (int pass = 0; pass < 2; ++pass) {
|
||||
codeByteBuffer.clear();
|
||||
|
||||
try {
|
||||
// Stop and wait for next block
|
||||
codeByteBuffer.put(OpCode.STP_IMD.compile());
|
||||
} catch (CompilationException e) {
|
||||
throw new IllegalStateException("Unable to compile AT?", e);
|
||||
}
|
||||
}
|
||||
|
||||
codeByteBuffer.flip();
|
||||
|
||||
byte[] codeBytes = new byte[codeByteBuffer.limit()];
|
||||
codeByteBuffer.get(codeBytes);
|
||||
|
||||
final short ciyamAtVersion = 2;
|
||||
final short numCallStackPages = 0;
|
||||
final short numUserStackPages = 0;
|
||||
final long minActivationAmount = 0L;
|
||||
|
||||
return MachineState.toCreationBytes(ciyamAtVersion, codeBytes, dataByteBuffer.array(), numCallStackPages, numUserStackPages, minActivationAmount);
|
||||
}
|
||||
|
||||
public static DeployAtTransaction doDeployAT(Repository repository, PrivateKeyAccount deployer, byte[] creationBytes, long fundingAmount) throws DataException {
|
||||
long txTimestamp = System.currentTimeMillis();
|
||||
byte[] lastReference = deployer.getLastReference();
|
||||
|
||||
if (lastReference == null) {
|
||||
System.err.println(String.format("Qortal account %s has no last reference", deployer.getAddress()));
|
||||
System.exit(2);
|
||||
}
|
||||
|
||||
Long fee = null;
|
||||
String name = "Test AT";
|
||||
String description = "Test AT";
|
||||
String atType = "Test";
|
||||
String tags = "TEST";
|
||||
|
||||
BaseTransactionData baseTransactionData = new BaseTransactionData(txTimestamp, Group.NO_GROUP, lastReference, deployer.getPublicKey(), fee, null);
|
||||
TransactionData deployAtTransactionData = new DeployAtTransactionData(baseTransactionData, name, description, atType, tags, creationBytes, fundingAmount, Asset.QORT);
|
||||
|
||||
DeployAtTransaction deployAtTransaction = new DeployAtTransaction(repository, deployAtTransactionData);
|
||||
|
||||
fee = deployAtTransaction.calcRecommendedFee();
|
||||
deployAtTransactionData.setFee(fee);
|
||||
|
||||
TransactionUtils.signAndMint(repository, deployAtTransactionData, deployer);
|
||||
|
||||
return deployAtTransaction;
|
||||
}
|
||||
}
|
@@ -2,8 +2,11 @@ package org.qortal.test.common;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.math.BigDecimal;
|
||||
import java.net.URL;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.security.Security;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
@@ -15,6 +18,7 @@ import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||
@@ -46,9 +50,15 @@ public class Common {
|
||||
|
||||
private static final Logger LOGGER = LogManager.getLogger(Common.class);
|
||||
|
||||
public static final String testConnectionUrl = "jdbc:hsqldb:mem:testdb";
|
||||
// For debugging, use this instead to write DB to disk for examination:
|
||||
// public static final String testConnectionUrl = "jdbc:hsqldb:file:testdb/blockchain;create=true";
|
||||
public static final String testConnectionUrlMemory = "jdbc:hsqldb:mem:testdb";
|
||||
public static final String testConnectionUrlDisk = "jdbc:hsqldb:file:%s/blockchain;create=true";
|
||||
|
||||
// For debugging, use testConnectionUrlDisk instead of memory, to write DB to disk for examination.
|
||||
// This can be achieved using `Common.useSettingsAndDb(Common.testSettingsFilename, false);`
|
||||
// where `false` specifies to use a repository on disk rather than one in memory.
|
||||
// Make sure to also comment out `Common.deleteTestRepository();` in closeRepository() below, so that
|
||||
// the files remain after the test finishes.
|
||||
|
||||
|
||||
public static final String testSettingsFilename = "test-settings-v2.json";
|
||||
|
||||
@@ -100,7 +110,7 @@ public class Common {
|
||||
return testAccountsByName.values().stream().map(account -> new TestAccount(repository, account)).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public static void useSettings(String settingsFilename) throws DataException {
|
||||
public static void useSettingsAndDb(String settingsFilename, boolean dbInMemory) throws DataException {
|
||||
closeRepository();
|
||||
|
||||
// Load/check settings, which potentially sets up blockchain config, etc.
|
||||
@@ -109,11 +119,15 @@ public class Common {
|
||||
assertNotNull("Test settings JSON file not found", testSettingsUrl);
|
||||
Settings.fileInstance(testSettingsUrl.getPath());
|
||||
|
||||
setRepository();
|
||||
setRepository(dbInMemory);
|
||||
|
||||
resetBlockchain();
|
||||
}
|
||||
|
||||
public static void useSettings(String settingsFilename) throws DataException {
|
||||
Common.useSettingsAndDb(settingsFilename, true);
|
||||
}
|
||||
|
||||
public static void useDefaultSettings() throws DataException {
|
||||
useSettings(testSettingsFilename);
|
||||
NTP.setFixedOffset(Settings.getInstance().getTestNtpOffset());
|
||||
@@ -186,15 +200,33 @@ public class Common {
|
||||
assertTrue(String.format("Non-genesis %s remains", typeName), remainingClone.isEmpty());
|
||||
}
|
||||
|
||||
@BeforeClass
|
||||
public static void setRepository() throws DataException {
|
||||
RepositoryFactory repositoryFactory = new HSQLDBRepositoryFactory(testConnectionUrl);
|
||||
public static void setRepository(boolean inMemory) throws DataException {
|
||||
String connectionUrlDisk = String.format(testConnectionUrlDisk, Settings.getInstance().getRepositoryPath());
|
||||
String connectionUrl = inMemory ? testConnectionUrlMemory : connectionUrlDisk;
|
||||
RepositoryFactory repositoryFactory = new HSQLDBRepositoryFactory(connectionUrl);
|
||||
RepositoryManager.setRepositoryFactory(repositoryFactory);
|
||||
}
|
||||
|
||||
public static void deleteTestRepository() throws DataException {
|
||||
// Delete repository directory if exists
|
||||
Path repositoryPath = Paths.get(Settings.getInstance().getRepositoryPath());
|
||||
try {
|
||||
FileUtils.deleteDirectory(repositoryPath.toFile());
|
||||
} catch (IOException e) {
|
||||
throw new DataException(String.format("Unable to delete test repository: %s", e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
@BeforeClass
|
||||
public static void setRepositoryInMemory() throws DataException {
|
||||
Common.deleteTestRepository();
|
||||
Common.setRepository(true);
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void closeRepository() throws DataException {
|
||||
RepositoryManager.closeRepositoryFactory();
|
||||
Common.deleteTestRepository(); // Comment out this line in you need to inspect the database after running a test
|
||||
}
|
||||
|
||||
// Test assertions
|
||||
|
@@ -35,10 +35,10 @@ public class DogecoinTests extends Common {
|
||||
@Test
|
||||
public void testGetMedianBlockTime() throws BlockStoreException, ForeignBlockchainException {
|
||||
long before = System.currentTimeMillis();
|
||||
System.out.println(String.format("Bitcoin median blocktime: %d", dogecoin.getMedianBlockTime()));
|
||||
System.out.println(String.format("Dogecoin median blocktime: %d", dogecoin.getMedianBlockTime()));
|
||||
long afterFirst = System.currentTimeMillis();
|
||||
|
||||
System.out.println(String.format("Bitcoin median blocktime: %d", dogecoin.getMedianBlockTime()));
|
||||
System.out.println(String.format("Dogecoin median blocktime: %d", dogecoin.getMedianBlockTime()));
|
||||
long afterSecond = System.currentTimeMillis();
|
||||
|
||||
long firstPeriod = afterFirst - before;
|
||||
@@ -64,10 +64,11 @@ public class DogecoinTests extends Common {
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore(value = "No testnet nodes available, so we can't regularly test buildSpend yet")
|
||||
public void testBuildSpend() {
|
||||
String xprv58 = "tprv8ZgxMBicQKsPdahhFSrCdvC1bsWyzHHZfTneTVqUXN6s1wEtZLwAkZXzFP6TYLg2aQMecZLXLre5bTVGajEB55L1HYJcawpdFG66STVAWPJ";
|
||||
String xprv58 = "dgpv51eADS3spNJh9drNeW1Tc1P9z2LyaQRXPBortsq6yice1k47C2u2Prvgxycr2ihNBWzKZ2LthcBBGiYkWZ69KUTVkcLVbnjq7pD8mnApEru";
|
||||
|
||||
String recipient = "2N8WCg52ULCtDSMjkgVTm5mtPdCsUptkHWE";
|
||||
String recipient = "DP1iFao33xdEPa5vaArpj7sykfzKNeiJeX";
|
||||
long amount = 1000L;
|
||||
|
||||
Transaction transaction = dogecoin.buildSpend(xprv58, recipient, amount);
|
||||
@@ -81,7 +82,7 @@ public class DogecoinTests extends Common {
|
||||
|
||||
@Test
|
||||
public void testGetWalletBalance() {
|
||||
String xprv58 = "tprv8ZgxMBicQKsPdahhFSrCdvC1bsWyzHHZfTneTVqUXN6s1wEtZLwAkZXzFP6TYLg2aQMecZLXLre5bTVGajEB55L1HYJcawpdFG66STVAWPJ";
|
||||
String xprv58 = "dgpv51eADS3spNJh9drNeW1Tc1P9z2LyaQRXPBortsq6yice1k47C2u2Prvgxycr2ihNBWzKZ2LthcBBGiYkWZ69KUTVkcLVbnjq7pD8mnApEru";
|
||||
|
||||
Long balance = dogecoin.getWalletBalance(xprv58);
|
||||
|
||||
@@ -102,7 +103,7 @@ public class DogecoinTests extends Common {
|
||||
|
||||
@Test
|
||||
public void testGetUnusedReceiveAddress() throws ForeignBlockchainException {
|
||||
String xprv58 = "tprv8ZgxMBicQKsPdahhFSrCdvC1bsWyzHHZfTneTVqUXN6s1wEtZLwAkZXzFP6TYLg2aQMecZLXLre5bTVGajEB55L1HYJcawpdFG66STVAWPJ";
|
||||
String xprv58 = "dgpv51eADS3spNJh9drNeW1Tc1P9z2LyaQRXPBortsq6yice1k47C2u2Prvgxycr2ihNBWzKZ2LthcBBGiYkWZ69KUTVkcLVbnjq7pD8mnApEru";
|
||||
|
||||
String address = dogecoin.getUnusedReceiveAddress(xprv58);
|
||||
|
||||
|
345
src/test/java/org/qortal/test/naming/IntegrityTests.java
Normal file
345
src/test/java/org/qortal/test/naming/IntegrityTests.java
Normal file
@@ -0,0 +1,345 @@
|
||||
package org.qortal.test.naming;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.qortal.account.PrivateKeyAccount;
|
||||
import org.qortal.controller.repository.NamesDatabaseIntegrityCheck;
|
||||
import org.qortal.data.transaction.*;
|
||||
import org.qortal.repository.DataException;
|
||||
import org.qortal.repository.Repository;
|
||||
import org.qortal.repository.RepositoryManager;
|
||||
import org.qortal.test.common.Common;
|
||||
import org.qortal.test.common.TransactionUtils;
|
||||
import org.qortal.test.common.transaction.TestTransaction;
|
||||
import org.qortal.transaction.Transaction;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
public class IntegrityTests extends Common {
|
||||
|
||||
@Before
|
||||
public void beforeTest() throws DataException {
|
||||
Common.useDefaultSettings();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidName() throws DataException {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
// Register-name
|
||||
PrivateKeyAccount alice = Common.getTestAccount(repository, "alice");
|
||||
String name = "test-name";
|
||||
String data = "{\"age\":30}";
|
||||
|
||||
RegisterNameTransactionData transactionData = new RegisterNameTransactionData(TestTransaction.generateBase(alice), name, data);
|
||||
TransactionUtils.signAndMint(repository, transactionData, alice);
|
||||
|
||||
// Ensure the name exists and the data is correct
|
||||
assertEquals(data, repository.getNameRepository().fromName(name).getData());
|
||||
|
||||
// Run the database integrity check for this name
|
||||
NamesDatabaseIntegrityCheck integrityCheck = new NamesDatabaseIntegrityCheck();
|
||||
assertEquals(1, integrityCheck.rebuildName(name, repository));
|
||||
|
||||
// Ensure the name still exists and the data is still correct
|
||||
assertEquals(data, repository.getNameRepository().fromName(name).getData());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMissingName() throws DataException {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
// Register-name
|
||||
PrivateKeyAccount alice = Common.getTestAccount(repository, "alice");
|
||||
String name = "test-name";
|
||||
String data = "{\"age\":30}";
|
||||
|
||||
RegisterNameTransactionData transactionData = new RegisterNameTransactionData(TestTransaction.generateBase(alice), name, data);
|
||||
TransactionUtils.signAndMint(repository, transactionData, alice);
|
||||
|
||||
// Ensure the name exists and the data is correct
|
||||
assertEquals(data, repository.getNameRepository().fromName(name).getData());
|
||||
|
||||
// Now delete the name, to simulate a database inconsistency
|
||||
repository.getNameRepository().delete(name);
|
||||
|
||||
// Ensure the name doesn't exist
|
||||
assertNull(repository.getNameRepository().fromName(name));
|
||||
|
||||
// Run the database integrity check for this name and check that a row was modified
|
||||
NamesDatabaseIntegrityCheck integrityCheck = new NamesDatabaseIntegrityCheck();
|
||||
assertEquals(1, integrityCheck.rebuildName(name, repository));
|
||||
|
||||
// Ensure the name exists again and the data is correct
|
||||
assertEquals(data, repository.getNameRepository().fromName(name).getData());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMissingNameAfterUpdate() throws DataException {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
// Register-name
|
||||
PrivateKeyAccount alice = Common.getTestAccount(repository, "alice");
|
||||
String name = "test-name";
|
||||
String data = "{\"age\":30}";
|
||||
|
||||
RegisterNameTransactionData transactionData = new RegisterNameTransactionData(TestTransaction.generateBase(alice), name, data);
|
||||
TransactionUtils.signAndMint(repository, transactionData, alice);
|
||||
|
||||
// Ensure the name exists and the data is correct
|
||||
assertEquals(data, repository.getNameRepository().fromName(name).getData());
|
||||
|
||||
// Update the name
|
||||
String newData = "{\"age\":31}";
|
||||
UpdateNameTransactionData updateTransactionData = new UpdateNameTransactionData(TestTransaction.generateBase(alice), name, name, newData);
|
||||
TransactionUtils.signAndMint(repository, updateTransactionData, alice);
|
||||
|
||||
// Ensure the name still exists and the data has been updated
|
||||
assertEquals(newData, repository.getNameRepository().fromName(name).getData());
|
||||
|
||||
// Now delete the name, to simulate a database inconsistency
|
||||
repository.getNameRepository().delete(name);
|
||||
|
||||
// Ensure the name doesn't exist
|
||||
assertNull(repository.getNameRepository().fromName(name));
|
||||
|
||||
// Run the database integrity check for this name
|
||||
// We expect 2 modifications to be made - the original register name followed by the update
|
||||
NamesDatabaseIntegrityCheck integrityCheck = new NamesDatabaseIntegrityCheck();
|
||||
assertEquals(2, integrityCheck.rebuildName(name, repository));
|
||||
|
||||
// Ensure the name exists and the data is correct
|
||||
assertEquals(newData, repository.getNameRepository().fromName(name).getData());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMissingNameAfterRename() throws DataException {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
// Register-name
|
||||
PrivateKeyAccount alice = Common.getTestAccount(repository, "alice");
|
||||
String name = "test-name";
|
||||
String data = "{\"age\":30}";
|
||||
|
||||
RegisterNameTransactionData transactionData = new RegisterNameTransactionData(TestTransaction.generateBase(alice), name, data);
|
||||
TransactionUtils.signAndMint(repository, transactionData, alice);
|
||||
|
||||
// Ensure the name exists and the data is correct
|
||||
assertEquals(data, repository.getNameRepository().fromName(name).getData());
|
||||
|
||||
// Rename the name
|
||||
String newName = "new-name";
|
||||
String newData = "{\"age\":31}";
|
||||
UpdateNameTransactionData updateTransactionData = new UpdateNameTransactionData(TestTransaction.generateBase(alice), name, newName, newData);
|
||||
TransactionUtils.signAndMint(repository, updateTransactionData, alice);
|
||||
|
||||
// Ensure the new name exists and the data has been updated
|
||||
assertEquals(newData, repository.getNameRepository().fromName(newName).getData());
|
||||
|
||||
// Ensure the old name doesn't exist
|
||||
assertNull(repository.getNameRepository().fromName(name));
|
||||
|
||||
// Now delete the new name, to simulate a database inconsistency
|
||||
repository.getNameRepository().delete(newName);
|
||||
|
||||
// Ensure the new name doesn't exist
|
||||
assertNull(repository.getNameRepository().fromName(newName));
|
||||
|
||||
// Attempt to register the new name
|
||||
transactionData = new RegisterNameTransactionData(TestTransaction.generateBase(alice), newName, data);
|
||||
Transaction transaction = Transaction.fromData(repository, transactionData);
|
||||
transaction.sign(alice);
|
||||
|
||||
// Transaction should be invalid, because the database inconsistency was fixed by RegisterNameTransaction.preProcess()
|
||||
Transaction.ValidationResult result = transaction.importAsUnconfirmed();
|
||||
assertTrue("Transaction should be invalid", Transaction.ValidationResult.OK != result);
|
||||
assertTrue("Name should already be registered", Transaction.ValidationResult.NAME_ALREADY_REGISTERED == result);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRegisterMissingName() throws DataException {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
// Register-name
|
||||
PrivateKeyAccount alice = Common.getTestAccount(repository, "alice");
|
||||
String name = "test-name";
|
||||
String data = "{\"age\":30}";
|
||||
|
||||
RegisterNameTransactionData transactionData = new RegisterNameTransactionData(TestTransaction.generateBase(alice), name, data);
|
||||
TransactionUtils.signAndMint(repository, transactionData, alice);
|
||||
|
||||
// Ensure the name exists and the data is correct
|
||||
assertEquals(data, repository.getNameRepository().fromName(name).getData());
|
||||
|
||||
// Now delete the name, to simulate a database inconsistency
|
||||
repository.getNameRepository().delete(name);
|
||||
|
||||
// Ensure the name doesn't exist
|
||||
assertNull(repository.getNameRepository().fromName(name));
|
||||
|
||||
// Attempt to register the name again
|
||||
String duplicateName = "TEST-nÁme";
|
||||
transactionData = new RegisterNameTransactionData(TestTransaction.generateBase(alice), duplicateName, data);
|
||||
Transaction transaction = Transaction.fromData(repository, transactionData);
|
||||
transaction.sign(alice);
|
||||
|
||||
// Transaction should be invalid, because the database inconsistency was fixed by RegisterNameTransaction.preProcess()
|
||||
Transaction.ValidationResult result = transaction.importAsUnconfirmed();
|
||||
assertTrue("Transaction should be invalid", Transaction.ValidationResult.OK != result);
|
||||
assertTrue("Name should already be registered", Transaction.ValidationResult.NAME_ALREADY_REGISTERED == result);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateMissingName() throws DataException {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
// Register-name
|
||||
PrivateKeyAccount alice = Common.getTestAccount(repository, "alice");
|
||||
String initialName = "test-name";
|
||||
String data = "{\"age\":30}";
|
||||
|
||||
RegisterNameTransactionData transactionData = new RegisterNameTransactionData(TestTransaction.generateBase(alice), initialName, data);
|
||||
TransactionUtils.signAndMint(repository, transactionData, alice);
|
||||
|
||||
// Ensure the name exists and the data is correct
|
||||
assertEquals(data, repository.getNameRepository().fromName(initialName).getData());
|
||||
|
||||
// Now delete the name, to simulate a database inconsistency
|
||||
repository.getNameRepository().delete(initialName);
|
||||
|
||||
// Ensure the name doesn't exist
|
||||
assertNull(repository.getNameRepository().fromName(initialName));
|
||||
|
||||
// Attempt to update the name
|
||||
String newName = "new-name";
|
||||
String newData = "";
|
||||
TransactionData updateTransactionData = new UpdateNameTransactionData(TestTransaction.generateBase(alice), initialName, newName, newData);
|
||||
Transaction transaction = Transaction.fromData(repository, updateTransactionData);
|
||||
transaction.sign(alice);
|
||||
|
||||
// Transaction should be valid, because the database inconsistency was fixed by UpdateNameTransaction.preProcess()
|
||||
Transaction.ValidationResult result = transaction.importAsUnconfirmed();
|
||||
assertTrue("Transaction should be valid", Transaction.ValidationResult.OK == result);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateToMissingName() throws DataException {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
// Register-name
|
||||
PrivateKeyAccount alice = Common.getTestAccount(repository, "alice");
|
||||
String initialName = "test-name";
|
||||
String data = "{\"age\":30}";
|
||||
|
||||
RegisterNameTransactionData transactionData = new RegisterNameTransactionData(TestTransaction.generateBase(alice), initialName, data);
|
||||
TransactionUtils.signAndMint(repository, transactionData, alice);
|
||||
|
||||
// Ensure the name exists and the data is correct
|
||||
assertEquals(data, repository.getNameRepository().fromName(initialName).getData());
|
||||
|
||||
// Register the second name that we will ultimately try and rename the first name to
|
||||
String secondName = "new-missing-name";
|
||||
String secondNameData = "{\"data2\":true}";
|
||||
transactionData = new RegisterNameTransactionData(TestTransaction.generateBase(alice), secondName, secondNameData);
|
||||
TransactionUtils.signAndMint(repository, transactionData, alice);
|
||||
|
||||
// Ensure the second name exists and the data is correct
|
||||
assertEquals(secondNameData, repository.getNameRepository().fromName(secondName).getData());
|
||||
|
||||
// Now delete the second name, to simulate a database inconsistency
|
||||
repository.getNameRepository().delete(secondName);
|
||||
|
||||
// Ensure the second name doesn't exist
|
||||
assertNull(repository.getNameRepository().fromName(secondName));
|
||||
|
||||
// Attempt to rename the first name to the second name
|
||||
TransactionData updateTransactionData = new UpdateNameTransactionData(TestTransaction.generateBase(alice), initialName, secondName, secondNameData);
|
||||
Transaction transaction = Transaction.fromData(repository, updateTransactionData);
|
||||
transaction.sign(alice);
|
||||
|
||||
// Transaction should be invalid, because the database inconsistency was fixed by UpdateNameTransaction.preProcess()
|
||||
// Therefore the name that we are trying to rename TO already exists
|
||||
Transaction.ValidationResult result = transaction.importAsUnconfirmed();
|
||||
assertTrue("Transaction should be invalid", Transaction.ValidationResult.OK != result);
|
||||
assertTrue("Destination name should already exist", Transaction.ValidationResult.NAME_ALREADY_REGISTERED == result);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSellMissingName() throws DataException {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
// Register-name
|
||||
PrivateKeyAccount alice = Common.getTestAccount(repository, "alice");
|
||||
String name = "test-name";
|
||||
String data = "{\"age\":30}";
|
||||
|
||||
RegisterNameTransactionData transactionData = new RegisterNameTransactionData(TestTransaction.generateBase(alice), name, data);
|
||||
TransactionUtils.signAndMint(repository, transactionData, alice);
|
||||
|
||||
// Ensure the name exists and the data is correct
|
||||
assertEquals(data, repository.getNameRepository().fromName(name).getData());
|
||||
|
||||
// Now delete the name, to simulate a database inconsistency
|
||||
repository.getNameRepository().delete(name);
|
||||
|
||||
// Ensure the name doesn't exist
|
||||
assertNull(repository.getNameRepository().fromName(name));
|
||||
|
||||
// Attempt to sell the name
|
||||
TransactionData sellTransactionData = new SellNameTransactionData(TestTransaction.generateBase(alice), name, 123456);
|
||||
Transaction transaction = Transaction.fromData(repository, sellTransactionData);
|
||||
transaction.sign(alice);
|
||||
|
||||
// Transaction should be valid, because the database inconsistency was fixed by SellNameTransaction.preProcess()
|
||||
Transaction.ValidationResult result = transaction.importAsUnconfirmed();
|
||||
assertTrue("Transaction should be valid", Transaction.ValidationResult.OK == result);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBuyMissingName() throws DataException {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
// Register-name
|
||||
PrivateKeyAccount alice = Common.getTestAccount(repository, "alice");
|
||||
String name = "test-name";
|
||||
String data = "{\"age\":30}";
|
||||
|
||||
RegisterNameTransactionData transactionData = new RegisterNameTransactionData(TestTransaction.generateBase(alice), name, data);
|
||||
TransactionUtils.signAndMint(repository, transactionData, alice);
|
||||
|
||||
// Ensure the name exists and the data is correct
|
||||
assertEquals(data, repository.getNameRepository().fromName(name).getData());
|
||||
|
||||
// Now delete the name, to simulate a database inconsistency
|
||||
repository.getNameRepository().delete(name);
|
||||
|
||||
// Ensure the name doesn't exist
|
||||
assertNull(repository.getNameRepository().fromName(name));
|
||||
|
||||
// Attempt to sell the name
|
||||
long amount = 123456;
|
||||
TransactionData sellTransactionData = new SellNameTransactionData(TestTransaction.generateBase(alice), name, amount);
|
||||
TransactionUtils.signAndMint(repository, sellTransactionData, alice);
|
||||
|
||||
// Ensure the name now exists
|
||||
assertNotNull(repository.getNameRepository().fromName(name));
|
||||
|
||||
// Now delete the name again, to simulate another database inconsistency
|
||||
repository.getNameRepository().delete(name);
|
||||
|
||||
// Ensure the name doesn't exist
|
||||
assertNull(repository.getNameRepository().fromName(name));
|
||||
|
||||
// Bob now attempts to buy the name
|
||||
String seller = alice.getAddress();
|
||||
PrivateKeyAccount bob = Common.getTestAccount(repository, "bob");
|
||||
TransactionData buyTransactionData = new BuyNameTransactionData(TestTransaction.generateBase(bob), name, amount, seller);
|
||||
Transaction transaction = Transaction.fromData(repository, buyTransactionData);
|
||||
transaction.sign(bob);
|
||||
|
||||
// Transaction should be valid, because the database inconsistency was fixed by SellNameTransaction.preProcess()
|
||||
Transaction.ValidationResult result = transaction.importAsUnconfirmed();
|
||||
assertTrue("Transaction should be valid", Transaction.ValidationResult.OK == result);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -7,14 +7,13 @@ import java.util.List;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.qortal.account.PrivateKeyAccount;
|
||||
import org.qortal.data.transaction.RegisterNameTransactionData;
|
||||
import org.qortal.data.transaction.TransactionData;
|
||||
import org.qortal.data.transaction.UpdateNameTransactionData;
|
||||
import org.qortal.controller.BlockMinter;
|
||||
import org.qortal.data.transaction.*;
|
||||
import org.qortal.naming.Name;
|
||||
import org.qortal.repository.DataException;
|
||||
import org.qortal.repository.Repository;
|
||||
import org.qortal.repository.RepositoryManager;
|
||||
import org.qortal.test.common.Common;
|
||||
import org.qortal.test.common.TransactionUtils;
|
||||
import org.qortal.test.common.*;
|
||||
import org.qortal.test.common.transaction.TestTransaction;
|
||||
import org.qortal.transaction.Transaction;
|
||||
import org.qortal.transaction.Transaction.ValidationResult;
|
||||
@@ -32,7 +31,7 @@ public class MiscTests extends Common {
|
||||
// Register-name
|
||||
PrivateKeyAccount alice = Common.getTestAccount(repository, "alice");
|
||||
String name = "initial-name";
|
||||
String data = "initial-data";
|
||||
String data = "{\"age\":30}";
|
||||
|
||||
RegisterNameTransactionData transactionData = new RegisterNameTransactionData(TestTransaction.generateBase(alice), name, data);
|
||||
TransactionUtils.signAndMint(repository, transactionData, alice);
|
||||
@@ -51,7 +50,7 @@ public class MiscTests extends Common {
|
||||
// Register-name
|
||||
PrivateKeyAccount alice = Common.getTestAccount(repository, "alice");
|
||||
String name = "test-name";
|
||||
String data = "{}";
|
||||
String data = "{\"age\":30}";
|
||||
|
||||
RegisterNameTransactionData transactionData = new RegisterNameTransactionData(TestTransaction.generateBase(alice), name, data);
|
||||
TransactionUtils.signAndMint(repository, transactionData, alice);
|
||||
@@ -67,6 +66,30 @@ public class MiscTests extends Common {
|
||||
}
|
||||
}
|
||||
|
||||
// test trying to register same name twice (with different creator)
|
||||
@Test
|
||||
public void testDuplicateRegisterNameWithDifferentCreator() throws DataException {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
// Register-name
|
||||
PrivateKeyAccount alice = Common.getTestAccount(repository, "alice");
|
||||
String name = "test-name";
|
||||
String data = "{}";
|
||||
|
||||
RegisterNameTransactionData transactionData = new RegisterNameTransactionData(TestTransaction.generateBase(alice), name, data);
|
||||
TransactionUtils.signAndMint(repository, transactionData, alice);
|
||||
|
||||
// duplicate (this time registered by Bob)
|
||||
PrivateKeyAccount bob = Common.getTestAccount(repository, "bob");
|
||||
String duplicateName = "TEST-nÁme";
|
||||
transactionData = new RegisterNameTransactionData(TestTransaction.generateBase(bob), duplicateName, data);
|
||||
Transaction transaction = Transaction.fromData(repository, transactionData);
|
||||
transaction.sign(alice);
|
||||
|
||||
ValidationResult result = transaction.importAsUnconfirmed();
|
||||
assertTrue("Transaction should be invalid", ValidationResult.OK != result);
|
||||
}
|
||||
}
|
||||
|
||||
// test register then trying to update another name to existing name
|
||||
@Test
|
||||
public void testUpdateToExistingName() throws DataException {
|
||||
@@ -74,7 +97,7 @@ public class MiscTests extends Common {
|
||||
// Register-name
|
||||
PrivateKeyAccount alice = Common.getTestAccount(repository, "alice");
|
||||
String name = "test-name";
|
||||
String data = "{}";
|
||||
String data = "{\"age\":30}";
|
||||
|
||||
TransactionData transactionData = new RegisterNameTransactionData(TestTransaction.generateBase(alice), name, data);
|
||||
TransactionUtils.signAndMint(repository, transactionData, alice);
|
||||
@@ -103,7 +126,7 @@ public class MiscTests extends Common {
|
||||
// Register-name
|
||||
PrivateKeyAccount alice = Common.getTestAccount(repository, "alice");
|
||||
String name = alice.getAddress();
|
||||
String data = "{}";
|
||||
String data = "{\"age\":30}";
|
||||
|
||||
RegisterNameTransactionData transactionData = new RegisterNameTransactionData(TestTransaction.generateBase(alice), name, data);
|
||||
Transaction transaction = Transaction.fromData(repository, transactionData);
|
||||
@@ -121,7 +144,7 @@ public class MiscTests extends Common {
|
||||
// Register-name
|
||||
PrivateKeyAccount alice = Common.getTestAccount(repository, "alice");
|
||||
String name = "test-name";
|
||||
String data = "{}";
|
||||
String data = "{\"age\":30}";
|
||||
|
||||
TransactionData transactionData = new RegisterNameTransactionData(TestTransaction.generateBase(alice), name, data);
|
||||
TransactionUtils.signAndMint(repository, transactionData, alice);
|
||||
@@ -138,4 +161,147 @@ public class MiscTests extends Common {
|
||||
}
|
||||
}
|
||||
|
||||
// test registering and then orphaning
|
||||
@Test
|
||||
public void testRegisterNameAndOrphan() throws DataException {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
|
||||
PrivateKeyAccount alice = Common.getTestAccount(repository, "alice");
|
||||
String name = "test-name";
|
||||
String data = "{\"age\":30}";
|
||||
|
||||
// Ensure the name doesn't exist
|
||||
assertNull(repository.getNameRepository().fromName(name));
|
||||
|
||||
// Register the name
|
||||
RegisterNameTransactionData transactionData = new RegisterNameTransactionData(TestTransaction.generateBase(alice), name, data);
|
||||
TransactionUtils.signAndMint(repository, transactionData, alice);
|
||||
|
||||
// Ensure the name exists and the data is correct
|
||||
assertEquals(data, repository.getNameRepository().fromName(name).getData());
|
||||
|
||||
// Orphan the latest block
|
||||
BlockUtils.orphanBlocks(repository, 1);
|
||||
|
||||
// Ensure the name doesn't exist once again
|
||||
assertNull(repository.getNameRepository().fromName(name));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOrphanAndReregisterName() throws DataException {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
|
||||
PrivateKeyAccount alice = Common.getTestAccount(repository, "alice");
|
||||
String name = "test-name";
|
||||
String data = "{\"age\":30}";
|
||||
|
||||
// Ensure the name doesn't exist
|
||||
assertNull(repository.getNameRepository().fromName(name));
|
||||
|
||||
// Register the name
|
||||
RegisterNameTransactionData transactionData = new RegisterNameTransactionData(TestTransaction.generateBase(alice), name, data);
|
||||
TransactionUtils.signAndMint(repository, transactionData, alice);
|
||||
|
||||
// Ensure the name exists and the data is correct
|
||||
assertEquals(data, repository.getNameRepository().fromName(name).getData());
|
||||
|
||||
// Orphan the latest block
|
||||
BlockUtils.orphanBlocks(repository, 1);
|
||||
|
||||
// Ensure the name doesn't exist once again
|
||||
assertNull(repository.getNameRepository().fromName(name));
|
||||
|
||||
// Now check there is an unconfirmed transaction
|
||||
assertEquals(1, repository.getTransactionRepository().getUnconfirmedTransactions().size());
|
||||
|
||||
// Re-mint the block, including the original transaction
|
||||
BlockMinter.mintTestingBlock(repository, Common.getTestAccount(repository, "alice-reward-share"));
|
||||
|
||||
// There should no longer be an unconfirmed transaction
|
||||
assertEquals(0, repository.getTransactionRepository().getUnconfirmedTransactions().size());
|
||||
|
||||
// Orphan the latest block
|
||||
BlockUtils.orphanBlocks(repository, 1);
|
||||
|
||||
// There should now be an unconfirmed transaction again
|
||||
assertEquals(1, repository.getTransactionRepository().getUnconfirmedTransactions().size());
|
||||
|
||||
// Re-mint the block, including the original transaction
|
||||
BlockMinter.mintTestingBlock(repository, Common.getTestAccount(repository, "alice-reward-share"));
|
||||
|
||||
// Ensure there are no unconfirmed transactions
|
||||
assertEquals(0, repository.getTransactionRepository().getUnconfirmedTransactions().size());
|
||||
}
|
||||
}
|
||||
|
||||
// test registering and then orphaning multiple times, with a different versions of the transaction each time
|
||||
// we can sometimes end up with more than one version of a transaction, if it is signed and submitted twice
|
||||
@Test
|
||||
public void testMultipleRegisterNameAndOrphan() throws DataException {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
|
||||
PrivateKeyAccount alice = Common.getTestAccount(repository, "alice");
|
||||
String name = "test-name";
|
||||
String data = "{\"age\":30}";
|
||||
|
||||
for (int i = 1; i <= 10; i++) {
|
||||
|
||||
// Ensure the name doesn't exist
|
||||
assertNull(repository.getNameRepository().fromName(name));
|
||||
|
||||
// Register the name
|
||||
RegisterNameTransactionData transactionData = new RegisterNameTransactionData(TestTransaction.generateBase(alice), name, data);
|
||||
TransactionUtils.signAndMint(repository, transactionData, alice);
|
||||
|
||||
// Ensure the name exists and the data is correct
|
||||
assertEquals(data, repository.getNameRepository().fromName(name).getData());
|
||||
|
||||
// The number of unconfirmed transactions should equal the number of cycles minus 1 (because one is in a block)
|
||||
// If more than one made it into a block, this test would fail
|
||||
assertEquals(i-1, repository.getTransactionRepository().getUnconfirmedTransactions().size());
|
||||
|
||||
// Orphan the latest block
|
||||
BlockUtils.orphanBlocks(repository, 1);
|
||||
|
||||
// The number of unconfirmed transactions should equal the number of cycles
|
||||
assertEquals(i, repository.getTransactionRepository().getUnconfirmedTransactions().size());
|
||||
|
||||
// Ensure the name doesn't exist once again
|
||||
assertNull(repository.getNameRepository().fromName(name));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSaveName() throws DataException {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
for (int i=0; i<10; i++) {
|
||||
|
||||
String name = "test-name";
|
||||
String data = "{\"age\":30}";
|
||||
|
||||
PrivateKeyAccount alice = Common.getTestAccount(repository, "alice");
|
||||
RegisterNameTransactionData transactionData = new RegisterNameTransactionData(TestTransaction.generateBase(alice), name, data);
|
||||
|
||||
// Ensure the name doesn't exist
|
||||
assertNull(repository.getNameRepository().fromName(name));
|
||||
|
||||
// Register the name
|
||||
Name nameObj = new Name(repository, transactionData);
|
||||
nameObj.register();
|
||||
|
||||
// Ensure the name now exists
|
||||
assertNotNull(repository.getNameRepository().fromName(name));
|
||||
|
||||
// Unregister the name
|
||||
nameObj.unregister();
|
||||
|
||||
// Ensure the name doesn't exist again
|
||||
assertNull(repository.getNameRepository().fromName(name));
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -5,6 +5,7 @@ import static org.junit.Assert.*;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.qortal.account.PrivateKeyAccount;
|
||||
import org.qortal.data.naming.NameData;
|
||||
import org.qortal.data.transaction.RegisterNameTransactionData;
|
||||
import org.qortal.data.transaction.TransactionData;
|
||||
import org.qortal.data.transaction.UpdateNameTransactionData;
|
||||
@@ -29,12 +30,21 @@ public class UpdateTests extends Common {
|
||||
// Register-name
|
||||
PrivateKeyAccount alice = Common.getTestAccount(repository, "alice");
|
||||
String initialName = "initial-name";
|
||||
String initialData = "initial-data";
|
||||
String initialReducedName = "initia1-name";
|
||||
String initialData = "{\"age\":30}";
|
||||
|
||||
TransactionData initialTransactionData = new RegisterNameTransactionData(TestTransaction.generateBase(alice), initialName, initialData);
|
||||
TransactionUtils.signAndMint(repository, initialTransactionData, alice);
|
||||
|
||||
// Check name, reduced name, and data exist
|
||||
assertTrue(repository.getNameRepository().nameExists(initialName));
|
||||
NameData nameData = repository.getNameRepository().fromName(initialName);
|
||||
assertEquals("initia1-name", nameData.getReducedName());
|
||||
assertEquals(initialData, nameData.getData());
|
||||
assertNotNull(repository.getNameRepository().fromReducedName(initialReducedName));
|
||||
|
||||
String newName = "new-name";
|
||||
String newReducedName = "new-name";
|
||||
String newData = "";
|
||||
TransactionData updateTransactionData = new UpdateNameTransactionData(TestTransaction.generateBase(alice), initialName, newName, newData);
|
||||
TransactionUtils.signAndMint(repository, updateTransactionData, alice);
|
||||
@@ -42,20 +52,37 @@ public class UpdateTests extends Common {
|
||||
// Check old name no longer exists
|
||||
assertFalse(repository.getNameRepository().nameExists(initialName));
|
||||
|
||||
// Check reduced name no longer exists
|
||||
assertNull(repository.getNameRepository().fromReducedName(initialReducedName));
|
||||
|
||||
// Check new name exists
|
||||
assertTrue(repository.getNameRepository().nameExists(newName));
|
||||
|
||||
// Check reduced name and data are correct for new name
|
||||
NameData newNameData = repository.getNameRepository().fromName(newReducedName);
|
||||
assertEquals(newReducedName, newNameData.getReducedName());
|
||||
// Data should remain the same because it was empty in the UpdateNameTransactionData
|
||||
assertEquals(initialData, newNameData.getData());
|
||||
|
||||
// Check updated timestamp is correct
|
||||
assertEquals((Long) updateTransactionData.getTimestamp(), repository.getNameRepository().fromName(newName).getUpdated());
|
||||
|
||||
// orphan and recheck
|
||||
BlockUtils.orphanLastBlock(repository);
|
||||
|
||||
// Check new name no longer exists
|
||||
// Check new name and reduced name no longer exist
|
||||
assertFalse(repository.getNameRepository().nameExists(newName));
|
||||
assertNull(repository.getNameRepository().fromReducedName(newReducedName));
|
||||
|
||||
// Check old name exists again
|
||||
// Check old name and reduced name exist again
|
||||
assertTrue(repository.getNameRepository().nameExists(initialName));
|
||||
assertNotNull(repository.getNameRepository().fromReducedName(initialReducedName));
|
||||
|
||||
// Check data and reduced name are still present for this name
|
||||
assertTrue(repository.getNameRepository().nameExists(initialName));
|
||||
nameData = repository.getNameRepository().fromName(initialName);
|
||||
assertEquals(initialReducedName, nameData.getReducedName());
|
||||
assertEquals(initialData, nameData.getData());
|
||||
|
||||
// Check updated timestamp is empty
|
||||
assertNull(repository.getNameRepository().fromName(initialName).getUpdated());
|
||||
@@ -68,11 +95,17 @@ public class UpdateTests extends Common {
|
||||
// Register-name
|
||||
PrivateKeyAccount alice = Common.getTestAccount(repository, "alice");
|
||||
String initialName = "initial-name";
|
||||
String initialData = "initial-data";
|
||||
String initialData = "{\"age\":30}";
|
||||
|
||||
String constantReducedName = "initia1-name";
|
||||
|
||||
TransactionData initialTransactionData = new RegisterNameTransactionData(TestTransaction.generateBase(alice), initialName, initialData);
|
||||
TransactionUtils.signAndMint(repository, initialTransactionData, alice);
|
||||
|
||||
// Check initial name exists
|
||||
assertTrue(repository.getNameRepository().nameExists(initialName));
|
||||
assertNotNull(repository.getNameRepository().fromReducedName(constantReducedName));
|
||||
|
||||
String newName = "Initial-Name";
|
||||
String newData = "";
|
||||
TransactionData updateTransactionData = new UpdateNameTransactionData(TestTransaction.generateBase(alice), initialName, newName, newData);
|
||||
@@ -83,6 +116,7 @@ public class UpdateTests extends Common {
|
||||
|
||||
// Check new name exists
|
||||
assertTrue(repository.getNameRepository().nameExists(newName));
|
||||
assertNotNull(repository.getNameRepository().fromReducedName(constantReducedName));
|
||||
|
||||
// Check updated timestamp is correct
|
||||
assertEquals((Long) updateTransactionData.getTimestamp(), repository.getNameRepository().fromName(newName).getUpdated());
|
||||
@@ -95,6 +129,7 @@ public class UpdateTests extends Common {
|
||||
|
||||
// Check old name exists again
|
||||
assertTrue(repository.getNameRepository().nameExists(initialName));
|
||||
assertNotNull(repository.getNameRepository().fromReducedName(constantReducedName));
|
||||
|
||||
// Check updated timestamp is empty
|
||||
assertNull(repository.getNameRepository().fromName(initialName).getUpdated());
|
||||
@@ -108,32 +143,43 @@ public class UpdateTests extends Common {
|
||||
// Register-name
|
||||
PrivateKeyAccount alice = Common.getTestAccount(repository, "alice");
|
||||
String initialName = "initial-name";
|
||||
String initialData = "initial-data";
|
||||
String initialReducedName = "initia1-name";
|
||||
String initialData = "{\"age\":30}";
|
||||
|
||||
TransactionData initialTransactionData = new RegisterNameTransactionData(TestTransaction.generateBase(alice), initialName, initialData);
|
||||
TransactionUtils.signAndMint(repository, initialTransactionData, alice);
|
||||
|
||||
// Check initial name exists
|
||||
assertTrue(repository.getNameRepository().nameExists(initialName));
|
||||
assertNotNull(repository.getNameRepository().fromReducedName(initialReducedName));
|
||||
|
||||
String middleName = "middle-name";
|
||||
String middleReducedName = "midd1e-name";
|
||||
String middleData = "";
|
||||
TransactionData middleTransactionData = new UpdateNameTransactionData(TestTransaction.generateBase(alice), initialName, middleName, middleData);
|
||||
TransactionUtils.signAndMint(repository, middleTransactionData, alice);
|
||||
|
||||
// Check old name no longer exists
|
||||
assertFalse(repository.getNameRepository().nameExists(initialName));
|
||||
assertNull(repository.getNameRepository().fromReducedName(initialReducedName));
|
||||
|
||||
// Check new name exists
|
||||
assertTrue(repository.getNameRepository().nameExists(middleName));
|
||||
assertNotNull(repository.getNameRepository().fromReducedName(middleReducedName));
|
||||
|
||||
String newestName = "newest-name";
|
||||
String newestReducedName = "newest-name";
|
||||
String newestData = "newest-data";
|
||||
TransactionData newestTransactionData = new UpdateNameTransactionData(TestTransaction.generateBase(alice), middleName, newestName, newestData);
|
||||
TransactionUtils.signAndMint(repository, newestTransactionData, alice);
|
||||
|
||||
// Check previous name no longer exists
|
||||
assertFalse(repository.getNameRepository().nameExists(middleName));
|
||||
assertNull(repository.getNameRepository().fromReducedName(middleReducedName));
|
||||
|
||||
// Check newest name exists
|
||||
assertTrue(repository.getNameRepository().nameExists(newestName));
|
||||
assertNotNull(repository.getNameRepository().fromReducedName(newestReducedName));
|
||||
|
||||
// Check updated timestamp is correct
|
||||
assertEquals((Long) newestTransactionData.getTimestamp(), repository.getNameRepository().fromName(newestName).getUpdated());
|
||||
@@ -143,9 +189,11 @@ public class UpdateTests extends Common {
|
||||
|
||||
// Check newest name no longer exists
|
||||
assertFalse(repository.getNameRepository().nameExists(newestName));
|
||||
assertNull(repository.getNameRepository().fromReducedName(newestReducedName));
|
||||
|
||||
// Check previous name exists again
|
||||
assertTrue(repository.getNameRepository().nameExists(middleName));
|
||||
assertNotNull(repository.getNameRepository().fromReducedName(middleReducedName));
|
||||
|
||||
// Check updated timestamp is correct
|
||||
assertEquals((Long) middleTransactionData.getTimestamp(), repository.getNameRepository().fromName(middleName).getUpdated());
|
||||
@@ -155,9 +203,11 @@ public class UpdateTests extends Common {
|
||||
|
||||
// Check new name no longer exists
|
||||
assertFalse(repository.getNameRepository().nameExists(middleName));
|
||||
assertNull(repository.getNameRepository().fromReducedName(middleReducedName));
|
||||
|
||||
// Check original name exists again
|
||||
assertTrue(repository.getNameRepository().nameExists(initialName));
|
||||
assertNotNull(repository.getNameRepository().fromReducedName(initialReducedName));
|
||||
|
||||
// Check updated timestamp is empty
|
||||
assertNull(repository.getNameRepository().fromName(initialName).getUpdated());
|
||||
@@ -171,11 +221,16 @@ public class UpdateTests extends Common {
|
||||
// Register-name
|
||||
PrivateKeyAccount alice = Common.getTestAccount(repository, "alice");
|
||||
String initialName = "initial-name";
|
||||
String initialData = "initial-data";
|
||||
String initialReducedName = "initia1-name";
|
||||
String initialData = "{\"age\":30}";
|
||||
|
||||
TransactionData transactionData = new RegisterNameTransactionData(TestTransaction.generateBase(alice), initialName, initialData);
|
||||
TransactionUtils.signAndMint(repository, transactionData, alice);
|
||||
|
||||
// Check initial name exists
|
||||
assertTrue(repository.getNameRepository().nameExists(initialName));
|
||||
assertNotNull(repository.getNameRepository().fromReducedName(initialReducedName));
|
||||
|
||||
// Don't update name, but update data.
|
||||
// This tests whether reverting a future update/sale can find the correct previous name
|
||||
String middleName = "";
|
||||
@@ -185,29 +240,35 @@ public class UpdateTests extends Common {
|
||||
|
||||
// Check old name still exists
|
||||
assertTrue(repository.getNameRepository().nameExists(initialName));
|
||||
assertNotNull(repository.getNameRepository().fromReducedName(initialReducedName));
|
||||
|
||||
String newestName = "newest-name";
|
||||
String newestReducedName = "newest-name";
|
||||
String newestData = "newest-data";
|
||||
transactionData = new UpdateNameTransactionData(TestTransaction.generateBase(alice), initialName, newestName, newestData);
|
||||
TransactionUtils.signAndMint(repository, transactionData, alice);
|
||||
|
||||
// Check previous name no longer exists
|
||||
assertFalse(repository.getNameRepository().nameExists(initialName));
|
||||
assertNull(repository.getNameRepository().fromReducedName(initialReducedName));
|
||||
|
||||
// Check newest name exists
|
||||
assertTrue(repository.getNameRepository().nameExists(newestName));
|
||||
assertNotNull(repository.getNameRepository().fromReducedName(newestReducedName));
|
||||
|
||||
// orphan and recheck
|
||||
BlockUtils.orphanLastBlock(repository);
|
||||
|
||||
// Check original name exists again
|
||||
assertTrue(repository.getNameRepository().nameExists(initialName));
|
||||
assertNotNull(repository.getNameRepository().fromReducedName(initialReducedName));
|
||||
|
||||
// orphan and recheck
|
||||
BlockUtils.orphanLastBlock(repository);
|
||||
|
||||
// Check original name still exists
|
||||
assertTrue(repository.getNameRepository().nameExists(initialName));
|
||||
assertNotNull(repository.getNameRepository().fromReducedName(initialReducedName));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -217,11 +278,16 @@ public class UpdateTests extends Common {
|
||||
// Register-name
|
||||
PrivateKeyAccount alice = Common.getTestAccount(repository, "alice");
|
||||
String initialName = "initial-name";
|
||||
String initialData = "initial-data";
|
||||
String initialReducedName = "initia1-name";
|
||||
String initialData = "{\"age\":30}";
|
||||
|
||||
TransactionData transactionData = new RegisterNameTransactionData(TestTransaction.generateBase(alice), initialName, initialData);
|
||||
TransactionUtils.signAndMint(repository, transactionData, alice);
|
||||
|
||||
// Check initial name exists
|
||||
assertTrue(repository.getNameRepository().nameExists(initialName));
|
||||
assertNotNull(repository.getNameRepository().fromReducedName(initialReducedName));
|
||||
|
||||
String newName = "";
|
||||
String newData = "new-data";
|
||||
transactionData = new UpdateNameTransactionData(TestTransaction.generateBase(alice), initialName, newName, newData);
|
||||
@@ -229,6 +295,7 @@ public class UpdateTests extends Common {
|
||||
|
||||
// Check name still exists
|
||||
assertTrue(repository.getNameRepository().nameExists(initialName));
|
||||
assertNotNull(repository.getNameRepository().fromReducedName(initialReducedName));
|
||||
|
||||
// Check data is correct
|
||||
assertEquals(newData, repository.getNameRepository().fromName(initialName).getData());
|
||||
@@ -238,6 +305,7 @@ public class UpdateTests extends Common {
|
||||
|
||||
// Check name still exists
|
||||
assertTrue(repository.getNameRepository().nameExists(initialName));
|
||||
assertNotNull(repository.getNameRepository().fromReducedName(initialReducedName));
|
||||
|
||||
// Check old data restored
|
||||
assertEquals(initialData, repository.getNameRepository().fromName(initialName).getData());
|
||||
@@ -251,13 +319,19 @@ public class UpdateTests extends Common {
|
||||
// Register-name
|
||||
PrivateKeyAccount alice = Common.getTestAccount(repository, "alice");
|
||||
String initialName = "initial-name";
|
||||
String initialData = "initial-data";
|
||||
String initialReducedName = "initia1-name";
|
||||
String initialData = "{\"age\":30}";
|
||||
|
||||
TransactionData transactionData = new RegisterNameTransactionData(TestTransaction.generateBase(alice), initialName, initialData);
|
||||
TransactionUtils.signAndMint(repository, transactionData, alice);
|
||||
|
||||
// Check initial name exists
|
||||
assertTrue(repository.getNameRepository().nameExists(initialName));
|
||||
assertNotNull(repository.getNameRepository().fromReducedName(initialReducedName));
|
||||
|
||||
// Update data
|
||||
String middleName = "middle-name";
|
||||
String middleReducedName = "midd1e-name";
|
||||
String middleData = "middle-data";
|
||||
transactionData = new UpdateNameTransactionData(TestTransaction.generateBase(alice), initialName, middleName, middleData);
|
||||
TransactionUtils.signAndMint(repository, transactionData, alice);
|
||||
@@ -266,6 +340,7 @@ public class UpdateTests extends Common {
|
||||
assertEquals(middleData, repository.getNameRepository().fromName(middleName).getData());
|
||||
|
||||
String newestName = "newest-name";
|
||||
String newestReducedName = "newest-name";
|
||||
String newestData = "newest-data";
|
||||
transactionData = new UpdateNameTransactionData(TestTransaction.generateBase(alice), middleName, newestName, newestData);
|
||||
TransactionUtils.signAndMint(repository, transactionData, alice);
|
||||
@@ -273,6 +348,14 @@ public class UpdateTests extends Common {
|
||||
// Check data is correct
|
||||
assertEquals(newestData, repository.getNameRepository().fromName(newestName).getData());
|
||||
|
||||
// Check initial name no longer exists
|
||||
assertFalse(repository.getNameRepository().nameExists(initialName));
|
||||
assertNull(repository.getNameRepository().fromReducedName(initialReducedName));
|
||||
|
||||
// Check newest name exists
|
||||
assertTrue(repository.getNameRepository().nameExists(newestName));
|
||||
assertNotNull(repository.getNameRepository().fromReducedName(newestReducedName));
|
||||
|
||||
// orphan and recheck
|
||||
BlockUtils.orphanLastBlock(repository);
|
||||
|
||||
@@ -284,6 +367,10 @@ public class UpdateTests extends Common {
|
||||
|
||||
// Check data is correct
|
||||
assertEquals(initialData, repository.getNameRepository().fromName(initialName).getData());
|
||||
|
||||
// Check initial name exists again
|
||||
assertTrue(repository.getNameRepository().nameExists(initialName));
|
||||
assertNotNull(repository.getNameRepository().fromReducedName(initialReducedName));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -294,38 +381,69 @@ public class UpdateTests extends Common {
|
||||
// Register-name
|
||||
PrivateKeyAccount alice = Common.getTestAccount(repository, "alice");
|
||||
String initialName = "initial-name";
|
||||
String initialData = "initial-data";
|
||||
String initialReducedName = "initia1-name";
|
||||
String initialData = "{\"age\":30}";
|
||||
|
||||
TransactionData transactionData = new RegisterNameTransactionData(TestTransaction.generateBase(alice), initialName, initialData);
|
||||
TransactionUtils.signAndMint(repository, transactionData, alice);
|
||||
|
||||
// Check initial name exists
|
||||
assertTrue(repository.getNameRepository().nameExists(initialName));
|
||||
assertNotNull(repository.getNameRepository().fromReducedName(initialReducedName));
|
||||
|
||||
// Don't update data, but update name.
|
||||
// This tests whether reverting a future update/sale can find the correct previous data
|
||||
String middleName = "middle-name";
|
||||
String middleReducedName = "midd1e-name";
|
||||
String middleData = "";
|
||||
transactionData = new UpdateNameTransactionData(TestTransaction.generateBase(alice), initialName, middleName, middleData);
|
||||
TransactionUtils.signAndMint(repository, transactionData, alice);
|
||||
|
||||
// Check original name no longer exists
|
||||
assertFalse(repository.getNameRepository().nameExists(initialName));
|
||||
assertNull(repository.getNameRepository().fromReducedName(initialReducedName));
|
||||
|
||||
// Check middle name exists
|
||||
assertTrue(repository.getNameRepository().nameExists(middleName));
|
||||
assertNotNull(repository.getNameRepository().fromReducedName(middleReducedName));
|
||||
|
||||
// Check data is correct
|
||||
assertEquals(initialData, repository.getNameRepository().fromName(middleName).getData());
|
||||
|
||||
String newestName = "newest-name";
|
||||
String newestReducedName = "newest-name";
|
||||
String newestData = "newest-data";
|
||||
transactionData = new UpdateNameTransactionData(TestTransaction.generateBase(alice), middleName, newestName, newestData);
|
||||
TransactionUtils.signAndMint(repository, transactionData, alice);
|
||||
|
||||
// Check middle name no longer exists
|
||||
assertFalse(repository.getNameRepository().nameExists(middleName));
|
||||
assertNull(repository.getNameRepository().fromReducedName(middleReducedName));
|
||||
|
||||
// Check newest name exists
|
||||
assertTrue(repository.getNameRepository().nameExists(newestName));
|
||||
assertNotNull(repository.getNameRepository().fromReducedName(newestReducedName));
|
||||
|
||||
// Check data is correct
|
||||
assertEquals(newestData, repository.getNameRepository().fromName(newestName).getData());
|
||||
|
||||
// orphan and recheck
|
||||
BlockUtils.orphanLastBlock(repository);
|
||||
|
||||
// Check middle name exists
|
||||
assertTrue(repository.getNameRepository().nameExists(middleName));
|
||||
assertNotNull(repository.getNameRepository().fromReducedName(middleReducedName));
|
||||
|
||||
// Check data is correct
|
||||
assertEquals(initialData, repository.getNameRepository().fromName(middleName).getData());
|
||||
|
||||
// orphan and recheck
|
||||
BlockUtils.orphanLastBlock(repository);
|
||||
|
||||
// Check initial name exists
|
||||
assertTrue(repository.getNameRepository().nameExists(initialName));
|
||||
assertNotNull(repository.getNameRepository().fromReducedName(initialReducedName));
|
||||
|
||||
// Check data is correct
|
||||
assertEquals(initialData, repository.getNameRepository().fromName(initialName).getData());
|
||||
}
|
||||
|
@@ -1,9 +1,13 @@
|
||||
{
|
||||
"repositoryPath": "testdb",
|
||||
"bitcoinNet": "REGTEST",
|
||||
"litecoinNet": "REGTEST",
|
||||
"restrictedApi": false,
|
||||
"blockchainConfig": "src/test/resources/test-chain-v2.json",
|
||||
"exportPath": "qortal-backup-test",
|
||||
"bootstrap": false,
|
||||
"wipeUnconfirmedOnStart": false,
|
||||
"testNtpOffset": 0,
|
||||
"minPeers": 0
|
||||
"minPeers": 0,
|
||||
"pruneBlockLimit": 100
|
||||
}
|
||||
|
13
src/test/resources/test-settings-v2-block-archive.json
Normal file
13
src/test/resources/test-settings-v2-block-archive.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"bitcoinNet": "TEST3",
|
||||
"litecoinNet": "TEST3",
|
||||
"restrictedApi": false,
|
||||
"blockchainConfig": "src/test/resources/test-chain-v2.json",
|
||||
"exportPath": "qortal-backup-test",
|
||||
"bootstrap": false,
|
||||
"wipeUnconfirmedOnStart": false,
|
||||
"testNtpOffset": 0,
|
||||
"minPeers": 0,
|
||||
"pruneBlockLimit": 100,
|
||||
"repositoryPath": "dbtest"
|
||||
}
|
@@ -1,7 +1,11 @@
|
||||
{
|
||||
"repositoryPath": "testdb",
|
||||
"restrictedApi": false,
|
||||
"blockchainConfig": "src/test/resources/test-chain-v2-founder-rewards.json",
|
||||
"exportPath": "qortal-backup-test",
|
||||
"bootstrap": false,
|
||||
"wipeUnconfirmedOnStart": false,
|
||||
"testNtpOffset": 0,
|
||||
"minPeers": 0
|
||||
"minPeers": 0,
|
||||
"pruneBlockLimit": 100
|
||||
}
|
||||
|
@@ -1,7 +1,11 @@
|
||||
{
|
||||
"repositoryPath": "testdb",
|
||||
"restrictedApi": false,
|
||||
"blockchainConfig": "src/test/resources/test-chain-v2-leftover-reward.json",
|
||||
"exportPath": "qortal-backup-test",
|
||||
"bootstrap": false,
|
||||
"wipeUnconfirmedOnStart": false,
|
||||
"testNtpOffset": 0,
|
||||
"minPeers": 0
|
||||
"minPeers": 0,
|
||||
"pruneBlockLimit": 100
|
||||
}
|
||||
|
@@ -1,7 +1,11 @@
|
||||
{
|
||||
"repositoryPath": "testdb",
|
||||
"restrictedApi": false,
|
||||
"blockchainConfig": "src/test/resources/test-chain-v2-minting.json",
|
||||
"exportPath": "qortal-backup-test",
|
||||
"bootstrap": false,
|
||||
"wipeUnconfirmedOnStart": false,
|
||||
"testNtpOffset": 0,
|
||||
"minPeers": 0
|
||||
"minPeers": 0,
|
||||
"pruneBlockLimit": 100
|
||||
}
|
||||
|
@@ -1,7 +1,11 @@
|
||||
{
|
||||
"repositoryPath": "testdb",
|
||||
"restrictedApi": false,
|
||||
"blockchainConfig": "src/test/resources/test-chain-v2-qora-holder-extremes.json",
|
||||
"exportPath": "qortal-backup-test",
|
||||
"bootstrap": false,
|
||||
"wipeUnconfirmedOnStart": false,
|
||||
"testNtpOffset": 0,
|
||||
"minPeers": 0
|
||||
"minPeers": 0,
|
||||
"pruneBlockLimit": 100
|
||||
}
|
||||
|
@@ -1,7 +1,11 @@
|
||||
{
|
||||
"repositoryPath": "testdb",
|
||||
"restrictedApi": false,
|
||||
"blockchainConfig": "src/test/resources/test-chain-v2-qora-holder.json",
|
||||
"exportPath": "qortal-backup-test",
|
||||
"bootstrap": false,
|
||||
"wipeUnconfirmedOnStart": false,
|
||||
"testNtpOffset": 0,
|
||||
"minPeers": 0
|
||||
"minPeers": 0,
|
||||
"pruneBlockLimit": 100
|
||||
}
|
||||
|
@@ -1,7 +1,11 @@
|
||||
{
|
||||
"repositoryPath": "testdb",
|
||||
"restrictedApi": false,
|
||||
"blockchainConfig": "src/test/resources/test-chain-v2-reward-levels.json",
|
||||
"exportPath": "qortal-backup-test",
|
||||
"bootstrap": false,
|
||||
"wipeUnconfirmedOnStart": false,
|
||||
"testNtpOffset": 0,
|
||||
"minPeers": 0
|
||||
"minPeers": 0,
|
||||
"pruneBlockLimit": 100
|
||||
}
|
||||
|
@@ -1,7 +1,11 @@
|
||||
{
|
||||
"repositoryPath": "testdb",
|
||||
"restrictedApi": false,
|
||||
"blockchainConfig": "src/test/resources/test-chain-v2-reward-scaling.json",
|
||||
"exportPath": "qortal-backup-test",
|
||||
"bootstrap": false,
|
||||
"wipeUnconfirmedOnStart": false,
|
||||
"testNtpOffset": 0,
|
||||
"minPeers": 0
|
||||
"minPeers": 0,
|
||||
"pruneBlockLimit": 100
|
||||
}
|
||||
|
@@ -1,10 +1,15 @@
|
||||
{
|
||||
"repositoryPath": "testdb",
|
||||
"bitcoinNet": "TEST3",
|
||||
"litecoinNet": "TEST3",
|
||||
"restrictedApi": false,
|
||||
"blockchainConfig": "src/test/resources/test-chain-v2.json",
|
||||
"exportPath": "qortal-backup-test",
|
||||
"bootstrap": false,
|
||||
"wipeUnconfirmedOnStart": false,
|
||||
"testNtpOffset": 0,
|
||||
"minPeers": 0,
|
||||
"pruneBlockLimit": 100,
|
||||
"bootstrapFilenamePrefix": "test-",
|
||||
"dataPath": "data-test"
|
||||
}
|
||||
|
Reference in New Issue
Block a user