diff --git a/src/main/java/org/qora/naming/Name.java b/src/main/java/org/qora/naming/Name.java index 37275e0f..5ffbf01a 100644 --- a/src/main/java/org/qora/naming/Name.java +++ b/src/main/java/org/qora/naming/Name.java @@ -179,6 +179,7 @@ public class Name { public void unbuy(BuyNameTransactionData buyNameTransactionData) throws DataException { // Mark as for-sale using existing price this.nameData.setIsForSale(true); + this.nameData.setSalePrice(buyNameTransactionData.getAmount()); // Previous name reference is taken from this transaction's cached copy this.nameData.setReference(buyNameTransactionData.getNameReference()); diff --git a/src/main/java/org/qora/utils/NTP.java b/src/main/java/org/qora/utils/NTP.java index 7707253a..59a75b74 100644 --- a/src/main/java/org/qora/utils/NTP.java +++ b/src/main/java/org/qora/utils/NTP.java @@ -127,6 +127,7 @@ public class NTP implements Runnable { if (isStarted) return; + isStarted = true; instanceExecutor = Executors.newSingleThreadExecutor(); instance = new NTP(); instanceExecutor.execute(instance); @@ -136,16 +137,21 @@ public class NTP implements Runnable { instanceExecutor.shutdownNow(); } + public static synchronized void testMode() { + // Fix offset to match system time + NTP.offset = 0L; + } + /** * Returns our estimate of internet time. * * @return internet time (ms), or null if unsynchronized. */ public static Long getTime() { - if (offset == null) + if (NTP.offset == null) return null; - return System.currentTimeMillis() + offset; + return System.currentTimeMillis() + NTP.offset; } public void run() { diff --git a/src/test/java/org/qora/test/common/BlockUtils.java b/src/test/java/org/qora/test/common/BlockUtils.java index f0fd5e8a..451e8b8a 100644 --- a/src/test/java/org/qora/test/common/BlockUtils.java +++ b/src/test/java/org/qora/test/common/BlockUtils.java @@ -23,4 +23,9 @@ public class BlockUtils { repository.saveChanges(); } + public static void orphanBlocks(Repository repository, int count) throws DataException { + for (int i = 0; i < count; ++i) + orphanLastBlock(repository); + } + } diff --git a/src/test/java/org/qora/test/common/Common.java b/src/test/java/org/qora/test/common/Common.java index 92e48f4b..bbb27be5 100644 --- a/src/test/java/org/qora/test/common/Common.java +++ b/src/test/java/org/qora/test/common/Common.java @@ -34,6 +34,7 @@ import org.qora.repository.RepositoryFactory; import org.qora.repository.RepositoryManager; import org.qora.repository.hsqldb.HSQLDBRepositoryFactory; import org.qora.settings.Settings; +import org.qora.utils.NTP; public class Common { @@ -101,6 +102,7 @@ public class Common { public static void useDefaultSettings() throws DataException { useSettings(testSettingsFilename); + NTP.testMode(); } public static void resetBlockchain() throws DataException { diff --git a/src/test/java/org/qora/test/common/transaction/TestTransaction.java b/src/test/java/org/qora/test/common/transaction/TestTransaction.java index 1da41851..623f9328 100644 --- a/src/test/java/org/qora/test/common/transaction/TestTransaction.java +++ b/src/test/java/org/qora/test/common/transaction/TestTransaction.java @@ -12,7 +12,7 @@ public abstract class TestTransaction { protected static final Random random = new Random(); - protected static BaseTransactionData generateBase(PrivateKeyAccount account) throws DataException { + public static BaseTransactionData generateBase(PrivateKeyAccount account) throws DataException { byte[] lastReference = account.getUnconfirmedLastReference(); if (lastReference == null) lastReference = account.getLastReference(); diff --git a/src/test/java/org/qora/test/naming/OrphaningTests.java b/src/test/java/org/qora/test/naming/OrphaningTests.java new file mode 100644 index 00000000..cef13145 --- /dev/null +++ b/src/test/java/org/qora/test/naming/OrphaningTests.java @@ -0,0 +1,254 @@ +package org.qora.test.naming; + +import static org.junit.Assert.*; + +import java.math.BigDecimal; +import java.util.Random; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.qora.account.PrivateKeyAccount; +import org.qora.block.BlockGenerator; +import org.qora.data.naming.NameData; +import org.qora.data.transaction.BuyNameTransactionData; +import org.qora.data.transaction.RegisterNameTransactionData; +import org.qora.data.transaction.SellNameTransactionData; +import org.qora.repository.DataException; +import org.qora.repository.Repository; +import org.qora.repository.RepositoryManager; +import org.qora.test.common.BlockUtils; +import org.qora.test.common.Common; +import org.qora.test.common.TransactionUtils; +import org.qora.test.common.transaction.TestTransaction; + +public class OrphaningTests extends Common { + + protected static final Random random = new Random(); + + private Repository repository; + private PrivateKeyAccount alice; + private PrivateKeyAccount bob; + private String name; + private BigDecimal price; + + @Before + public void beforeTest() throws DataException { + Common.useDefaultSettings(); + + repository = RepositoryManager.getRepository(); + alice = Common.getTestAccount(repository, "alice"); + bob = Common.getTestAccount(repository, "bob"); + + name = "test name" + " " + random.nextInt(1_000_000); + price = new BigDecimal(random.nextInt(1000)).setScale(8); + } + + @After + public void afterTest() throws DataException { + name = null; + price = null; + + alice = null; + bob = null; + repository = null; + + Common.orphanCheck(); + } + + @Test + public void testRegisterName() throws DataException { + // Register-name + RegisterNameTransactionData transactionData = new RegisterNameTransactionData(TestTransaction.generateBase(alice), alice.getAddress(), name, "{}"); + TransactionUtils.signAndForge(repository, transactionData, alice); + + String name = transactionData.getName(); + + // Check name does exist + assertTrue(repository.getNameRepository().nameExists(name)); + + // Orphan register-name + BlockUtils.orphanLastBlock(repository); + + // Check name no longer exists + assertFalse(repository.getNameRepository().nameExists(name)); + + // Re-process register-name + BlockGenerator.generateTestingBlock(repository, alice); + + // Check name does exist + assertTrue(repository.getNameRepository().nameExists(name)); + } + + @Test + public void testSellName() throws DataException { + // Register-name + testRegisterName(); + + // Sell-name + SellNameTransactionData transactionData = new SellNameTransactionData(TestTransaction.generateBase(alice), name, price); + TransactionUtils.signAndForge(repository, transactionData, alice); + + NameData nameData; + + // Check name is for sale + nameData = repository.getNameRepository().fromName(name); + assertTrue(nameData.getIsForSale()); + assertEqualBigDecimals("price incorrect", price, nameData.getSalePrice()); + + // Orphan sell-name + BlockUtils.orphanLastBlock(repository); + + // Check name no longer for sale + nameData = repository.getNameRepository().fromName(name); + assertFalse(nameData.getIsForSale()); + // Not concerned about price + + // Re-process sell-name + BlockGenerator.generateTestingBlock(repository, alice); + + // Check name is for sale + nameData = repository.getNameRepository().fromName(name); + assertTrue(nameData.getIsForSale()); + assertEqualBigDecimals("price incorrect", price, nameData.getSalePrice()); + + // Orphan sell-name and register-name + BlockUtils.orphanBlocks(repository, 2); + + // Check name no longer exists + assertFalse(repository.getNameRepository().nameExists(name)); + nameData = repository.getNameRepository().fromName(name); + assertNull(nameData); + + // Re-process register-name and sell-name + BlockGenerator.generateTestingBlock(repository, alice); + // Unconfirmed sell-name transaction not included in previous block + // as it isn't valid until name exists thanks to register-name transaction. + BlockGenerator.generateTestingBlock(repository, alice); + + // Check name does exist + assertTrue(repository.getNameRepository().nameExists(name)); + + // Check name is for sale + nameData = repository.getNameRepository().fromName(name); + assertTrue(nameData.getIsForSale()); + assertEqualBigDecimals("price incorrect", price, nameData.getSalePrice()); + } + + @Test + public void testBuyName() throws DataException { + // Register-name and sell-name + testSellName(); + + String seller = alice.getAddress(); + + // Buy-name + BuyNameTransactionData transactionData = new BuyNameTransactionData(TestTransaction.generateBase(bob), name, price, seller); + TransactionUtils.signAndForge(repository, transactionData, bob); + + NameData nameData; + + // Check name is sold + nameData = repository.getNameRepository().fromName(name); + assertFalse(nameData.getIsForSale()); + // Not concerned about price + + // Orphan buy-name + BlockUtils.orphanLastBlock(repository); + + // Check name is for sale (not sold) + nameData = repository.getNameRepository().fromName(name); + assertTrue(nameData.getIsForSale()); + assertEqualBigDecimals("price incorrect", price, nameData.getSalePrice()); + + // Re-process buy-name + BlockGenerator.generateTestingBlock(repository, alice); + + // Check name is sold + nameData = repository.getNameRepository().fromName(name); + assertFalse(nameData.getIsForSale()); + // Not concerned about price + assertEquals(bob.getAddress(), nameData.getOwner()); + + // Orphan buy-name and sell-name + BlockUtils.orphanBlocks(repository, 2); + + // Check name no longer for sale + nameData = repository.getNameRepository().fromName(name); + assertFalse(nameData.getIsForSale()); + // Not concerned about price + assertEquals(alice.getAddress(), nameData.getOwner()); + + // Re-process sell-name and buy-name + BlockGenerator.generateTestingBlock(repository, alice); + // Unconfirmed buy-name transaction not included in previous block + // as it isn't valid until name is for sale thanks to sell-name transaction. + BlockGenerator.generateTestingBlock(repository, alice); + + // Check name is sold + nameData = repository.getNameRepository().fromName(name); + assertFalse(nameData.getIsForSale()); + // Not concerned about price + assertEquals(bob.getAddress(), nameData.getOwner()); + } + + @Test + public void testSellBuySellName() throws DataException { + // Register-name, sell-name, buy-name + testBuyName(); + + // Sell-name + BigDecimal newPrice = new BigDecimal(random.nextInt(1000)).setScale(8); + SellNameTransactionData transactionData = new SellNameTransactionData(TestTransaction.generateBase(bob), name, newPrice); + TransactionUtils.signAndForge(repository, transactionData, bob); + + NameData nameData; + + // Check name is for sale + nameData = repository.getNameRepository().fromName(name); + assertTrue(nameData.getIsForSale()); + assertEqualBigDecimals("price incorrect", newPrice, nameData.getSalePrice()); + + // Orphan sell-name + BlockUtils.orphanLastBlock(repository); + + // Check name no longer for sale + nameData = repository.getNameRepository().fromName(name); + assertFalse(nameData.getIsForSale()); + // Not concerned about price + + // Re-process sell-name + BlockGenerator.generateTestingBlock(repository, alice); + + // Check name is for sale + nameData = repository.getNameRepository().fromName(name); + assertTrue(nameData.getIsForSale()); + assertEqualBigDecimals("price incorrect", newPrice, nameData.getSalePrice()); + + // Orphan sell-name and buy-name + BlockUtils.orphanBlocks(repository, 2); + + // Check name is for sale + nameData = repository.getNameRepository().fromName(name); + assertTrue(nameData.getIsForSale()); + // Note: original sale price + assertEqualBigDecimals("price incorrect", price, nameData.getSalePrice()); + assertEquals(alice.getAddress(), nameData.getOwner()); + + // Re-process buy-name and sell-name + BlockGenerator.generateTestingBlock(repository, bob); + // Unconfirmed sell-name transaction not included in previous block + // as it isn't valid until name owned by bob thanks to buy-name transaction. + BlockGenerator.generateTestingBlock(repository, bob); + + // Check name does exist + assertTrue(repository.getNameRepository().nameExists(name)); + + // Check name is for sale + nameData = repository.getNameRepository().fromName(name); + assertTrue(nameData.getIsForSale()); + assertEqualBigDecimals("price incorrect", newPrice, nameData.getSalePrice()); + assertEquals(bob.getAddress(), nameData.getOwner()); + } + +} diff --git a/src/test/resources/test-chain-v2.json b/src/test/resources/test-chain-v2.json index 04db3c4d..033446c0 100644 --- a/src/test/resources/test-chain-v2.json +++ b/src/test/resources/test-chain-v2.json @@ -44,6 +44,7 @@ "arbitraryTimestamp": 0, "powfixTimestamp": 0, "v2Timestamp": 0, - "newAssetPricingTimestamp": 0 + "newAssetPricingTimestamp": 0, + "groupApprovalTimestamp": 0 } }