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 {