diff --git a/pom.xml b/pom.xml index 1eb8adb1..35c77bcc 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 org.qortal qortal - 3.8.5 + 3.8.9 jar true diff --git a/src/main/java/org/qortal/api/resource/BootstrapResource.java b/src/main/java/org/qortal/api/resource/BootstrapResource.java index b9382dcb..78630dfb 100644 --- a/src/main/java/org/qortal/api/resource/BootstrapResource.java +++ b/src/main/java/org/qortal/api/resource/BootstrapResource.java @@ -60,7 +60,7 @@ public class BootstrapResource { bootstrap.validateBlockchain(); return bootstrap.create(); - } catch (DataException | InterruptedException | IOException e) { + } catch (Exception e) { LOGGER.info("Unable to create bootstrap", e); throw ApiExceptionFactory.INSTANCE.createCustomException(request, ApiError.REPOSITORY_ISSUE, e.getMessage()); } diff --git a/src/main/java/org/qortal/arbitrary/misc/Service.java b/src/main/java/org/qortal/arbitrary/misc/Service.java index 01419d2f..5ea1b7aa 100644 --- a/src/main/java/org/qortal/arbitrary/misc/Service.java +++ b/src/main/java/org/qortal/arbitrary/misc/Service.java @@ -84,6 +84,8 @@ public enum Service { QCHAT_IMAGE(420, true, 500*1024L, null), VIDEO(500, false, null, null), AUDIO(600, false, null, null), + QCHAT_AUDIO(610, true, 10*1024*1024L, null), + QCHAT_VOICE(620, true, 10*1024*1024L, null), BLOG(700, false, null, null), BLOG_POST(777, false, null, null), BLOG_COMMENT(778, false, null, null), diff --git a/src/main/java/org/qortal/controller/arbitrary/ArbitraryDataCleanupManager.java b/src/main/java/org/qortal/controller/arbitrary/ArbitraryDataCleanupManager.java index 39425b7e..34acf0cb 100644 --- a/src/main/java/org/qortal/controller/arbitrary/ArbitraryDataCleanupManager.java +++ b/src/main/java/org/qortal/controller/arbitrary/ArbitraryDataCleanupManager.java @@ -204,7 +204,7 @@ public class ArbitraryDataCleanupManager extends Thread { if (completeFileExists && !allChunksExist) { // We have the complete file but not the chunks, so let's convert it - LOGGER.info(String.format("Transaction %s has complete file but no chunks", + LOGGER.debug(String.format("Transaction %s has complete file but no chunks", Base58.encode(arbitraryTransactionData.getSignature()))); ArbitraryTransactionUtils.convertFileToChunks(arbitraryTransactionData, now, STALE_FILE_TIMEOUT); diff --git a/src/main/java/org/qortal/crosschain/Digibyte.java b/src/main/java/org/qortal/crosschain/Digibyte.java index 2b31468d..c5d96383 100644 --- a/src/main/java/org/qortal/crosschain/Digibyte.java +++ b/src/main/java/org/qortal/crosschain/Digibyte.java @@ -45,6 +45,7 @@ public class Digibyte extends Bitcoiny { return Arrays.asList( // Servers chosen on NO BASIS WHATSOEVER from various sources! // Status verified at https://1209k.com/bitcoin-eye/ele.php?chain=dgb + new Server("electrum.qortal.link", Server.ConnectionType.SSL, 55002), new Server("electrum-dgb.qortal.online", ConnectionType.SSL, 50002), new Server("electrum1-dgb.qortal.online", ConnectionType.SSL, 50002), new Server("electrum1.cipig.net", ConnectionType.SSL, 20059), diff --git a/src/main/java/org/qortal/crosschain/Dogecoin.java b/src/main/java/org/qortal/crosschain/Dogecoin.java index 6e763377..99f557a5 100644 --- a/src/main/java/org/qortal/crosschain/Dogecoin.java +++ b/src/main/java/org/qortal/crosschain/Dogecoin.java @@ -46,6 +46,7 @@ public class Dogecoin extends Bitcoiny { return Arrays.asList( // Servers chosen on NO BASIS WHATSOEVER from various sources! // Status verified at https://1209k.com/bitcoin-eye/ele.php?chain=doge + new Server("electrum.qortal.link", Server.ConnectionType.SSL, 54002), new Server("electrum-doge.qortal.online", ConnectionType.SSL, 50002), new Server("electrum1-doge.qortal.online", ConnectionType.SSL, 50002), new Server("electrum1.cipig.net", ConnectionType.SSL, 20060), diff --git a/src/main/java/org/qortal/crosschain/Litecoin.java b/src/main/java/org/qortal/crosschain/Litecoin.java index 4e672d3f..1dd9037a 100644 --- a/src/main/java/org/qortal/crosschain/Litecoin.java +++ b/src/main/java/org/qortal/crosschain/Litecoin.java @@ -50,6 +50,7 @@ public class Litecoin extends Bitcoiny { //BEHIND new Server("62.171.169.176", Server.ConnectionType.SSL, 50002), //PHISHY new Server("electrum-ltc.bysh.me", Server.ConnectionType.SSL, 50002), new Server("backup.electrum-ltc.org", Server.ConnectionType.SSL, 443), + new Server("electrum.qortal.link", Server.ConnectionType.SSL, 50002), new Server("electrum.ltc.xurious.com", Server.ConnectionType.SSL, 50002), new Server("electrum-ltc.petrkr.net", Server.ConnectionType.SSL, 60002), new Server("electrum-ltc.qortal.online", Server.ConnectionType.SSL, 50002), diff --git a/src/main/java/org/qortal/crosschain/Ravencoin.java b/src/main/java/org/qortal/crosschain/Ravencoin.java index f571a141..6030fa50 100644 --- a/src/main/java/org/qortal/crosschain/Ravencoin.java +++ b/src/main/java/org/qortal/crosschain/Ravencoin.java @@ -48,6 +48,7 @@ public class Ravencoin extends Bitcoiny { //CLOSED new Server("aethyn.com", ConnectionType.SSL, 50002), //CLOSED new Server("electrum2.rvn.rocks", ConnectionType.SSL, 50002), //BEHIND new Server("electrum3.rvn.rocks", ConnectionType.SSL, 50002), + new Server("electrum.qortal.link", Server.ConnectionType.SSL, 56002), new Server("electrum-rvn.qortal.online", ConnectionType.SSL, 50002), new Server("electrum1-rvn.qortal.online", ConnectionType.SSL, 50002), new Server("electrum1.cipig.net", ConnectionType.SSL, 20051), diff --git a/src/main/java/org/qortal/data/chat/ActiveChats.java b/src/main/java/org/qortal/data/chat/ActiveChats.java index c546d637..d5ebcf3f 100644 --- a/src/main/java/org/qortal/data/chat/ActiveChats.java +++ b/src/main/java/org/qortal/data/chat/ActiveChats.java @@ -17,17 +17,21 @@ public class ActiveChats { private Long timestamp; private String sender; private String senderName; + private byte[] signature; + private byte[] data; protected GroupChat() { /* JAXB */ } - public GroupChat(int groupId, String groupName, Long timestamp, String sender, String senderName) { + public GroupChat(int groupId, String groupName, Long timestamp, String sender, String senderName, byte[] signature, byte[] data) { this.groupId = groupId; this.groupName = groupName; this.timestamp = timestamp; this.sender = sender; this.senderName = senderName; + this.signature = signature; + this.data = data; } public int getGroupId() { @@ -49,6 +53,14 @@ public class ActiveChats { public String getSenderName() { return this.senderName; } + + public byte[] getSignature() { + return this.signature; + } + + public byte[] getData() { + return this.data; + } } @XmlAccessorType(XmlAccessType.FIELD) @@ -118,4 +130,4 @@ public class ActiveChats { return this.direct; } -} +} \ No newline at end of file diff --git a/src/main/java/org/qortal/naming/Name.java b/src/main/java/org/qortal/naming/Name.java index ecf826a5..1751cca8 100644 --- a/src/main/java/org/qortal/naming/Name.java +++ b/src/main/java/org/qortal/naming/Name.java @@ -16,6 +16,8 @@ import org.qortal.repository.Repository; import org.qortal.transaction.Transaction.TransactionType; import org.qortal.utils.Unicode; +import java.util.Objects; + public class Name { // Properties @@ -116,7 +118,7 @@ public class Name { this.repository.getNameRepository().save(this.nameData); - if (!updateNameTransactionData.getNewName().isEmpty()) + if (!updateNameTransactionData.getNewName().isEmpty() && !Objects.equals(updateNameTransactionData.getName(), updateNameTransactionData.getNewName())) // Name has changed, delete old entry this.repository.getNameRepository().delete(updateNameTransactionData.getNewName()); diff --git a/src/main/java/org/qortal/repository/Bootstrap.java b/src/main/java/org/qortal/repository/Bootstrap.java index 626433e8..2d2605cc 100644 --- a/src/main/java/org/qortal/repository/Bootstrap.java +++ b/src/main/java/org/qortal/repository/Bootstrap.java @@ -279,7 +279,9 @@ public class Bootstrap { LOGGER.info("Generating checksum file..."); String checksum = Crypto.digestHexString(compressedOutputPath.toFile(), 1024*1024); + LOGGER.info("checksum: {}", checksum); Path checksumPath = Paths.get(String.format("%s.sha256", compressedOutputPath.toString())); + LOGGER.info("Writing checksum to path: {}", checksumPath); Files.writeString(checksumPath, checksum, StandardOpenOption.CREATE); // Return the path to the compressed bootstrap file diff --git a/src/main/java/org/qortal/repository/hsqldb/HSQLDBChatRepository.java b/src/main/java/org/qortal/repository/hsqldb/HSQLDBChatRepository.java index 08226d53..a995a0b3 100644 --- a/src/main/java/org/qortal/repository/hsqldb/HSQLDBChatRepository.java +++ b/src/main/java/org/qortal/repository/hsqldb/HSQLDBChatRepository.java @@ -177,11 +177,11 @@ public class HSQLDBChatRepository implements ChatRepository { private List getActiveGroupChats(String address) throws DataException { // Find groups where address is a member and potential latest message details - String groupsSql = "SELECT group_id, group_name, latest_timestamp, sender, sender_name " + String groupsSql = "SELECT group_id, group_name, latest_timestamp, sender, sender_name, signature, data " + "FROM GroupMembers " + "JOIN Groups USING (group_id) " + "LEFT OUTER JOIN LATERAL(" - + "SELECT created_when AS latest_timestamp, sender, name AS sender_name " + + "SELECT created_when AS latest_timestamp, sender, name AS sender_name, signature, data " + "FROM ChatTransactions " + "JOIN Transactions USING (signature) " + "LEFT OUTER JOIN Names AS SenderNames ON SenderNames.owner = sender " @@ -205,8 +205,10 @@ public class HSQLDBChatRepository implements ChatRepository { String sender = resultSet.getString(4); String senderName = resultSet.getString(5); + byte[] signature = resultSet.getBytes(6); + byte[] data = resultSet.getBytes(7); - GroupChat groupChat = new GroupChat(groupId, groupName, timestamp, sender, senderName); + GroupChat groupChat = new GroupChat(groupId, groupName, timestamp, sender, senderName, signature, data); groupChats.add(groupChat); } while (resultSet.next()); } @@ -215,7 +217,7 @@ public class HSQLDBChatRepository implements ChatRepository { } // We need different SQL to handle group-less chat - String grouplessSql = "SELECT created_when, sender, SenderNames.name " + String grouplessSql = "SELECT created_when, sender, SenderNames.name, signature, data " + "FROM ChatTransactions " + "JOIN Transactions USING (signature) " + "LEFT OUTER JOIN Names AS SenderNames ON SenderNames.owner = sender " @@ -228,15 +230,19 @@ public class HSQLDBChatRepository implements ChatRepository { Long timestamp = null; String sender = null; String senderName = null; + byte[] signature = null; + byte[] data = null; if (resultSet != null) { // We found a recipient-less, group-less CHAT message, so report its details timestamp = resultSet.getLong(1); sender = resultSet.getString(2); senderName = resultSet.getString(3); + signature = resultSet.getBytes(4); + data = resultSet.getBytes(5); } - GroupChat groupChat = new GroupChat(0, null, timestamp, sender, senderName); + GroupChat groupChat = new GroupChat(0, null, timestamp, sender, senderName, signature, data); groupChats.add(groupChat); } catch (SQLException e) { throw new DataException("Unable to fetch active group chats from repository", e); @@ -291,4 +297,4 @@ public class HSQLDBChatRepository implements ChatRepository { return directChats; } -} +} \ No newline at end of file diff --git a/src/main/java/org/qortal/settings/Settings.java b/src/main/java/org/qortal/settings/Settings.java index 5799bd26..ae5dc173 100644 --- a/src/main/java/org/qortal/settings/Settings.java +++ b/src/main/java/org/qortal/settings/Settings.java @@ -215,7 +215,7 @@ public class Settings { public long recoveryModeTimeout = 10 * 60 * 1000L; /** Minimum peer version number required in order to sync with them */ - private String minPeerVersion = "3.8.2"; + private String minPeerVersion = "3.8.7"; /** Whether to allow connections with peers below minPeerVersion * If true, we won't sync with them but they can still sync with us, and will show in the peers list * If false, sync will be blocked both ways, and they will not appear in the peers list */ diff --git a/src/main/java/org/qortal/transaction/CancelSellNameTransaction.java b/src/main/java/org/qortal/transaction/CancelSellNameTransaction.java index 788492a9..876f0aed 100644 --- a/src/main/java/org/qortal/transaction/CancelSellNameTransaction.java +++ b/src/main/java/org/qortal/transaction/CancelSellNameTransaction.java @@ -5,6 +5,7 @@ import java.util.List; import org.qortal.account.Account; import org.qortal.asset.Asset; +import org.qortal.controller.repository.NamesDatabaseIntegrityCheck; import org.qortal.data.naming.NameData; import org.qortal.data.transaction.CancelSellNameTransactionData; import org.qortal.data.transaction.TransactionData; @@ -81,7 +82,13 @@ public class CancelSellNameTransaction extends Transaction { @Override public void preProcess() throws DataException { - // Nothing to do + CancelSellNameTransactionData cancelSellNameTransactionData = (CancelSellNameTransactionData) transactionData; + + // Rebuild this name in the Names table from the transaction history + // This is necessary because in some rare cases names can be missing from the Names table after registration + // but we have been unable to reproduce the issue and track down the root cause + NamesDatabaseIntegrityCheck namesDatabaseIntegrityCheck = new NamesDatabaseIntegrityCheck(); + namesDatabaseIntegrityCheck.rebuildName(cancelSellNameTransactionData.getName(), this.repository); } @Override diff --git a/src/test/java/org/qortal/test/naming/UpdateTests.java b/src/test/java/org/qortal/test/naming/UpdateTests.java index 24af5317..54227e94 100644 --- a/src/test/java/org/qortal/test/naming/UpdateTests.java +++ b/src/test/java/org/qortal/test/naming/UpdateTests.java @@ -219,6 +219,65 @@ public class UpdateTests extends Common { } } + // Test that multiple UPDATE_NAME transactions work as expected, when using a matching name and newName string + @Test + public void testDoubleUpdateNameWithMatchingNewName() throws DataException { + try (final Repository repository = RepositoryManager.getRepository()) { + // Register-name + PrivateKeyAccount alice = Common.getTestAccount(repository, "alice"); + String name = "name"; + String reducedName = "name"; + String data = "{\"age\":30}"; + + TransactionData initialTransactionData = new RegisterNameTransactionData(TestTransaction.generateBase(alice), name, data); + initialTransactionData.setFee(new RegisterNameTransaction(null, null).getUnitFee(initialTransactionData.getTimestamp())); + TransactionUtils.signAndMint(repository, initialTransactionData, alice); + + // Check name exists + assertTrue(repository.getNameRepository().nameExists(name)); + assertNotNull(repository.getNameRepository().fromReducedName(reducedName)); + + // Update name + TransactionData middleTransactionData = new UpdateNameTransactionData(TestTransaction.generateBase(alice), name, name, data); + TransactionUtils.signAndMint(repository, middleTransactionData, alice); + + // Check name still exists + assertTrue(repository.getNameRepository().nameExists(name)); + assertNotNull(repository.getNameRepository().fromReducedName(reducedName)); + + // Update name again + TransactionData newestTransactionData = new UpdateNameTransactionData(TestTransaction.generateBase(alice), name, name, data); + TransactionUtils.signAndMint(repository, newestTransactionData, alice); + + // Check name still exists + assertTrue(repository.getNameRepository().nameExists(name)); + assertNotNull(repository.getNameRepository().fromReducedName(reducedName)); + + // Check updated timestamp is correct + assertEquals((Long) newestTransactionData.getTimestamp(), repository.getNameRepository().fromName(name).getUpdated()); + + // orphan and recheck + BlockUtils.orphanLastBlock(repository); + + // Check name still exists + assertTrue(repository.getNameRepository().nameExists(name)); + assertNotNull(repository.getNameRepository().fromReducedName(reducedName)); + + // Check updated timestamp is correct + assertEquals((Long) middleTransactionData.getTimestamp(), repository.getNameRepository().fromName(name).getUpdated()); + + // orphan and recheck + BlockUtils.orphanLastBlock(repository); + + // Check name still exists + assertTrue(repository.getNameRepository().nameExists(name)); + assertNotNull(repository.getNameRepository().fromReducedName(reducedName)); + + // Check updated timestamp is empty + assertNull(repository.getNameRepository().fromName(name).getUpdated()); + } + } + // Test that reverting using previous UPDATE_NAME works as expected @Test public void testIntermediateUpdateName() throws DataException {