From 5581b83c577f3406f593685a50e7f4a32d151883 Mon Sep 17 00:00:00 2001 From: CalDescent Date: Mon, 19 Sep 2022 11:03:06 +0100 Subject: [PATCH 01/19] Added initial admin approval features for groups owned by the null account. * The dev group (ID 1) is owned by the null account with public key 11111111111111111111111111111111 * To regain access to otherwise blocked owner-based rules, it has different validation logic * which applies to groups with this same null owner. * * The main difference is that approval is required for certain transaction types relating to * null-owned groups. This allows existing admins to approve updates to the group (using group's * approval threshold) instead of these actions being performed by the owner. * * Since these apply to all null-owned groups, this allows anyone to update their group to * the null owner if they want to take advantage of this decentralized approval system. * * Currently, the affected transaction types are: * - AddGroupAdminTransaction * - RemoveGroupAdminTransaction * * This same approach could ultimately be applied to other group transactions too. --- .../data/transaction/TransactionData.java | 4 + src/main/java/org/qortal/group/Group.java | 3 + .../transaction/AddGroupAdminTransaction.java | 10 +- .../RemoveGroupAdminTransaction.java | 11 +- .../org/qortal/transaction/Transaction.java | 19 +- .../qortal/test/group/DevGroupAdminTests.java | 388 ++++++++++++++++++ src/test/resources/test-chain-v2.json | 2 + 7 files changed, 423 insertions(+), 14 deletions(-) create mode 100644 src/test/java/org/qortal/test/group/DevGroupAdminTests.java diff --git a/src/main/java/org/qortal/data/transaction/TransactionData.java b/src/main/java/org/qortal/data/transaction/TransactionData.java index 060901f2..ec1139f4 100644 --- a/src/main/java/org/qortal/data/transaction/TransactionData.java +++ b/src/main/java/org/qortal/data/transaction/TransactionData.java @@ -128,6 +128,10 @@ public abstract class TransactionData { return this.txGroupId; } + public void setTxGroupId(int txGroupId) { + this.txGroupId = txGroupId; + } + public byte[] getReference() { return this.reference; } diff --git a/src/main/java/org/qortal/group/Group.java b/src/main/java/org/qortal/group/Group.java index 1dbb18b0..465743a9 100644 --- a/src/main/java/org/qortal/group/Group.java +++ b/src/main/java/org/qortal/group/Group.java @@ -80,6 +80,9 @@ public class Group { // Useful constants public static final int NO_GROUP = 0; + // Null owner address corresponds with public key "11111111111111111111111111111111" + public static String NULL_OWNER_ADDRESS = "QdSnUy6sUiEnaN87dWmE92g1uQjrvPgrWG"; + public static final int MIN_NAME_SIZE = 3; public static final int MAX_NAME_SIZE = 32; public static final int MAX_DESCRIPTION_SIZE = 128; diff --git a/src/main/java/org/qortal/transaction/AddGroupAdminTransaction.java b/src/main/java/org/qortal/transaction/AddGroupAdminTransaction.java index 15dc51bf..3cd9845d 100644 --- a/src/main/java/org/qortal/transaction/AddGroupAdminTransaction.java +++ b/src/main/java/org/qortal/transaction/AddGroupAdminTransaction.java @@ -2,6 +2,7 @@ package org.qortal.transaction; import java.util.Collections; import java.util.List; +import java.util.Objects; import org.qortal.account.Account; import org.qortal.asset.Asset; @@ -64,9 +65,14 @@ public class AddGroupAdminTransaction extends Transaction { Account owner = getOwner(); String groupOwner = this.repository.getGroupRepository().getOwner(groupId); + boolean groupOwnedByNullAccount = Objects.equals(groupOwner, Group.NULL_OWNER_ADDRESS); - // Check transaction's public key matches group's current owner - if (!owner.getAddress().equals(groupOwner)) + // Require approval if transaction relates to a group owned by the null account + if (groupOwnedByNullAccount && !this.needsGroupApproval()) + return ValidationResult.GROUP_APPROVAL_REQUIRED; + + // Check transaction's public key matches group's current owner (except for groups owned by the null account) + if (!groupOwnedByNullAccount && !owner.getAddress().equals(groupOwner)) return ValidationResult.INVALID_GROUP_OWNER; // Check address is a group member diff --git a/src/main/java/org/qortal/transaction/RemoveGroupAdminTransaction.java b/src/main/java/org/qortal/transaction/RemoveGroupAdminTransaction.java index 3e5f1e6d..8d538143 100644 --- a/src/main/java/org/qortal/transaction/RemoveGroupAdminTransaction.java +++ b/src/main/java/org/qortal/transaction/RemoveGroupAdminTransaction.java @@ -2,6 +2,7 @@ package org.qortal.transaction; import java.util.Collections; import java.util.List; +import java.util.Objects; import org.qortal.account.Account; import org.qortal.asset.Asset; @@ -65,9 +66,15 @@ public class RemoveGroupAdminTransaction extends Transaction { return ValidationResult.GROUP_DOES_NOT_EXIST; Account owner = getOwner(); + String groupOwner = this.repository.getGroupRepository().getOwner(groupId); + boolean groupOwnedByNullAccount = Objects.equals(groupOwner, Group.NULL_OWNER_ADDRESS); - // Check transaction's public key matches group's current owner - if (!owner.getAddress().equals(groupData.getOwner())) + // Require approval if transaction relates to a group owned by the null account + if (groupOwnedByNullAccount && !this.needsGroupApproval()) + return ValidationResult.GROUP_APPROVAL_REQUIRED; + + // Check transaction's public key matches group's current owner (except for groups owned by the null account) + if (!groupOwnedByNullAccount && !owner.getAddress().equals(groupOwner)) return ValidationResult.INVALID_GROUP_OWNER; Account admin = getAdmin(); diff --git a/src/main/java/org/qortal/transaction/Transaction.java b/src/main/java/org/qortal/transaction/Transaction.java index b56d48cf..203cc342 100644 --- a/src/main/java/org/qortal/transaction/Transaction.java +++ b/src/main/java/org/qortal/transaction/Transaction.java @@ -1,13 +1,7 @@ package org.qortal.transaction; import java.math.BigInteger; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Comparator; -import java.util.EnumSet; -import java.util.Iterator; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.concurrent.locks.ReentrantLock; import java.util.function.Predicate; @@ -69,8 +63,8 @@ public abstract class Transaction { AT(21, false), CREATE_GROUP(22, true), UPDATE_GROUP(23, true), - ADD_GROUP_ADMIN(24, false), - REMOVE_GROUP_ADMIN(25, false), + ADD_GROUP_ADMIN(24, true), + REMOVE_GROUP_ADMIN(25, true), GROUP_BAN(26, false), CANCEL_GROUP_BAN(27, false), GROUP_KICK(28, false), @@ -250,6 +244,7 @@ public abstract class Transaction { INVALID_TIMESTAMP_SIGNATURE(95), ADDRESS_BLOCKED(96), NAME_BLOCKED(97), + GROUP_APPROVAL_REQUIRED(98), INVALID_BUT_OK(999), NOT_YET_RELEASED(1000); @@ -760,9 +755,13 @@ public abstract class Transaction { // Group no longer exists? Possibly due to blockchain orphaning undoing group creation? return true; // stops tx being included in block but it will eventually expire + String groupOwner = this.repository.getGroupRepository().getOwner(txGroupId); + boolean groupOwnedByNullAccount = Objects.equals(groupOwner, Group.NULL_OWNER_ADDRESS); + // If transaction's creator is group admin (of group with ID txGroupId) then auto-approve + // This is disabled for null-owned groups, since these require approval from other admins PublicKeyAccount creator = this.getCreator(); - if (groupRepository.adminExists(txGroupId, creator.getAddress())) + if (!groupOwnedByNullAccount && groupRepository.adminExists(txGroupId, creator.getAddress())) return false; return true; diff --git a/src/test/java/org/qortal/test/group/DevGroupAdminTests.java b/src/test/java/org/qortal/test/group/DevGroupAdminTests.java new file mode 100644 index 00000000..131359c6 --- /dev/null +++ b/src/test/java/org/qortal/test/group/DevGroupAdminTests.java @@ -0,0 +1,388 @@ +package org.qortal.test.group; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.qortal.account.PrivateKeyAccount; +import org.qortal.data.transaction.*; +import org.qortal.group.Group; +import org.qortal.group.Group.ApprovalThreshold; +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.GroupUtils; +import org.qortal.test.common.TransactionUtils; +import org.qortal.test.common.transaction.TestTransaction; +import org.qortal.transaction.Transaction; +import org.qortal.transaction.Transaction.ValidationResult; +import org.qortal.utils.Base58; + +import static org.junit.Assert.*; + +/** + * Dev group admin tests + * + * The dev group (ID 1) is owned by the null account with public key 11111111111111111111111111111111 + * To regain access to otherwise blocked owner-based rules, it has different validation logic + * which applies to groups with this same null owner. + * + * The main difference is that approval is required for certain transaction types relating to + * null-owned groups. This allows existing admins to approve updates to the group (using group's + * approval threshold) instead of these actions being performed by the owner. + * + * Since these apply to all null-owned groups, this allows anyone to update their group to + * the null owner if they want to take advantage of this decentralized approval system. + * + * Currently, the affected transaction types are: + * - AddGroupAdminTransaction + * - RemoveGroupAdminTransaction + * + * This same approach could ultimately be applied to other group transactions too. + */ +public class DevGroupAdminTests extends Common { + + private static final int DEV_GROUP_ID = 1; + + @Before + public void beforeTest() throws DataException { + Common.useDefaultSettings(); + } + + @After + public void afterTest() throws DataException { + Common.orphanCheck(); + } + + @Test + public void testGroupKickMember() throws DataException { + try (final Repository repository = RepositoryManager.getRepository()) { + PrivateKeyAccount alice = Common.getTestAccount(repository, "alice"); + PrivateKeyAccount bob = Common.getTestAccount(repository, "bob"); + + // Dev group + int groupId = DEV_GROUP_ID; + + // Confirm Bob is not a member + assertFalse(isMember(repository, bob.getAddress(), groupId)); + + // Attempt to kick Bob + ValidationResult result = groupKick(repository, alice, groupId, bob.getAddress()); + // Should NOT be OK + assertNotSame(ValidationResult.OK, result); + + // Alice to invite Bob, as it's a closed group + groupInvite(repository, alice, groupId, bob.getAddress(), 3600); + + // Bob to join + joinGroup(repository, bob, groupId); + + // Confirm Bob now a member + assertTrue(isMember(repository, bob.getAddress(), groupId)); + + // Attempt to kick Bob + result = groupKick(repository, alice, groupId, bob.getAddress()); + // Should be OK + assertEquals(ValidationResult.OK, result); + + // Confirm Bob no longer a member + assertFalse(isMember(repository, bob.getAddress(), groupId)); + + // Orphan last block + BlockUtils.orphanLastBlock(repository); + + // Confirm Bob now a member + assertTrue(isMember(repository, bob.getAddress(), groupId)); + } + } + + @Test + public void testGroupKickAdmin() throws DataException { + try (final Repository repository = RepositoryManager.getRepository()) { + PrivateKeyAccount alice = Common.getTestAccount(repository, "alice"); + PrivateKeyAccount bob = Common.getTestAccount(repository, "bob"); + + // Dev group + int groupId = DEV_GROUP_ID; + + // Confirm Bob is not a member + assertFalse(isMember(repository, bob.getAddress(), groupId)); + + // Alice to invite Bob, as it's a closed group + groupInvite(repository, alice, groupId, bob.getAddress(), 3600); + + // Bob to join + joinGroup(repository, bob, groupId); + + // Confirm Bob now a member + assertTrue(isMember(repository, bob.getAddress(), groupId)); + + // Promote Bob to admin + TransactionData addGroupAdminTransactionData = addGroupAdmin(repository, alice, groupId, bob.getAddress()); + + // Confirm transaction needs approval, and hasn't been approved + Transaction.ApprovalStatus approvalStatus = GroupUtils.getApprovalStatus(repository, addGroupAdminTransactionData.getSignature()); + assertEquals("incorrect transaction approval status", Transaction.ApprovalStatus.PENDING, approvalStatus); + + // Have Alice approve Bob's approval-needed transaction + GroupUtils.approveTransaction(repository, "alice", addGroupAdminTransactionData.getSignature(), true); + + // Mint a block so that the transaction becomes approved + BlockUtils.mintBlock(repository); + + // Confirm transaction is approved + approvalStatus = GroupUtils.getApprovalStatus(repository, addGroupAdminTransactionData.getSignature()); + assertEquals("incorrect transaction approval status", Transaction.ApprovalStatus.APPROVED, approvalStatus); + + // Confirm Bob is now admin + assertTrue(isAdmin(repository, bob.getAddress(), groupId)); + + // Attempt to kick Bob + ValidationResult result = groupKick(repository, alice, groupId, bob.getAddress()); + // Shouldn't be allowed + assertEquals(ValidationResult.INVALID_GROUP_OWNER, result); + + // Confirm Bob is still a member + assertTrue(isMember(repository, bob.getAddress(), groupId)); + + // Confirm Bob still an admin + assertTrue(isAdmin(repository, bob.getAddress(), groupId)); + + // Orphan last block + BlockUtils.orphanLastBlock(repository); + + // Confirm Bob no longer an admin (ADD_GROUP_ADMIN no longer approved) + assertFalse(isAdmin(repository, bob.getAddress(), groupId)); + + // Have Alice try to kick herself! + result = groupKick(repository, alice, groupId, alice.getAddress()); + // Should NOT be OK + assertNotSame(ValidationResult.OK, result); + + // Have Bob try to kick Alice + result = groupKick(repository, bob, groupId, alice.getAddress()); + // Should NOT be OK + assertNotSame(ValidationResult.OK, result); + } + } + + @Test + public void testGroupBanMember() throws DataException { + try (final Repository repository = RepositoryManager.getRepository()) { + PrivateKeyAccount alice = Common.getTestAccount(repository, "alice"); + PrivateKeyAccount bob = Common.getTestAccount(repository, "bob"); + + // Dev group + int groupId = DEV_GROUP_ID; + + // Confirm Bob is not a member + assertFalse(isMember(repository, bob.getAddress(), groupId)); + + // Attempt to cancel non-existent Bob ban + ValidationResult result = cancelGroupBan(repository, alice, groupId, bob.getAddress()); + // Should NOT be OK + assertNotSame(ValidationResult.OK, result); + + // Attempt to ban Bob + result = groupBan(repository, alice, groupId, bob.getAddress()); + // Should be OK + assertEquals(ValidationResult.OK, result); + + // Bob attempts to rejoin + result = joinGroup(repository, bob, groupId); + // Should NOT be OK + assertNotSame(ValidationResult.OK, result); + + // Orphan last block (Bob ban) + BlockUtils.orphanLastBlock(repository); + // Delete unconfirmed group-ban transaction + TransactionUtils.deleteUnconfirmedTransactions(repository); + + // Confirm Bob is not a member + assertFalse(isMember(repository, bob.getAddress(), groupId)); + + // Alice to invite Bob, as it's a closed group + groupInvite(repository, alice, groupId, bob.getAddress(), 3600); + + // Bob to join + result = joinGroup(repository, bob, groupId); + // Should be OK + assertEquals(ValidationResult.OK, result); + + // Confirm Bob now a member + assertTrue(isMember(repository, bob.getAddress(), groupId)); + + // Attempt to ban Bob + result = groupBan(repository, alice, groupId, bob.getAddress()); + // Should be OK + assertEquals(ValidationResult.OK, result); + + // Confirm Bob no longer a member + assertFalse(isMember(repository, bob.getAddress(), groupId)); + + // Bob attempts to rejoin + result = joinGroup(repository, bob, groupId); + // Should NOT be OK + assertNotSame(ValidationResult.OK, result); + + // Cancel Bob's ban + result = cancelGroupBan(repository, alice, groupId, bob.getAddress()); + // Should be OK + assertEquals(ValidationResult.OK, result); + + // Bob attempts to rejoin + result = joinGroup(repository, bob, groupId); + // Should be OK + assertEquals(ValidationResult.OK, result); + + // Orphan last block (Bob join) + BlockUtils.orphanLastBlock(repository); + // Delete unconfirmed join-group transaction + TransactionUtils.deleteUnconfirmedTransactions(repository); + + // Orphan last block (Cancel Bob ban) + BlockUtils.orphanLastBlock(repository); + // Delete unconfirmed cancel-ban transaction + TransactionUtils.deleteUnconfirmedTransactions(repository); + + // Bob attempts to rejoin + result = joinGroup(repository, bob, groupId); + // Should NOT be OK + assertNotSame(ValidationResult.OK, result); + + // Orphan last block (Bob ban) + BlockUtils.orphanLastBlock(repository); + // Delete unconfirmed group-ban transaction + TransactionUtils.deleteUnconfirmedTransactions(repository); + + // Confirm Bob now a member + assertTrue(isMember(repository, bob.getAddress(), groupId)); + } + } + + @Test + public void testGroupBanAdmin() throws DataException { + try (final Repository repository = RepositoryManager.getRepository()) { + PrivateKeyAccount alice = Common.getTestAccount(repository, "alice"); + PrivateKeyAccount bob = Common.getTestAccount(repository, "bob"); + + // Dev group + int groupId = DEV_GROUP_ID; + + // Confirm Bob is not a member + assertFalse(isMember(repository, bob.getAddress(), groupId)); + + // Alice to invite Bob, as it's a closed group + groupInvite(repository, alice, groupId, bob.getAddress(), 3600); + + // Bob to join + ValidationResult result = joinGroup(repository, bob, groupId); + // Should be OK + assertEquals(ValidationResult.OK, result); + + // Promote Bob to admin + TransactionData addGroupAdminTransactionData = addGroupAdmin(repository, alice, groupId, bob.getAddress()); + + // Confirm transaction needs approval, and hasn't been approved + Transaction.ApprovalStatus approvalStatus = GroupUtils.getApprovalStatus(repository, addGroupAdminTransactionData.getSignature()); + assertEquals("incorrect transaction approval status", Transaction.ApprovalStatus.PENDING, approvalStatus); + + // Have Alice approve Bob's approval-needed transaction + GroupUtils.approveTransaction(repository, "alice", addGroupAdminTransactionData.getSignature(), true); + + // Mint a block so that the transaction becomes approved + BlockUtils.mintBlock(repository); + + // Confirm transaction is approved + approvalStatus = GroupUtils.getApprovalStatus(repository, addGroupAdminTransactionData.getSignature()); + assertEquals("incorrect transaction approval status", Transaction.ApprovalStatus.APPROVED, approvalStatus); + + // Confirm Bob is now admin + assertTrue(isAdmin(repository, bob.getAddress(), groupId)); + + // Attempt to ban Bob + result = groupBan(repository, alice, groupId, bob.getAddress()); + // .. but we can't, because Bob is an admin and the group has no owner + assertEquals(ValidationResult.INVALID_GROUP_OWNER, result); + + // Confirm Bob still a member + assertTrue(isMember(repository, bob.getAddress(), groupId)); + + // ... and still an admin + assertTrue(isAdmin(repository, bob.getAddress(), groupId)); + + // Have Alice try to ban herself! + result = groupBan(repository, alice, groupId, alice.getAddress()); + // Should NOT be OK + assertNotSame(ValidationResult.OK, result); + + // Have Bob try to ban Alice + result = groupBan(repository, bob, groupId, alice.getAddress()); + // Should NOT be OK + assertNotSame(ValidationResult.OK, result); + } + } + + + private ValidationResult joinGroup(Repository repository, PrivateKeyAccount joiner, int groupId) throws DataException { + JoinGroupTransactionData transactionData = new JoinGroupTransactionData(TestTransaction.generateBase(joiner), groupId); + ValidationResult result = TransactionUtils.signAndImport(repository, transactionData, joiner); + + if (result == ValidationResult.OK) + BlockUtils.mintBlock(repository); + + return result; + } + + private void groupInvite(Repository repository, PrivateKeyAccount admin, int groupId, String invitee, int timeToLive) throws DataException { + GroupInviteTransactionData transactionData = new GroupInviteTransactionData(TestTransaction.generateBase(admin), groupId, invitee, timeToLive); + TransactionUtils.signAndMint(repository, transactionData, admin); + } + + private ValidationResult groupKick(Repository repository, PrivateKeyAccount admin, int groupId, String member) throws DataException { + GroupKickTransactionData transactionData = new GroupKickTransactionData(TestTransaction.generateBase(admin), groupId, member, "testing"); + ValidationResult result = TransactionUtils.signAndImport(repository, transactionData, admin); + + if (result == ValidationResult.OK) + BlockUtils.mintBlock(repository); + + return result; + } + + private ValidationResult groupBan(Repository repository, PrivateKeyAccount admin, int groupId, String member) throws DataException { + GroupBanTransactionData transactionData = new GroupBanTransactionData(TestTransaction.generateBase(admin), groupId, member, "testing", 0); + ValidationResult result = TransactionUtils.signAndImport(repository, transactionData, admin); + + if (result == ValidationResult.OK) + BlockUtils.mintBlock(repository); + + return result; + } + + private ValidationResult cancelGroupBan(Repository repository, PrivateKeyAccount admin, int groupId, String member) throws DataException { + CancelGroupBanTransactionData transactionData = new CancelGroupBanTransactionData(TestTransaction.generateBase(admin), groupId, member); + ValidationResult result = TransactionUtils.signAndImport(repository, transactionData, admin); + + if (result == ValidationResult.OK) + BlockUtils.mintBlock(repository); + + return result; + } + + private TransactionData addGroupAdmin(Repository repository, PrivateKeyAccount owner, int groupId, String member) throws DataException { + AddGroupAdminTransactionData transactionData = new AddGroupAdminTransactionData(TestTransaction.generateBase(owner), groupId, member); + transactionData.setTxGroupId(groupId); + TransactionUtils.signAndMint(repository, transactionData, owner); + return transactionData; + } + + private boolean isMember(Repository repository, String address, int groupId) throws DataException { + return repository.getGroupRepository().memberExists(groupId, address); + } + + private boolean isAdmin(Repository repository, String address, int groupId) throws DataException { + return repository.getGroupRepository().adminExists(groupId, address); + } + +} diff --git a/src/test/resources/test-chain-v2.json b/src/test/resources/test-chain-v2.json index 5f439602..e3a2f4f2 100644 --- a/src/test/resources/test-chain-v2.json +++ b/src/test/resources/test-chain-v2.json @@ -90,6 +90,8 @@ { "type": "CREATE_GROUP", "creatorPublicKey": "2tiMr5LTpaWCgbRvkPK8TFd7k63DyHJMMFFsz9uBf1ZP", "groupName": "dev-group", "description": "developer group", "isOpen": false, "approvalThreshold": "PCT100", "minimumBlockDelay": 0, "maximumBlockDelay": 1440 }, + { "type": "UPDATE_GROUP", "ownerPublicKey": "2tiMr5LTpaWCgbRvkPK8TFd7k63DyHJMMFFsz9uBf1ZP", "groupId": 1, "newOwner": "QdSnUy6sUiEnaN87dWmE92g1uQjrvPgrWG", "newDescription": "developer group", "newIsOpen": false, "newApprovalThreshold": "PCT40", "minimumBlockDelay": 10, "maximumBlockDelay": 1440 }, + { "type": "ISSUE_ASSET", "issuerPublicKey": "2tiMr5LTpaWCgbRvkPK8TFd7k63DyHJMMFFsz9uBf1ZP", "assetName": "TEST", "description": "test asset", "data": "", "quantity": "1000000", "isDivisible": true, "fee": 0 }, { "type": "ISSUE_ASSET", "issuerPublicKey": "C6wuddsBV3HzRrXUtezE7P5MoRXp5m3mEDokRDGZB6ry", "assetName": "OTHER", "description": "other test asset", "data": "", "quantity": "1000000", "isDivisible": true, "fee": 0 }, { "type": "ISSUE_ASSET", "issuerPublicKey": "2tiMr5LTpaWCgbRvkPK8TFd7k63DyHJMMFFsz9uBf1ZP", "assetName": "GOLD", "description": "gold test asset", "data": "", "quantity": "1000000", "isDivisible": true, "fee": 0 }, From 93fd80e289b9923e348c87bcd4e089d340215cb2 Mon Sep 17 00:00:00 2001 From: CalDescent Date: Mon, 19 Sep 2022 16:34:31 +0100 Subject: [PATCH 02/19] Require that add/remove admin transactions can only be created by group members. For regular groups, we require that the owner adds/removes the admins, so group membership is adequately checked. However for null-owned groups this check is skipped. So we need an additional condition to prevent non-group members from issuing a transaction for approval by the group admins. --- .../java/org/qortal/transaction/AddGroupAdminTransaction.java | 4 ++++ .../org/qortal/transaction/RemoveGroupAdminTransaction.java | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/src/main/java/org/qortal/transaction/AddGroupAdminTransaction.java b/src/main/java/org/qortal/transaction/AddGroupAdminTransaction.java index 3cd9845d..f38638c5 100644 --- a/src/main/java/org/qortal/transaction/AddGroupAdminTransaction.java +++ b/src/main/java/org/qortal/transaction/AddGroupAdminTransaction.java @@ -79,6 +79,10 @@ public class AddGroupAdminTransaction extends Transaction { if (!this.repository.getGroupRepository().memberExists(groupId, memberAddress)) return ValidationResult.NOT_GROUP_MEMBER; + // Check transaction creator is a group member + if (!this.repository.getGroupRepository().memberExists(groupId, this.getCreator().getAddress())) + return ValidationResult.NOT_GROUP_MEMBER; + // Check group member is not already an admin if (this.repository.getGroupRepository().adminExists(groupId, memberAddress)) return ValidationResult.ALREADY_GROUP_ADMIN; diff --git a/src/main/java/org/qortal/transaction/RemoveGroupAdminTransaction.java b/src/main/java/org/qortal/transaction/RemoveGroupAdminTransaction.java index 8d538143..043b5423 100644 --- a/src/main/java/org/qortal/transaction/RemoveGroupAdminTransaction.java +++ b/src/main/java/org/qortal/transaction/RemoveGroupAdminTransaction.java @@ -77,6 +77,10 @@ public class RemoveGroupAdminTransaction extends Transaction { if (!groupOwnedByNullAccount && !owner.getAddress().equals(groupOwner)) return ValidationResult.INVALID_GROUP_OWNER; + // Check transaction creator is a group member + if (!this.repository.getGroupRepository().memberExists(groupId, this.getCreator().getAddress())) + return ValidationResult.NOT_GROUP_MEMBER; + Account admin = getAdmin(); // Check member is an admin From b3273ff01a8652e536507781eee64eff100c6b8a Mon Sep 17 00:00:00 2001 From: CalDescent Date: Sun, 23 Oct 2022 16:47:42 +0100 Subject: [PATCH 03/19] Removed all mempow feature trigger conditionals. We no longer need all the code complexity, now that 24 hours have passed since activation. We don't validate online accounts beyond 12 hours, and the data is trimmed after 24 hours. --- src/main/java/org/qortal/block/Block.java | 102 ++++++++---------- .../java/org/qortal/block/BlockChain.java | 8 -- .../controller/OnlineAccountsManager.java | 62 +++-------- .../transform/block/BlockTransformer.java | 12 +-- src/main/resources/blockchain.json | 1 - .../org/qortal/test/common/AccountUtils.java | 4 +- 6 files changed, 62 insertions(+), 127 deletions(-) diff --git a/src/main/java/org/qortal/block/Block.java b/src/main/java/org/qortal/block/Block.java index 07c7db6f..55c13b36 100644 --- a/src/main/java/org/qortal/block/Block.java +++ b/src/main/java/org/qortal/block/Block.java @@ -366,14 +366,9 @@ public class Block { long timestamp = calcTimestamp(parentBlockData, minter.getPublicKey(), minterLevel); long onlineAccountsTimestamp = OnlineAccountsManager.getCurrentOnlineAccountTimestamp(); - // Fetch our list of online accounts + // Fetch our list of online accounts, removing any that are missing a nonce List onlineAccounts = OnlineAccountsManager.getInstance().getOnlineAccounts(onlineAccountsTimestamp); - - // If mempow is active, remove any legacy accounts that are missing a nonce - if (timestamp >= BlockChain.getInstance().getOnlineAccountsMemoryPoWTimestamp()) { - onlineAccounts.removeIf(a -> a.getNonce() == null || a.getNonce() < 0); - } - + onlineAccounts.removeIf(a -> a.getNonce() == null || a.getNonce() < 0); if (onlineAccounts.isEmpty()) { LOGGER.debug("No online accounts - not even our own?"); return null; @@ -412,29 +407,27 @@ public class Block { // Aggregated, single signature byte[] onlineAccountsSignatures = Qortal25519Extras.aggregateSignatures(signaturesToAggregate); - // Add nonces to the end of the online accounts signatures if mempow is active - if (timestamp >= BlockChain.getInstance().getOnlineAccountsMemoryPoWTimestamp()) { - try { - // Create ordered list of nonce values - List nonces = new ArrayList<>(); - for (int i = 0; i < onlineAccountsCount; ++i) { - Integer accountIndex = accountIndexes.get(i); - OnlineAccountData onlineAccountData = indexedOnlineAccounts.get(accountIndex); - nonces.add(onlineAccountData.getNonce()); - } - - // Encode the nonces to a byte array - byte[] encodedNonces = BlockTransformer.encodeOnlineAccountNonces(nonces); - - // Append the encoded nonces to the encoded online account signatures - ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); - outputStream.write(onlineAccountsSignatures); - outputStream.write(encodedNonces); - onlineAccountsSignatures = outputStream.toByteArray(); - } - catch (TransformationException | IOException e) { - return null; + // Add nonces to the end of the online accounts signatures + try { + // Create ordered list of nonce values + List nonces = new ArrayList<>(); + for (int i = 0; i < onlineAccountsCount; ++i) { + Integer accountIndex = accountIndexes.get(i); + OnlineAccountData onlineAccountData = indexedOnlineAccounts.get(accountIndex); + nonces.add(onlineAccountData.getNonce()); } + + // Encode the nonces to a byte array + byte[] encodedNonces = BlockTransformer.encodeOnlineAccountNonces(nonces); + + // Append the encoded nonces to the encoded online account signatures + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + outputStream.write(onlineAccountsSignatures); + outputStream.write(encodedNonces); + onlineAccountsSignatures = outputStream.toByteArray(); + } + catch (TransformationException | IOException e) { + return null; } byte[] minterSignature = minter.sign(BlockTransformer.getBytesForMinterSignature(parentBlockData, @@ -1047,14 +1040,9 @@ public class Block { final int signaturesLength = Transformer.SIGNATURE_LENGTH; final int noncesLength = onlineRewardShares.size() * Transformer.INT_LENGTH; - if (this.blockData.getTimestamp() >= BlockChain.getInstance().getOnlineAccountsMemoryPoWTimestamp()) { - // We expect nonces to be appended to the online accounts signatures - if (this.blockData.getOnlineAccountsSignatures().length != signaturesLength + noncesLength) - return ValidationResult.ONLINE_ACCOUNT_SIGNATURES_MALFORMED; - } else { - if (this.blockData.getOnlineAccountsSignatures().length != signaturesLength) - return ValidationResult.ONLINE_ACCOUNT_SIGNATURES_MALFORMED; - } + // We expect nonces to be appended to the online accounts signatures + if (this.blockData.getOnlineAccountsSignatures().length != signaturesLength + noncesLength) + return ValidationResult.ONLINE_ACCOUNT_SIGNATURES_MALFORMED; // Check signatures long onlineTimestamp = this.blockData.getOnlineAccountsTimestamp(); @@ -1063,32 +1051,30 @@ public class Block { byte[] encodedOnlineAccountSignatures = this.blockData.getOnlineAccountsSignatures(); // Split online account signatures into signature(s) + nonces, then validate the nonces - if (this.blockData.getTimestamp() >= BlockChain.getInstance().getOnlineAccountsMemoryPoWTimestamp()) { - byte[] extractedSignatures = BlockTransformer.extract(encodedOnlineAccountSignatures, 0, signaturesLength); - byte[] extractedNonces = BlockTransformer.extract(encodedOnlineAccountSignatures, signaturesLength, onlineRewardShares.size() * Transformer.INT_LENGTH); - encodedOnlineAccountSignatures = extractedSignatures; + byte[] extractedSignatures = BlockTransformer.extract(encodedOnlineAccountSignatures, 0, signaturesLength); + byte[] extractedNonces = BlockTransformer.extract(encodedOnlineAccountSignatures, signaturesLength, onlineRewardShares.size() * Transformer.INT_LENGTH); + encodedOnlineAccountSignatures = extractedSignatures; - List nonces = BlockTransformer.decodeOnlineAccountNonces(extractedNonces); + List nonces = BlockTransformer.decodeOnlineAccountNonces(extractedNonces); - // Build block's view of online accounts (without signatures, as we don't need them here) - Set onlineAccounts = new HashSet<>(); - for (int i = 0; i < onlineRewardShares.size(); ++i) { - Integer nonce = nonces.get(i); - byte[] publicKey = onlineRewardShares.get(i).getRewardSharePublicKey(); + // Build block's view of online accounts (without signatures, as we don't need them here) + Set onlineAccounts = new HashSet<>(); + for (int i = 0; i < onlineRewardShares.size(); ++i) { + Integer nonce = nonces.get(i); + byte[] publicKey = onlineRewardShares.get(i).getRewardSharePublicKey(); - OnlineAccountData onlineAccountData = new OnlineAccountData(onlineTimestamp, null, publicKey, nonce); - onlineAccounts.add(onlineAccountData); - } - - // Remove those already validated & cached by online accounts manager - no need to re-validate them - OnlineAccountsManager.getInstance().removeKnown(onlineAccounts, onlineTimestamp); - - // Validate the rest - for (OnlineAccountData onlineAccount : onlineAccounts) - if (!OnlineAccountsManager.getInstance().verifyMemoryPoW(onlineAccount, this.blockData.getTimestamp())) - return ValidationResult.ONLINE_ACCOUNT_NONCE_INCORRECT; + OnlineAccountData onlineAccountData = new OnlineAccountData(onlineTimestamp, null, publicKey, nonce); + onlineAccounts.add(onlineAccountData); } + // Remove those already validated & cached by online accounts manager - no need to re-validate them + OnlineAccountsManager.getInstance().removeKnown(onlineAccounts, onlineTimestamp); + + // Validate the rest + for (OnlineAccountData onlineAccount : onlineAccounts) + if (!OnlineAccountsManager.getInstance().verifyMemoryPoW(onlineAccount, this.blockData.getTimestamp())) + return ValidationResult.ONLINE_ACCOUNT_NONCE_INCORRECT; + // Extract online accounts' timestamp signatures from block data. Only one signature if aggregated. List onlineAccountsSignatures = BlockTransformer.decodeTimestampSignatures(encodedOnlineAccountSignatures); diff --git a/src/main/java/org/qortal/block/BlockChain.java b/src/main/java/org/qortal/block/BlockChain.java index 42692a18..826fdd78 100644 --- a/src/main/java/org/qortal/block/BlockChain.java +++ b/src/main/java/org/qortal/block/BlockChain.java @@ -195,10 +195,6 @@ public class BlockChain { * featureTriggers because unit tests need to set this value via Reflection. */ private long onlineAccountsModulusV2Timestamp; - /** Feature trigger timestamp for online accounts mempow verification. Can't use featureTriggers - * because unit tests need to set this value via Reflection. */ - private long onlineAccountsMemoryPoWTimestamp; - /** Max reward shares by block height */ public static class MaxRewardSharesByTimestamp { public long timestamp; @@ -359,10 +355,6 @@ public class BlockChain { return this.onlineAccountsModulusV2Timestamp; } - public long getOnlineAccountsMemoryPoWTimestamp() { - return this.onlineAccountsMemoryPoWTimestamp; - } - /** Returns true if approval-needing transaction types require a txGroupId other than NO_GROUP. */ public boolean getRequireGroupForApproval() { return this.requireGroupForApproval; diff --git a/src/main/java/org/qortal/controller/OnlineAccountsManager.java b/src/main/java/org/qortal/controller/OnlineAccountsManager.java index 45b47f5d..0e24bdfc 100644 --- a/src/main/java/org/qortal/controller/OnlineAccountsManager.java +++ b/src/main/java/org/qortal/controller/OnlineAccountsManager.java @@ -20,7 +20,6 @@ import org.qortal.network.message.*; import org.qortal.repository.DataException; import org.qortal.repository.Repository; import org.qortal.repository.RepositoryManager; -import org.qortal.settings.Settings; import org.qortal.utils.Base58; import org.qortal.utils.NTP; import org.qortal.utils.NamedThreadFactory; @@ -156,7 +155,6 @@ public class OnlineAccountsManager { return; byte[] timestampBytes = Longs.toByteArray(onlineAccountsTimestamp); - final boolean mempowActive = onlineAccountsTimestamp >= BlockChain.getInstance().getOnlineAccountsMemoryPoWTimestamp(); Set replacementAccounts = new HashSet<>(); for (PrivateKeyAccount onlineAccount : onlineAccounts) { @@ -165,7 +163,7 @@ public class OnlineAccountsManager { byte[] signature = Qortal25519Extras.signForAggregation(onlineAccount.getPrivateKey(), timestampBytes); byte[] publicKey = onlineAccount.getPublicKey(); - Integer nonce = mempowActive ? new Random().nextInt(500000) : null; + Integer nonce = new Random().nextInt(500000); OnlineAccountData ourOnlineAccountData = new OnlineAccountData(onlineAccountsTimestamp, signature, publicKey, nonce); replacementAccounts.add(ourOnlineAccountData); @@ -321,13 +319,10 @@ public class OnlineAccountsManager { return false; } - // Validate mempow if feature trigger is active (or if online account's timestamp is past the trigger timestamp) - long memoryPoWStartTimestamp = BlockChain.getInstance().getOnlineAccountsMemoryPoWTimestamp(); - if (now >= memoryPoWStartTimestamp || onlineAccountTimestamp >= memoryPoWStartTimestamp) { - if (!getInstance().verifyMemoryPoW(onlineAccountData, now)) { - LOGGER.trace(() -> String.format("Rejecting online reward-share for account %s due to invalid PoW nonce", mintingAccount.getAddress())); - return false; - } + // Validate mempow + if (!getInstance().verifyMemoryPoW(onlineAccountData, now)) { + LOGGER.trace(() -> String.format("Rejecting online reward-share for account %s due to invalid PoW nonce", mintingAccount.getAddress())); + return false; } return true; @@ -471,12 +466,10 @@ public class OnlineAccountsManager { // 'next' timestamp (prioritize this as it's the most important, if mempow active) final long nextOnlineAccountsTimestamp = toOnlineAccountTimestamp(now) + getOnlineTimestampModulus(); - if (isMemoryPoWActive(now)) { - boolean success = computeOurAccountsForTimestamp(nextOnlineAccountsTimestamp); - if (!success) { - // We didn't compute the required nonce value(s), and so can't proceed until they have been retried - return; - } + boolean success = computeOurAccountsForTimestamp(nextOnlineAccountsTimestamp); + if (!success) { + // We didn't compute the required nonce value(s), and so can't proceed until they have been retried + return; } // 'current' timestamp @@ -553,21 +546,15 @@ public class OnlineAccountsManager { // Compute nonce Integer nonce; - if (isMemoryPoWActive(NTP.getTime())) { - try { - nonce = this.computeMemoryPoW(mempowBytes, publicKey, onlineAccountsTimestamp); - if (nonce == null) { - // A nonce is required - return false; - } - } catch (TimeoutException e) { - LOGGER.info(String.format("Timed out computing nonce for account %.8s", Base58.encode(publicKey))); + try { + nonce = this.computeMemoryPoW(mempowBytes, publicKey, onlineAccountsTimestamp); + if (nonce == null) { + // A nonce is required return false; } - } - else { - // Send -1 if we haven't computed a nonce due to feature trigger timestamp - nonce = -1; + } catch (TimeoutException e) { + LOGGER.info(String.format("Timed out computing nonce for account %.8s", Base58.encode(publicKey))); + return false; } byte[] signature = Qortal25519Extras.signForAggregation(privateKey, timestampBytes); @@ -599,12 +586,6 @@ public class OnlineAccountsManager { // MemoryPoW - private boolean isMemoryPoWActive(Long timestamp) { - if (timestamp >= BlockChain.getInstance().getOnlineAccountsMemoryPoWTimestamp()) { - return true; - } - return false; - } private byte[] getMemoryPoWBytes(byte[] publicKey, long onlineAccountsTimestamp) throws IOException { byte[] timestampBytes = Longs.toByteArray(onlineAccountsTimestamp); @@ -616,11 +597,6 @@ public class OnlineAccountsManager { } private Integer computeMemoryPoW(byte[] bytes, byte[] publicKey, long onlineAccountsTimestamp) throws TimeoutException { - if (!isMemoryPoWActive(NTP.getTime())) { - LOGGER.info("Mempow start timestamp not yet reached"); - return null; - } - LOGGER.info(String.format("Computing nonce for account %.8s and timestamp %d...", Base58.encode(publicKey), onlineAccountsTimestamp)); // Calculate the time until the next online timestamp and use it as a timeout when computing the nonce @@ -643,12 +619,6 @@ public class OnlineAccountsManager { } public boolean verifyMemoryPoW(OnlineAccountData onlineAccountData, Long timestamp) { - long memoryPoWStartTimestamp = BlockChain.getInstance().getOnlineAccountsMemoryPoWTimestamp(); - if (timestamp < memoryPoWStartTimestamp && onlineAccountData.getTimestamp() < memoryPoWStartTimestamp) { - // Not active yet, so treat it as valid - return true; - } - // Require a valid nonce value if (onlineAccountData.getNonce() == null || onlineAccountData.getNonce() < 0) { return false; diff --git a/src/main/java/org/qortal/transform/block/BlockTransformer.java b/src/main/java/org/qortal/transform/block/BlockTransformer.java index 9e02a6f5..c97aa090 100644 --- a/src/main/java/org/qortal/transform/block/BlockTransformer.java +++ b/src/main/java/org/qortal/transform/block/BlockTransformer.java @@ -235,7 +235,7 @@ public class BlockTransformer extends Transformer { // Online accounts timestamp is only present if there are also signatures onlineAccountsTimestamp = byteBuffer.getLong(); - final int signaturesByteLength = getOnlineAccountSignaturesLength(onlineAccountsSignaturesCount, onlineAccountsCount, timestamp); + final int signaturesByteLength = (onlineAccountsSignaturesCount * Transformer.SIGNATURE_LENGTH) + (onlineAccountsCount * INT_LENGTH); if (signaturesByteLength > BlockChain.getInstance().getMaxBlockSize()) throw new TransformationException("Byte data too long for online accounts signatures"); @@ -511,16 +511,6 @@ public class BlockTransformer extends Transformer { return nonces; } - public static int getOnlineAccountSignaturesLength(int onlineAccountsSignaturesCount, int onlineAccountCount, long blockTimestamp) { - if (blockTimestamp >= BlockChain.getInstance().getOnlineAccountsMemoryPoWTimestamp()) { - // Once mempow is active, we expect the online account signatures to be appended with the nonce values - return (onlineAccountsSignaturesCount * Transformer.SIGNATURE_LENGTH) + (onlineAccountCount * INT_LENGTH); - } - else { - // Before mempow, only the online account signatures were included (which will likely be a single signature) - return onlineAccountsSignaturesCount * Transformer.SIGNATURE_LENGTH; - } - } public static byte[] extract(byte[] input, int pos, int length) { byte[] output = new byte[length]; diff --git a/src/main/resources/blockchain.json b/src/main/resources/blockchain.json index e9f1500d..893add5e 100644 --- a/src/main/resources/blockchain.json +++ b/src/main/resources/blockchain.json @@ -24,7 +24,6 @@ "onlineAccountSignaturesMinLifetime": 43200000, "onlineAccountSignaturesMaxLifetime": 86400000, "onlineAccountsModulusV2Timestamp": 1659801600000, - "onlineAccountsMemoryPoWTimestamp": 1666454400000, "rewardsByHeight": [ { "height": 1, "reward": 5.00 }, { "height": 259201, "reward": 4.75 }, diff --git a/src/test/java/org/qortal/test/common/AccountUtils.java b/src/test/java/org/qortal/test/common/AccountUtils.java index 0d0b6d6a..0d8baae2 100644 --- a/src/test/java/org/qortal/test/common/AccountUtils.java +++ b/src/test/java/org/qortal/test/common/AccountUtils.java @@ -124,8 +124,6 @@ public class AccountUtils { long timestamp = System.currentTimeMillis(); byte[] timestampBytes = Longs.toByteArray(timestamp); - final boolean mempowActive = timestamp >= BlockChain.getInstance().getOnlineAccountsMemoryPoWTimestamp(); - for (int a = 0; a < numAccounts; ++a) { byte[] privateKey = new byte[Transformer.PUBLIC_KEY_LENGTH]; SECURE_RANDOM.nextBytes(privateKey); @@ -135,7 +133,7 @@ public class AccountUtils { byte[] signature = signForAggregation(privateKey, timestampBytes); - Integer nonce = mempowActive ? new Random().nextInt(500000) : null; + Integer nonce = new Random().nextInt(500000); onlineAccounts.add(new OnlineAccountData(timestamp, signature, publicKey, nonce)); } From f83d4bac7b054b73f5ec42a8fbc6e07c8561e74b Mon Sep 17 00:00:00 2001 From: CalDescent Date: Sun, 23 Oct 2022 17:01:58 +0100 Subject: [PATCH 04/19] Reduced online accounts mempow difficulty to 5 on testnets. This allows testnets to more easily coexist on the same machines that are running a mainnet instance, and still tests the mempow computation and verification in a non-resource-intensive way. --- .../controller/OnlineAccountsManager.java | 31 +++++++++++++++---- 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/qortal/controller/OnlineAccountsManager.java b/src/main/java/org/qortal/controller/OnlineAccountsManager.java index 0e24bdfc..5e0c2abe 100644 --- a/src/main/java/org/qortal/controller/OnlineAccountsManager.java +++ b/src/main/java/org/qortal/controller/OnlineAccountsManager.java @@ -20,6 +20,7 @@ import org.qortal.network.message.*; import org.qortal.repository.DataException; import org.qortal.repository.Repository; import org.qortal.repository.RepositoryManager; +import org.qortal.settings.Settings; import org.qortal.utils.Base58; import org.qortal.utils.NTP; import org.qortal.utils.NamedThreadFactory; @@ -63,9 +64,13 @@ public class OnlineAccountsManager { private static final long ONLINE_ACCOUNTS_COMPUTE_INITIAL_SLEEP_INTERVAL = 30 * 1000L; // ms - // MemoryPoW - public final int POW_BUFFER_SIZE = 1 * 1024 * 1024; // bytes - public int POW_DIFFICULTY = 18; // leading zero bits + // MemoryPoW - mainnet + public static final int POW_BUFFER_SIZE = 1 * 1024 * 1024; // bytes + public static final int POW_DIFFICULTY = 18; // leading zero bits + + // MemoryPoW - testnet + public static final int POW_BUFFER_SIZE_TESTNET = 1 * 1024 * 1024; // bytes + public static final int POW_DIFFICULTY_TESTNET = 5; // leading zero bits private final ScheduledExecutorService executor = Executors.newScheduledThreadPool(4, new NamedThreadFactory("OnlineAccounts")); private volatile boolean isStopping = false; @@ -111,6 +116,20 @@ public class OnlineAccountsManager { return (timestamp / getOnlineTimestampModulus()) * getOnlineTimestampModulus(); } + private static int getPoWBufferSize() { + if (Settings.getInstance().isTestNet()) + return POW_BUFFER_SIZE_TESTNET; + + return POW_BUFFER_SIZE; + } + + private static int getPoWDifficulty() { + if (Settings.getInstance().isTestNet()) + return POW_DIFFICULTY_TESTNET; + + return POW_DIFFICULTY; + } + private OnlineAccountsManager() { } @@ -604,7 +623,7 @@ public class OnlineAccountsManager { final long nextOnlineAccountsTimestamp = toOnlineAccountTimestamp(startTime) + getOnlineTimestampModulus(); long timeUntilNextTimestamp = nextOnlineAccountsTimestamp - startTime; - Integer nonce = MemoryPoW.compute2(bytes, POW_BUFFER_SIZE, POW_DIFFICULTY, timeUntilNextTimestamp); + Integer nonce = MemoryPoW.compute2(bytes, getPoWBufferSize(), getPoWDifficulty(), timeUntilNextTimestamp); double totalSeconds = (NTP.getTime() - startTime) / 1000.0f; int minutes = (int) ((totalSeconds % 3600) / 60); @@ -613,7 +632,7 @@ public class OnlineAccountsManager { LOGGER.info(String.format("Computed nonce for timestamp %d and account %.8s: %d. Buffer size: %d. Difficulty: %d. " + "Time taken: %02d:%02d. Hashrate: %f", onlineAccountsTimestamp, Base58.encode(publicKey), - nonce, POW_BUFFER_SIZE, POW_DIFFICULTY, minutes, seconds, hashRate)); + nonce, getPoWBufferSize(), getPoWDifficulty(), minutes, seconds, hashRate)); return nonce; } @@ -634,7 +653,7 @@ public class OnlineAccountsManager { } // Verify the nonce - return MemoryPoW.verify2(mempowBytes, POW_BUFFER_SIZE, POW_DIFFICULTY, nonce); + return MemoryPoW.verify2(mempowBytes, getPoWBufferSize(), getPoWDifficulty(), nonce); } From 510328db47dfe512ec47f9b91e3cf2d74ebd7c10 Mon Sep 17 00:00:00 2001 From: CalDescent Date: Fri, 28 Oct 2022 15:50:43 +0100 Subject: [PATCH 05/19] Removed unused timestamp value. --- src/main/java/org/qortal/block/Block.java | 2 +- .../java/org/qortal/controller/OnlineAccountsManager.java | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/qortal/block/Block.java b/src/main/java/org/qortal/block/Block.java index 55c13b36..c024308a 100644 --- a/src/main/java/org/qortal/block/Block.java +++ b/src/main/java/org/qortal/block/Block.java @@ -1072,7 +1072,7 @@ public class Block { // Validate the rest for (OnlineAccountData onlineAccount : onlineAccounts) - if (!OnlineAccountsManager.getInstance().verifyMemoryPoW(onlineAccount, this.blockData.getTimestamp())) + if (!OnlineAccountsManager.getInstance().verifyMemoryPoW(onlineAccount)) return ValidationResult.ONLINE_ACCOUNT_NONCE_INCORRECT; // Extract online accounts' timestamp signatures from block data. Only one signature if aggregated. diff --git a/src/main/java/org/qortal/controller/OnlineAccountsManager.java b/src/main/java/org/qortal/controller/OnlineAccountsManager.java index 5e0c2abe..aa35541d 100644 --- a/src/main/java/org/qortal/controller/OnlineAccountsManager.java +++ b/src/main/java/org/qortal/controller/OnlineAccountsManager.java @@ -339,7 +339,7 @@ public class OnlineAccountsManager { } // Validate mempow - if (!getInstance().verifyMemoryPoW(onlineAccountData, now)) { + if (!getInstance().verifyMemoryPoW(onlineAccountData)) { LOGGER.trace(() -> String.format("Rejecting online reward-share for account %s due to invalid PoW nonce", mintingAccount.getAddress())); return false; } @@ -582,7 +582,7 @@ public class OnlineAccountsManager { OnlineAccountData ourOnlineAccountData = new OnlineAccountData(onlineAccountsTimestamp, signature, publicKey, nonce); // Make sure to verify before adding - if (verifyMemoryPoW(ourOnlineAccountData, NTP.getTime())) { + if (verifyMemoryPoW(ourOnlineAccountData)) { ourOnlineAccounts.add(ourOnlineAccountData); } } @@ -637,7 +637,7 @@ public class OnlineAccountsManager { return nonce; } - public boolean verifyMemoryPoW(OnlineAccountData onlineAccountData, Long timestamp) { + public boolean verifyMemoryPoW(OnlineAccountData onlineAccountData) { // Require a valid nonce value if (onlineAccountData.getNonce() == null || onlineAccountData.getNonce() < 0) { return false; From 30cd56165a69bbcbe75eb7c35eb33383cf3b653d Mon Sep 17 00:00:00 2001 From: CalDescent Date: Fri, 28 Oct 2022 16:02:25 +0100 Subject: [PATCH 06/19] Speed up syncing blocks in the range of 1-12 hours ago by caching the valid online accounts. --- src/main/java/org/qortal/block/Block.java | 3 +++ .../org/qortal/controller/OnlineAccountsManager.java | 9 +++++---- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/qortal/block/Block.java b/src/main/java/org/qortal/block/Block.java index c024308a..99a82808 100644 --- a/src/main/java/org/qortal/block/Block.java +++ b/src/main/java/org/qortal/block/Block.java @@ -1075,6 +1075,9 @@ public class Block { if (!OnlineAccountsManager.getInstance().verifyMemoryPoW(onlineAccount)) return ValidationResult.ONLINE_ACCOUNT_NONCE_INCORRECT; + // Cache the valid online accounts as they will likely be needed for the next block + OnlineAccountsManager.getInstance().addBlocksOnlineAccounts(onlineAccounts, onlineTimestamp); + // Extract online accounts' timestamp signatures from block data. Only one signature if aggregated. List onlineAccountsSignatures = BlockTransformer.decodeTimestampSignatures(encodedOnlineAccountSignatures); diff --git a/src/main/java/org/qortal/controller/OnlineAccountsManager.java b/src/main/java/org/qortal/controller/OnlineAccountsManager.java index aa35541d..53968cfd 100644 --- a/src/main/java/org/qortal/controller/OnlineAccountsManager.java +++ b/src/main/java/org/qortal/controller/OnlineAccountsManager.java @@ -737,11 +737,12 @@ public class OnlineAccountsManager { * Typically called by {@link Block#areOnlineAccountsValid()} */ public void addBlocksOnlineAccounts(Set blocksOnlineAccounts, Long timestamp) { - // We want to add to 'current' in preference if possible - if (this.currentOnlineAccounts.containsKey(timestamp)) { - addAccounts(blocksOnlineAccounts); + // If these are current accounts, then there is no need to cache them, and should instead rely + // on the more complete entries we already have in self.currentOnlineAccounts. + // Note: since sig-agg, we no longer have individual signatures included in blocks, so we + // mustn't add anything to currentOnlineAccounts from here. + if (this.currentOnlineAccounts.containsKey(timestamp)) return; - } // Add to block cache instead this.latestBlocksOnlineAccounts.computeIfAbsent(timestamp, k -> ConcurrentHashMap.newKeySet()) From b64c05353157b74ebba12c8c2856d6847cf0e390 Mon Sep 17 00:00:00 2001 From: CalDescent Date: Fri, 28 Oct 2022 16:54:53 +0100 Subject: [PATCH 07/19] Reuse the work buffer when verifying online accounts from the OnlineAccountsManager import queue. This is a hopeful fix for extra memory usage since mempow activated, due to adding a lot of load to the garbage collector. It only applies to accounts verified from the import queue; the optimization hasn't been applied to block processing. But verifying online accounts when processing blocks is rare and generally would only last a short amount of time. --- src/main/java/org/qortal/block/Block.java | 2 +- .../qortal/controller/OnlineAccountsManager.java | 13 +++++++++---- src/main/java/org/qortal/crypto/MemoryPoW.java | 9 ++++++++- 3 files changed, 18 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/qortal/block/Block.java b/src/main/java/org/qortal/block/Block.java index 99a82808..5e838458 100644 --- a/src/main/java/org/qortal/block/Block.java +++ b/src/main/java/org/qortal/block/Block.java @@ -1072,7 +1072,7 @@ public class Block { // Validate the rest for (OnlineAccountData onlineAccount : onlineAccounts) - if (!OnlineAccountsManager.getInstance().verifyMemoryPoW(onlineAccount)) + if (!OnlineAccountsManager.getInstance().verifyMemoryPoW(onlineAccount, null)) return ValidationResult.ONLINE_ACCOUNT_NONCE_INCORRECT; // Cache the valid online accounts as they will likely be needed for the next block diff --git a/src/main/java/org/qortal/controller/OnlineAccountsManager.java b/src/main/java/org/qortal/controller/OnlineAccountsManager.java index 53968cfd..1aea118b 100644 --- a/src/main/java/org/qortal/controller/OnlineAccountsManager.java +++ b/src/main/java/org/qortal/controller/OnlineAccountsManager.java @@ -72,6 +72,11 @@ public class OnlineAccountsManager { public static final int POW_BUFFER_SIZE_TESTNET = 1 * 1024 * 1024; // bytes public static final int POW_DIFFICULTY_TESTNET = 5; // leading zero bits + // IMPORTANT: if we ever need to dynamically modify the buffer size using a feature trigger, the + // pre-allocated buffer below will NOT work, and we should instead use a dynamically allocated + // one for the transition period. + private static long[] POW_VERIFY_WORK_BUFFER = new long[getPoWBufferSize() / 8]; + private final ScheduledExecutorService executor = Executors.newScheduledThreadPool(4, new NamedThreadFactory("OnlineAccounts")); private volatile boolean isStopping = false; @@ -339,7 +344,7 @@ public class OnlineAccountsManager { } // Validate mempow - if (!getInstance().verifyMemoryPoW(onlineAccountData)) { + if (!getInstance().verifyMemoryPoW(onlineAccountData, POW_VERIFY_WORK_BUFFER)) { LOGGER.trace(() -> String.format("Rejecting online reward-share for account %s due to invalid PoW nonce", mintingAccount.getAddress())); return false; } @@ -582,7 +587,7 @@ public class OnlineAccountsManager { OnlineAccountData ourOnlineAccountData = new OnlineAccountData(onlineAccountsTimestamp, signature, publicKey, nonce); // Make sure to verify before adding - if (verifyMemoryPoW(ourOnlineAccountData)) { + if (verifyMemoryPoW(ourOnlineAccountData, null)) { ourOnlineAccounts.add(ourOnlineAccountData); } } @@ -637,7 +642,7 @@ public class OnlineAccountsManager { return nonce; } - public boolean verifyMemoryPoW(OnlineAccountData onlineAccountData) { + public boolean verifyMemoryPoW(OnlineAccountData onlineAccountData, long[] workBuffer) { // Require a valid nonce value if (onlineAccountData.getNonce() == null || onlineAccountData.getNonce() < 0) { return false; @@ -653,7 +658,7 @@ public class OnlineAccountsManager { } // Verify the nonce - return MemoryPoW.verify2(mempowBytes, getPoWBufferSize(), getPoWDifficulty(), nonce); + return MemoryPoW.verify2(mempowBytes, workBuffer, getPoWBufferSize(), getPoWDifficulty(), nonce); } diff --git a/src/main/java/org/qortal/crypto/MemoryPoW.java b/src/main/java/org/qortal/crypto/MemoryPoW.java index f27c8f7a..634b8f9b 100644 --- a/src/main/java/org/qortal/crypto/MemoryPoW.java +++ b/src/main/java/org/qortal/crypto/MemoryPoW.java @@ -99,6 +99,10 @@ public class MemoryPoW { } public static boolean verify2(byte[] data, int workBufferLength, long difficulty, int nonce) { + return verify2(data, null, workBufferLength, difficulty, nonce); + } + + public static boolean verify2(byte[] data, long[] workBuffer, int workBufferLength, long difficulty, int nonce) { // Hash data with SHA256 byte[] hash = Crypto.digest(data); @@ -111,7 +115,10 @@ public class MemoryPoW { byteBuffer = null; int longBufferLength = workBufferLength / 8; - long[] workBuffer = new long[longBufferLength]; + + if (workBuffer == null) + workBuffer = new long[longBufferLength]; + long[] state = new long[4]; long seed = 8682522807148012L; From 59a804c560c22d2655f1f80b75293ed930861d9a Mon Sep 17 00:00:00 2001 From: CalDescent Date: Fri, 28 Oct 2022 16:57:52 +0100 Subject: [PATCH 08/19] Include "blocks remaining" in systray when syncing from more than 60 minutes away from a peer's chain tip. --- .../org/qortal/controller/Controller.java | 6 +++++ .../org/qortal/controller/Synchronizer.java | 26 +++++++++++++++++++ src/main/resources/i18n/SysTray_de.properties | 2 ++ src/main/resources/i18n/SysTray_en.properties | 2 ++ src/main/resources/i18n/SysTray_es.properties | 2 ++ src/main/resources/i18n/SysTray_fi.properties | 2 ++ src/main/resources/i18n/SysTray_fr.properties | 2 ++ src/main/resources/i18n/SysTray_hu.properties | 2 ++ src/main/resources/i18n/SysTray_it.properties | 2 ++ src/main/resources/i18n/SysTray_ko.properties | 2 ++ src/main/resources/i18n/SysTray_nl.properties | 2 ++ src/main/resources/i18n/SysTray_ro.properties | 2 ++ src/main/resources/i18n/SysTray_ru.properties | 2 ++ src/main/resources/i18n/SysTray_sv.properties | 2 ++ .../resources/i18n/SysTray_zh_CN.properties | 2 ++ .../resources/i18n/SysTray_zh_TW.properties | 2 ++ 16 files changed, 60 insertions(+) diff --git a/src/main/java/org/qortal/controller/Controller.java b/src/main/java/org/qortal/controller/Controller.java index 6fe6a159..bcd010e8 100644 --- a/src/main/java/org/qortal/controller/Controller.java +++ b/src/main/java/org/qortal/controller/Controller.java @@ -838,6 +838,12 @@ public class Controller extends Thread { String tooltip = String.format("%s - %d %s", actionText, numberOfPeers, connectionsText); if (!Settings.getInstance().isLite()) { tooltip = tooltip.concat(String.format(" - %s %d", heightText, height)); + + final Integer blocksRemaining = Synchronizer.getInstance().getBlocksRemaining(); + if (blocksRemaining != null && blocksRemaining > 0) { + String blocksRemainingText = Translator.INSTANCE.translate("SysTray", "BLOCKS_REMAINING"); + tooltip = tooltip.concat(String.format(" - %d %s", blocksRemaining, blocksRemainingText)); + } } tooltip = tooltip.concat(String.format("\n%s: %s", Translator.INSTANCE.translate("SysTray", "BUILD_VERSION"), this.buildVersion)); SysTray.getInstance().setToolTipText(tooltip); diff --git a/src/main/java/org/qortal/controller/Synchronizer.java b/src/main/java/org/qortal/controller/Synchronizer.java index 6f2a0fe1..cd9483e9 100644 --- a/src/main/java/org/qortal/controller/Synchronizer.java +++ b/src/main/java/org/qortal/controller/Synchronizer.java @@ -76,6 +76,8 @@ public class Synchronizer extends Thread { private volatile boolean isSynchronizing = false; /** Temporary estimate of synchronization progress for SysTray use. */ private volatile int syncPercent = 0; + /** Temporary estimate of blocks remaining for SysTray use. */ + private volatile int blocksRemaining = 0; private static volatile boolean requestSync = false; private boolean syncRequestPending = false; @@ -181,6 +183,18 @@ public class Synchronizer extends Thread { } } + public Integer getBlocksRemaining() { + synchronized (this.syncLock) { + // Report as 0 blocks remaining if the latest block is within the last 60 mins + final Long minLatestBlockTimestamp = NTP.getTime() - (60 * 60 * 1000L); + if (Controller.getInstance().isUpToDate(minLatestBlockTimestamp)) { + return 0; + } + + return this.isSynchronizing ? this.blocksRemaining : null; + } + } + public void requestSync() { requestSync = true; } @@ -1457,6 +1471,12 @@ public class Synchronizer extends Thread { repository.saveChanges(); + synchronized (this.syncLock) { + if (peer.getChainTipData() != null) { + this.blocksRemaining = peer.getChainTipData().getHeight() - newBlock.getBlockData().getHeight(); + } + } + Controller.getInstance().onNewBlock(newBlock.getBlockData()); } @@ -1552,6 +1572,12 @@ public class Synchronizer extends Thread { repository.saveChanges(); + synchronized (this.syncLock) { + if (peer.getChainTipData() != null) { + this.blocksRemaining = peer.getChainTipData().getHeight() - newBlock.getBlockData().getHeight(); + } + } + Controller.getInstance().onNewBlock(newBlock.getBlockData()); } diff --git a/src/main/resources/i18n/SysTray_de.properties b/src/main/resources/i18n/SysTray_de.properties index b949ca8c..4dc7edd2 100644 --- a/src/main/resources/i18n/SysTray_de.properties +++ b/src/main/resources/i18n/SysTray_de.properties @@ -7,6 +7,8 @@ AUTO_UPDATE = Automatisches Update BLOCK_HEIGHT = height +BLOCKS_REMAINING = blocks remaining + BUILD_VERSION = Build-Version CHECK_TIME_ACCURACY = Prüfe Zeitgenauigkeit diff --git a/src/main/resources/i18n/SysTray_en.properties b/src/main/resources/i18n/SysTray_en.properties index 204f0df2..39940be0 100644 --- a/src/main/resources/i18n/SysTray_en.properties +++ b/src/main/resources/i18n/SysTray_en.properties @@ -7,6 +7,8 @@ AUTO_UPDATE = Auto Update BLOCK_HEIGHT = height +BLOCKS_REMAINING = blocks remaining + BUILD_VERSION = Build version CHECK_TIME_ACCURACY = Check time accuracy diff --git a/src/main/resources/i18n/SysTray_es.properties b/src/main/resources/i18n/SysTray_es.properties index d4b931d4..36cbb22c 100644 --- a/src/main/resources/i18n/SysTray_es.properties +++ b/src/main/resources/i18n/SysTray_es.properties @@ -7,6 +7,8 @@ AUTO_UPDATE = Actualización automática BLOCK_HEIGHT = altura +BLOCKS_REMAINING = blocks remaining + BUILD_VERSION = Versión de compilación CHECK_TIME_ACCURACY = Comprobar la precisión del tiempo diff --git a/src/main/resources/i18n/SysTray_fi.properties b/src/main/resources/i18n/SysTray_fi.properties index bc787715..4038d615 100644 --- a/src/main/resources/i18n/SysTray_fi.properties +++ b/src/main/resources/i18n/SysTray_fi.properties @@ -7,6 +7,8 @@ AUTO_UPDATE = Automaattinen päivitys BLOCK_HEIGHT = korkeus +BLOCKS_REMAINING = blocks remaining + BUILD_VERSION = Versio CHECK_TIME_ACCURACY = Tarkista ajan tarkkuus diff --git a/src/main/resources/i18n/SysTray_fr.properties b/src/main/resources/i18n/SysTray_fr.properties index 6e60713c..2e376842 100644 --- a/src/main/resources/i18n/SysTray_fr.properties +++ b/src/main/resources/i18n/SysTray_fr.properties @@ -7,6 +7,8 @@ AUTO_UPDATE = Mise à jour automatique BLOCK_HEIGHT = hauteur +BLOCKS_REMAINING = blocks remaining + BUILD_VERSION = Numéro de version CHECK_TIME_ACCURACY = Vérifier l'heure diff --git a/src/main/resources/i18n/SysTray_hu.properties b/src/main/resources/i18n/SysTray_hu.properties index 9bc51ff5..74ab21ac 100644 --- a/src/main/resources/i18n/SysTray_hu.properties +++ b/src/main/resources/i18n/SysTray_hu.properties @@ -7,6 +7,8 @@ AUTO_UPDATE = Automatikus Frissítés BLOCK_HEIGHT = blokkmagasság +BLOCKS_REMAINING = blocks remaining + BUILD_VERSION = Verzió CHECK_TIME_ACCURACY = Óra pontosságának ellenőrzése diff --git a/src/main/resources/i18n/SysTray_it.properties b/src/main/resources/i18n/SysTray_it.properties index bf61cc46..d966d825 100644 --- a/src/main/resources/i18n/SysTray_it.properties +++ b/src/main/resources/i18n/SysTray_it.properties @@ -7,6 +7,8 @@ AUTO_UPDATE = Aggiornamento automatico BLOCK_HEIGHT = altezza +BLOCKS_REMAINING = blocks remaining + BUILD_VERSION = Versione CHECK_TIME_ACCURACY = Controlla la precisione dell'ora diff --git a/src/main/resources/i18n/SysTray_ko.properties b/src/main/resources/i18n/SysTray_ko.properties index 9773a54f..dc6cb69b 100644 --- a/src/main/resources/i18n/SysTray_ko.properties +++ b/src/main/resources/i18n/SysTray_ko.properties @@ -7,6 +7,8 @@ AUTO_UPDATE = 자동 업데이트 BLOCK_HEIGHT = 높이 +BLOCKS_REMAINING = blocks remaining + BUILD_VERSION = 빌드 버전 CHECK_TIME_ACCURACY = 시간 정확도 점검 diff --git a/src/main/resources/i18n/SysTray_nl.properties b/src/main/resources/i18n/SysTray_nl.properties index 8a4f112b..c2acb7ce 100644 --- a/src/main/resources/i18n/SysTray_nl.properties +++ b/src/main/resources/i18n/SysTray_nl.properties @@ -7,6 +7,8 @@ AUTO_UPDATE = Automatische Update BLOCK_HEIGHT = Block hoogte +BLOCKS_REMAINING = blocks remaining + BUILD_VERSION = Versie nummer CHECK_TIME_ACCURACY = Controleer accuraatheid van de tijd diff --git a/src/main/resources/i18n/SysTray_ro.properties b/src/main/resources/i18n/SysTray_ro.properties index 0e1aa6c6..4130bbcb 100644 --- a/src/main/resources/i18n/SysTray_ro.properties +++ b/src/main/resources/i18n/SysTray_ro.properties @@ -7,6 +7,8 @@ AUTO_UPDATE = Actualizare automata BLOCK_HEIGHT = dimensiune +BLOCKS_REMAINING = blocks remaining + BUILD_VERSION = versiunea compilatiei CHECK_TIME_ACCURACY = verificare exactitate ora diff --git a/src/main/resources/i18n/SysTray_ru.properties b/src/main/resources/i18n/SysTray_ru.properties index fc3d8648..ff346304 100644 --- a/src/main/resources/i18n/SysTray_ru.properties +++ b/src/main/resources/i18n/SysTray_ru.properties @@ -7,6 +7,8 @@ AUTO_UPDATE = Автоматическое обновление BLOCK_HEIGHT = Высота блока +BLOCKS_REMAINING = blocks remaining + BUILD_VERSION = Версия сборки CHECK_TIME_ACCURACY = Проверка точного времени diff --git a/src/main/resources/i18n/SysTray_sv.properties b/src/main/resources/i18n/SysTray_sv.properties index 0e74337b..96f291b5 100644 --- a/src/main/resources/i18n/SysTray_sv.properties +++ b/src/main/resources/i18n/SysTray_sv.properties @@ -7,6 +7,8 @@ AUTO_UPDATE = Automatisk uppdatering BLOCK_HEIGHT = höjd +BLOCKS_REMAINING = blocks remaining + BUILD_VERSION = Byggversion CHECK_TIME_ACCURACY = Kontrollera tidens noggrannhet diff --git a/src/main/resources/i18n/SysTray_zh_CN.properties b/src/main/resources/i18n/SysTray_zh_CN.properties index c103d24b..d6848a7c 100644 --- a/src/main/resources/i18n/SysTray_zh_CN.properties +++ b/src/main/resources/i18n/SysTray_zh_CN.properties @@ -7,6 +7,8 @@ AUTO_UPDATE = 自动更新 BLOCK_HEIGHT = 区块高度 +BLOCKS_REMAINING = blocks remaining + BUILD_VERSION = 版本 CHECK_TIME_ACCURACY = 检查时间准确性 diff --git a/src/main/resources/i18n/SysTray_zh_TW.properties b/src/main/resources/i18n/SysTray_zh_TW.properties index 5e6ccc3e..eabdbb63 100644 --- a/src/main/resources/i18n/SysTray_zh_TW.properties +++ b/src/main/resources/i18n/SysTray_zh_TW.properties @@ -7,6 +7,8 @@ AUTO_UPDATE = 自動更新 BLOCK_HEIGHT = 區塊高度 +BLOCKS_REMAINING = blocks remaining + BUILD_VERSION = 版本 CHECK_TIME_ACCURACY = 檢查時間準確性 From 166425bee9e943ddf8c603da7e1c7d4184481276 Mon Sep 17 00:00:00 2001 From: CalDescent Date: Fri, 28 Oct 2022 17:20:39 +0100 Subject: [PATCH 09/19] Added feature trigger timestamp (TBC) to increase online accounts mempow difficulty (also TBC). --- src/main/java/org/qortal/block/BlockChain.java | 7 ++++++- .../controller/OnlineAccountsManager.java | 17 +++++++++++------ src/main/resources/blockchain.json | 3 ++- 3 files changed, 19 insertions(+), 8 deletions(-) diff --git a/src/main/java/org/qortal/block/BlockChain.java b/src/main/java/org/qortal/block/BlockChain.java index 826fdd78..5e1f44f3 100644 --- a/src/main/java/org/qortal/block/BlockChain.java +++ b/src/main/java/org/qortal/block/BlockChain.java @@ -73,7 +73,8 @@ public class BlockChain { calcChainWeightTimestamp, transactionV5Timestamp, transactionV6Timestamp, - disableReferenceTimestamp; + disableReferenceTimestamp, + increaseOnlineAccountsDifficultyTimestamp; } // Custom transaction fees @@ -478,6 +479,10 @@ public class BlockChain { return this.featureTriggers.get(FeatureTrigger.disableReferenceTimestamp.name()).longValue(); } + public long getIncreaseOnlineAccountsDifficultyTimestamp() { + return this.featureTriggers.get(FeatureTrigger.increaseOnlineAccountsDifficultyTimestamp.name()).longValue(); + } + // More complex getters for aspects that change by height or timestamp diff --git a/src/main/java/org/qortal/controller/OnlineAccountsManager.java b/src/main/java/org/qortal/controller/OnlineAccountsManager.java index 1aea118b..fd2c38df 100644 --- a/src/main/java/org/qortal/controller/OnlineAccountsManager.java +++ b/src/main/java/org/qortal/controller/OnlineAccountsManager.java @@ -66,7 +66,8 @@ public class OnlineAccountsManager { // MemoryPoW - mainnet public static final int POW_BUFFER_SIZE = 1 * 1024 * 1024; // bytes - public static final int POW_DIFFICULTY = 18; // leading zero bits + public static final int POW_DIFFICULTY_V1 = 18; // leading zero bits + public static final int POW_DIFFICULTY_V2 = 19; // leading zero bits // MemoryPoW - testnet public static final int POW_BUFFER_SIZE_TESTNET = 1 * 1024 * 1024; // bytes @@ -128,11 +129,14 @@ public class OnlineAccountsManager { return POW_BUFFER_SIZE; } - private static int getPoWDifficulty() { + private static int getPoWDifficulty(long timestamp) { if (Settings.getInstance().isTestNet()) return POW_DIFFICULTY_TESTNET; - return POW_DIFFICULTY; + if (timestamp >= BlockChain.getInstance().getIncreaseOnlineAccountsDifficultyTimestamp()) + return POW_DIFFICULTY_V2; + + return POW_DIFFICULTY_V1; } private OnlineAccountsManager() { @@ -628,7 +632,8 @@ public class OnlineAccountsManager { final long nextOnlineAccountsTimestamp = toOnlineAccountTimestamp(startTime) + getOnlineTimestampModulus(); long timeUntilNextTimestamp = nextOnlineAccountsTimestamp - startTime; - Integer nonce = MemoryPoW.compute2(bytes, getPoWBufferSize(), getPoWDifficulty(), timeUntilNextTimestamp); + int difficulty = getPoWDifficulty(onlineAccountsTimestamp); + Integer nonce = MemoryPoW.compute2(bytes, getPoWBufferSize(), difficulty, timeUntilNextTimestamp); double totalSeconds = (NTP.getTime() - startTime) / 1000.0f; int minutes = (int) ((totalSeconds % 3600) / 60); @@ -637,7 +642,7 @@ public class OnlineAccountsManager { LOGGER.info(String.format("Computed nonce for timestamp %d and account %.8s: %d. Buffer size: %d. Difficulty: %d. " + "Time taken: %02d:%02d. Hashrate: %f", onlineAccountsTimestamp, Base58.encode(publicKey), - nonce, getPoWBufferSize(), getPoWDifficulty(), minutes, seconds, hashRate)); + nonce, getPoWBufferSize(), difficulty, minutes, seconds, hashRate)); return nonce; } @@ -658,7 +663,7 @@ public class OnlineAccountsManager { } // Verify the nonce - return MemoryPoW.verify2(mempowBytes, workBuffer, getPoWBufferSize(), getPoWDifficulty(), nonce); + return MemoryPoW.verify2(mempowBytes, workBuffer, getPoWBufferSize(), getPoWDifficulty(onlineAccountData.getTimestamp()), nonce); } diff --git a/src/main/resources/blockchain.json b/src/main/resources/blockchain.json index 893add5e..34671c76 100644 --- a/src/main/resources/blockchain.json +++ b/src/main/resources/blockchain.json @@ -79,7 +79,8 @@ "calcChainWeightTimestamp": 1620579600000, "transactionV5Timestamp": 1642176000000, "transactionV6Timestamp": 9999999999999, - "disableReferenceTimestamp": 1655222400000 + "disableReferenceTimestamp": 1655222400000, + "increaseOnlineAccountsDifficultyTimestamp": 9999999999999 }, "genesisInfo": { "version": 4, From f739d8f5c6a9d0edc4a9a138f5468efe6ee8eeb7 Mon Sep 17 00:00:00 2001 From: CalDescent Date: Fri, 28 Oct 2022 18:06:34 +0100 Subject: [PATCH 10/19] Added increaseOnlineAccountsDifficultyTimestamp feature trigger to unit tests. --- src/test/resources/test-chain-v2-block-timestamps.json | 3 ++- src/test/resources/test-chain-v2-disable-reference.json | 3 ++- src/test/resources/test-chain-v2-founder-rewards.json | 3 ++- src/test/resources/test-chain-v2-leftover-reward.json | 3 ++- src/test/resources/test-chain-v2-minting.json | 3 ++- src/test/resources/test-chain-v2-qora-holder-extremes.json | 3 ++- src/test/resources/test-chain-v2-qora-holder-reduction.json | 3 ++- src/test/resources/test-chain-v2-qora-holder.json | 3 ++- src/test/resources/test-chain-v2-reward-levels.json | 3 ++- src/test/resources/test-chain-v2-reward-scaling.json | 3 ++- src/test/resources/test-chain-v2-reward-shares.json | 3 ++- src/test/resources/test-chain-v2.json | 3 ++- 12 files changed, 24 insertions(+), 12 deletions(-) diff --git a/src/test/resources/test-chain-v2-block-timestamps.json b/src/test/resources/test-chain-v2-block-timestamps.json index 37224684..4a883bd9 100644 --- a/src/test/resources/test-chain-v2-block-timestamps.json +++ b/src/test/resources/test-chain-v2-block-timestamps.json @@ -69,7 +69,8 @@ "calcChainWeightTimestamp": 0, "transactionV5Timestamp": 0, "transactionV6Timestamp": 9999999999999, - "disableReferenceTimestamp": 9999999999999 + "disableReferenceTimestamp": 9999999999999, + "increaseOnlineAccountsDifficultyTimestamp": 9999999999999 }, "genesisInfo": { "version": 4, diff --git a/src/test/resources/test-chain-v2-disable-reference.json b/src/test/resources/test-chain-v2-disable-reference.json index 7ea0b86d..e8fee5e0 100644 --- a/src/test/resources/test-chain-v2-disable-reference.json +++ b/src/test/resources/test-chain-v2-disable-reference.json @@ -72,7 +72,8 @@ "calcChainWeightTimestamp": 0, "transactionV5Timestamp": 0, "transactionV6Timestamp": 0, - "disableReferenceTimestamp": 0 + "disableReferenceTimestamp": 0, + "increaseOnlineAccountsDifficultyTimestamp": 9999999999999 }, "genesisInfo": { "version": 4, diff --git a/src/test/resources/test-chain-v2-founder-rewards.json b/src/test/resources/test-chain-v2-founder-rewards.json index 85a50f83..17a713a0 100644 --- a/src/test/resources/test-chain-v2-founder-rewards.json +++ b/src/test/resources/test-chain-v2-founder-rewards.json @@ -73,7 +73,8 @@ "calcChainWeightTimestamp": 0, "transactionV5Timestamp": 0, "transactionV6Timestamp": 0, - "disableReferenceTimestamp": 9999999999999 + "disableReferenceTimestamp": 9999999999999, + "increaseOnlineAccountsDifficultyTimestamp": 9999999999999 }, "genesisInfo": { "version": 4, diff --git a/src/test/resources/test-chain-v2-leftover-reward.json b/src/test/resources/test-chain-v2-leftover-reward.json index ebc3ccfa..b57c3195 100644 --- a/src/test/resources/test-chain-v2-leftover-reward.json +++ b/src/test/resources/test-chain-v2-leftover-reward.json @@ -73,7 +73,8 @@ "calcChainWeightTimestamp": 0, "transactionV5Timestamp": 0, "transactionV6Timestamp": 0, - "disableReferenceTimestamp": 9999999999999 + "disableReferenceTimestamp": 9999999999999, + "increaseOnlineAccountsDifficultyTimestamp": 9999999999999 }, "genesisInfo": { "version": 4, diff --git a/src/test/resources/test-chain-v2-minting.json b/src/test/resources/test-chain-v2-minting.json index cc91f993..60b3cd76 100644 --- a/src/test/resources/test-chain-v2-minting.json +++ b/src/test/resources/test-chain-v2-minting.json @@ -73,7 +73,8 @@ "calcChainWeightTimestamp": 0, "transactionV5Timestamp": 0, "transactionV6Timestamp": 0, - "disableReferenceTimestamp": 9999999999999 + "disableReferenceTimestamp": 9999999999999, + "increaseOnlineAccountsDifficultyTimestamp": 9999999999999 }, "genesisInfo": { "version": 4, diff --git a/src/test/resources/test-chain-v2-qora-holder-extremes.json b/src/test/resources/test-chain-v2-qora-holder-extremes.json index 085d1dbf..2d044687 100644 --- a/src/test/resources/test-chain-v2-qora-holder-extremes.json +++ b/src/test/resources/test-chain-v2-qora-holder-extremes.json @@ -73,7 +73,8 @@ "calcChainWeightTimestamp": 0, "transactionV5Timestamp": 0, "transactionV6Timestamp": 0, - "disableReferenceTimestamp": 9999999999999 + "disableReferenceTimestamp": 9999999999999, + "increaseOnlineAccountsDifficultyTimestamp": 9999999999999 }, "genesisInfo": { "version": 4, diff --git a/src/test/resources/test-chain-v2-qora-holder-reduction.json b/src/test/resources/test-chain-v2-qora-holder-reduction.json index 75858057..3cf8848e 100644 --- a/src/test/resources/test-chain-v2-qora-holder-reduction.json +++ b/src/test/resources/test-chain-v2-qora-holder-reduction.json @@ -74,7 +74,8 @@ "transactionV5Timestamp": 0, "transactionV6Timestamp": 0, "disableReferenceTimestamp": 9999999999999, - "aggregateSignatureTimestamp": 0 + "aggregateSignatureTimestamp": 0, + "increaseOnlineAccountsDifficultyTimestamp": 9999999999999 }, "genesisInfo": { "version": 4, diff --git a/src/test/resources/test-chain-v2-qora-holder.json b/src/test/resources/test-chain-v2-qora-holder.json index 0706c5bb..93965b76 100644 --- a/src/test/resources/test-chain-v2-qora-holder.json +++ b/src/test/resources/test-chain-v2-qora-holder.json @@ -73,7 +73,8 @@ "calcChainWeightTimestamp": 0, "transactionV5Timestamp": 0, "transactionV6Timestamp": 0, - "disableReferenceTimestamp": 9999999999999 + "disableReferenceTimestamp": 9999999999999, + "increaseOnlineAccountsDifficultyTimestamp": 9999999999999 }, "genesisInfo": { "version": 4, diff --git a/src/test/resources/test-chain-v2-reward-levels.json b/src/test/resources/test-chain-v2-reward-levels.json index b3644d6b..06422e71 100644 --- a/src/test/resources/test-chain-v2-reward-levels.json +++ b/src/test/resources/test-chain-v2-reward-levels.json @@ -73,7 +73,8 @@ "calcChainWeightTimestamp": 0, "transactionV5Timestamp": 0, "transactionV6Timestamp": 0, - "disableReferenceTimestamp": 9999999999999 + "disableReferenceTimestamp": 9999999999999, + "increaseOnlineAccountsDifficultyTimestamp": 9999999999999 }, "genesisInfo": { "version": 4, diff --git a/src/test/resources/test-chain-v2-reward-scaling.json b/src/test/resources/test-chain-v2-reward-scaling.json index 1c68dda4..6adcd0ac 100644 --- a/src/test/resources/test-chain-v2-reward-scaling.json +++ b/src/test/resources/test-chain-v2-reward-scaling.json @@ -73,7 +73,8 @@ "calcChainWeightTimestamp": 0, "transactionV5Timestamp": 0, "transactionV6Timestamp": 0, - "disableReferenceTimestamp": 9999999999999 + "disableReferenceTimestamp": 9999999999999, + "increaseOnlineAccountsDifficultyTimestamp": 9999999999999 }, "genesisInfo": { "version": 4, diff --git a/src/test/resources/test-chain-v2-reward-shares.json b/src/test/resources/test-chain-v2-reward-shares.json index 10d2aab3..95324b56 100644 --- a/src/test/resources/test-chain-v2-reward-shares.json +++ b/src/test/resources/test-chain-v2-reward-shares.json @@ -73,7 +73,8 @@ "newConsensusTimestamp": 0, "transactionV5Timestamp": 0, "transactionV6Timestamp": 0, - "disableReferenceTimestamp": 9999999999999 + "disableReferenceTimestamp": 9999999999999, + "increaseOnlineAccountsDifficultyTimestamp": 9999999999999 }, "genesisInfo": { "version": 4, diff --git a/src/test/resources/test-chain-v2.json b/src/test/resources/test-chain-v2.json index 5f439602..c0fb9861 100644 --- a/src/test/resources/test-chain-v2.json +++ b/src/test/resources/test-chain-v2.json @@ -73,7 +73,8 @@ "calcChainWeightTimestamp": 0, "transactionV5Timestamp": 0, "transactionV6Timestamp": 0, - "disableReferenceTimestamp": 9999999999999 + "disableReferenceTimestamp": 9999999999999, + "increaseOnlineAccountsDifficultyTimestamp": 9999999999999 }, "genesisInfo": { "version": 4, From fa80c838645effa63936acbfd84c0fbca0269b56 Mon Sep 17 00:00:00 2001 From: CalDescent Date: Sun, 30 Oct 2022 17:07:56 +0000 Subject: [PATCH 11/19] Remove QORTAL_METADATA service as this uses its own protocol instead. --- src/main/java/org/qortal/arbitrary/misc/Service.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/java/org/qortal/arbitrary/misc/Service.java b/src/main/java/org/qortal/arbitrary/misc/Service.java index 5d94d806..99e9de36 100644 --- a/src/main/java/org/qortal/arbitrary/misc/Service.java +++ b/src/main/java/org/qortal/arbitrary/misc/Service.java @@ -47,8 +47,7 @@ public enum Service { LIST(900, true, null, null), PLAYLIST(910, true, null, null), APP(1000, false, null, null), - METADATA(1100, false, null, null), - QORTAL_METADATA(1111, true, 10*1024L, Arrays.asList("title", "description", "tags")); + METADATA(1100, false, null, null); public final int value; private final boolean requiresValidation; From 4043ae19285faf56411ff1a5dc7a6033c1543c09 Mon Sep 17 00:00:00 2001 From: CalDescent Date: Sun, 30 Oct 2022 17:23:46 +0000 Subject: [PATCH 12/19] Added QCHAT_IMAGE service (with 500KB file size limit). --- src/main/java/org/qortal/arbitrary/misc/Service.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/org/qortal/arbitrary/misc/Service.java b/src/main/java/org/qortal/arbitrary/misc/Service.java index 99e9de36..981aa119 100644 --- a/src/main/java/org/qortal/arbitrary/misc/Service.java +++ b/src/main/java/org/qortal/arbitrary/misc/Service.java @@ -38,6 +38,7 @@ public enum Service { GIT_REPOSITORY(300, false, null, null), IMAGE(400, true, 10*1024*1024L, null), THUMBNAIL(410, true, 500*1024L, null), + QCHAT_IMAGE(420, true, 500*1024L, null), VIDEO(500, false, null, null), AUDIO(600, false, null, null), BLOG(700, false, null, null), From 0628847d14db75aa30e784ef81c1c26794d7c56b Mon Sep 17 00:00:00 2001 From: CalDescent Date: Sun, 30 Oct 2022 17:25:11 +0000 Subject: [PATCH 13/19] Removed QORTAL_METADATA service tests. --- .../test/arbitrary/ArbitraryServiceTests.java | 74 ------------------- 1 file changed, 74 deletions(-) diff --git a/src/test/java/org/qortal/test/arbitrary/ArbitraryServiceTests.java b/src/test/java/org/qortal/test/arbitrary/ArbitraryServiceTests.java index 4db8bdc7..d71910f7 100644 --- a/src/test/java/org/qortal/test/arbitrary/ArbitraryServiceTests.java +++ b/src/test/java/org/qortal/test/arbitrary/ArbitraryServiceTests.java @@ -101,78 +101,4 @@ public class ArbitraryServiceTests extends Common { assertEquals(ValidationResult.MISSING_INDEX_FILE, service.validate(path)); } - @Test - public void testValidQortalMetadata() throws IOException { - // Metadata is to describe an arbitrary resource (title, description, tags, etc) - String dataString = "{\"title\":\"Test Title\", \"description\":\"Test description\", \"tags\":[\"test\"]}"; - - // Write to temp path - Path path = Files.createTempFile("testValidQortalMetadata", null); - path.toFile().deleteOnExit(); - Files.write(path, dataString.getBytes(), StandardOpenOption.CREATE); - - Service service = Service.QORTAL_METADATA; - assertTrue(service.isValidationRequired()); - assertEquals(ValidationResult.OK, service.validate(path)); - } - - @Test - public void testQortalMetadataMissingKeys() throws IOException { - // Metadata is to describe an arbitrary resource (title, description, tags, etc) - String dataString = "{\"description\":\"Test description\", \"tags\":[\"test\"]}"; - - // Write to temp path - Path path = Files.createTempFile("testQortalMetadataMissingKeys", null); - path.toFile().deleteOnExit(); - Files.write(path, dataString.getBytes(), StandardOpenOption.CREATE); - - Service service = Service.QORTAL_METADATA; - assertTrue(service.isValidationRequired()); - assertEquals(ValidationResult.MISSING_KEYS, service.validate(path)); - } - - @Test - public void testQortalMetadataTooLarge() throws IOException { - // Metadata is to describe an arbitrary resource (title, description, tags, etc) - String dataString = "{\"title\":\"Test Title\", \"description\":\"Test description\", \"tags\":[\"test\"]}"; - - // Generate some large data to go along with it - int largeDataSize = 11*1024; // Larger than allowed 10kiB - byte[] largeData = new byte[largeDataSize]; - new Random().nextBytes(largeData); - - // Write to temp path - Path path = Files.createTempDirectory("testQortalMetadataTooLarge"); - path.toFile().deleteOnExit(); - Files.write(Paths.get(path.toString(), "data"), dataString.getBytes(), StandardOpenOption.CREATE); - Files.write(Paths.get(path.toString(), "large_data"), largeData, StandardOpenOption.CREATE); - - Service service = Service.QORTAL_METADATA; - assertTrue(service.isValidationRequired()); - assertEquals(ValidationResult.EXCEEDS_SIZE_LIMIT, service.validate(path)); - } - - @Test - public void testMultipleFileMetadata() throws IOException { - // Metadata is to describe an arbitrary resource (title, description, tags, etc) - String dataString = "{\"title\":\"Test Title\", \"description\":\"Test description\", \"tags\":[\"test\"]}"; - - // Generate some large data to go along with it - int otherDataSize = 1024; // Smaller than 10kiB limit - byte[] otherData = new byte[otherDataSize]; - new Random().nextBytes(otherData); - - // Write to temp path - Path path = Files.createTempDirectory("testMultipleFileMetadata"); - path.toFile().deleteOnExit(); - Files.write(Paths.get(path.toString(), "data"), dataString.getBytes(), StandardOpenOption.CREATE); - Files.write(Paths.get(path.toString(), "other_data"), otherData, StandardOpenOption.CREATE); - - Service service = Service.QORTAL_METADATA; - assertTrue(service.isValidationRequired()); - - // There are multiple files, so we don't know which one to parse as JSON - assertEquals(ValidationResult.MISSING_KEYS, service.validate(path)); - } - } From 985c195e9e1a3cbdf6791bae3ce73947d2925931 Mon Sep 17 00:00:00 2001 From: CalDescent Date: Sun, 30 Oct 2022 17:33:21 +0000 Subject: [PATCH 14/19] Added GIF_REPOSITORY, with custom validation function and unit tests. --- .../org/qortal/arbitrary/misc/Service.java | 35 ++++++++- .../test/arbitrary/ArbitraryServiceTests.java | 74 +++++++++++++++++++ 2 files changed, 106 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/qortal/arbitrary/misc/Service.java b/src/main/java/org/qortal/arbitrary/misc/Service.java index 981aa119..5dd8d94e 100644 --- a/src/main/java/org/qortal/arbitrary/misc/Service.java +++ b/src/main/java/org/qortal/arbitrary/misc/Service.java @@ -1,16 +1,18 @@ package org.qortal.arbitrary.misc; +import org.apache.commons.io.FilenameUtils; import org.json.JSONObject; import org.qortal.arbitrary.ArbitraryDataRenderer; import org.qortal.transaction.Transaction; import org.qortal.utils.FilesystemUtils; +import java.io.File; import java.io.IOException; import java.nio.file.Path; import java.nio.file.Paths; -import java.util.Arrays; import java.util.List; import java.util.Map; +import java.util.Objects; import static java.util.Arrays.stream; import static java.util.stream.Collectors.toMap; @@ -48,7 +50,31 @@ public enum Service { LIST(900, true, null, null), PLAYLIST(910, true, null, null), APP(1000, false, null, null), - METADATA(1100, false, null, null); + METADATA(1100, false, null, null), + GIF_REPOSITORY(1200, true, 25*1024*1024L, null) { + @Override + public ValidationResult validate(Path path) { + // Custom validation function to require .gif files only, and at least 1 + int gifCount = 0; + File[] files = path.toFile().listFiles(); + if (files != null) { + for (File file : files) { + if (file.isDirectory()) { + return ValidationResult.DIRECTORIES_NOT_ALLOWED; + } + String extension = FilenameUtils.getExtension(file.getName()).toLowerCase(); + if (!Objects.equals(extension, "gif")) { + return ValidationResult.INVALID_FILE_EXTENSION; + } + gifCount++; + } + } + if (gifCount == 0) { + return ValidationResult.MISSING_DATA; + } + return ValidationResult.OK; + } + }; public final int value; private final boolean requiresValidation; @@ -114,7 +140,10 @@ public enum Service { OK(1), MISSING_KEYS(2), EXCEEDS_SIZE_LIMIT(3), - MISSING_INDEX_FILE(4); + MISSING_INDEX_FILE(4), + DIRECTORIES_NOT_ALLOWED(5), + INVALID_FILE_EXTENSION(6), + MISSING_DATA(7); public final int value; diff --git a/src/test/java/org/qortal/test/arbitrary/ArbitraryServiceTests.java b/src/test/java/org/qortal/test/arbitrary/ArbitraryServiceTests.java index d71910f7..e6a51776 100644 --- a/src/test/java/org/qortal/test/arbitrary/ArbitraryServiceTests.java +++ b/src/test/java/org/qortal/test/arbitrary/ArbitraryServiceTests.java @@ -101,4 +101,78 @@ public class ArbitraryServiceTests extends Common { assertEquals(ValidationResult.MISSING_INDEX_FILE, service.validate(path)); } + @Test + public void testValidateGifRepository() throws IOException { + // Generate some random data + byte[] data = new byte[1024]; + new Random().nextBytes(data); + + // Write the data to several files in a temp path + Path path = Files.createTempDirectory("testValidateGifRepository"); + path.toFile().deleteOnExit(); + Files.write(Paths.get(path.toString(), "image1.gif"), data, StandardOpenOption.CREATE); + Files.write(Paths.get(path.toString(), "image2.gif"), data, StandardOpenOption.CREATE); + Files.write(Paths.get(path.toString(), "image3.gif"), data, StandardOpenOption.CREATE); + + Service service = Service.GIF_REPOSITORY; + assertTrue(service.isValidationRequired()); + + // There is an index file in the root + assertEquals(ValidationResult.OK, service.validate(path)); + } + + @Test + public void testValidateMultiLayerGifRepository() throws IOException { + // Generate some random data + byte[] data = new byte[1024]; + new Random().nextBytes(data); + + // Write the data to several files in a temp path + Path path = Files.createTempDirectory("testValidateMultiLayerGifRepository"); + path.toFile().deleteOnExit(); + Files.write(Paths.get(path.toString(), "image1.gif"), data, StandardOpenOption.CREATE); + + Path subdirectory = Paths.get(path.toString(), "subdirectory"); + Files.createDirectories(subdirectory); + Files.write(Paths.get(subdirectory.toString(), "image2.gif"), data, StandardOpenOption.CREATE); + Files.write(Paths.get(subdirectory.toString(), "image3.gif"), data, StandardOpenOption.CREATE); + + Service service = Service.GIF_REPOSITORY; + assertTrue(service.isValidationRequired()); + + // There is an index file in the root + assertEquals(ValidationResult.DIRECTORIES_NOT_ALLOWED, service.validate(path)); + } + + @Test + public void testValidateEmptyGifRepository() throws IOException { + Path path = Files.createTempDirectory("testValidateEmptyGifRepository"); + + Service service = Service.GIF_REPOSITORY; + assertTrue(service.isValidationRequired()); + + // There is an index file in the root + assertEquals(ValidationResult.MISSING_DATA, service.validate(path)); + } + + @Test + public void testValidateInvalidGifRepository() throws IOException { + // Generate some random data + byte[] data = new byte[1024]; + new Random().nextBytes(data); + + // Write the data to several files in a temp path + Path path = Files.createTempDirectory("testValidateInvalidGifRepository"); + path.toFile().deleteOnExit(); + Files.write(Paths.get(path.toString(), "image1.gif"), data, StandardOpenOption.CREATE); + Files.write(Paths.get(path.toString(), "image2.gif"), data, StandardOpenOption.CREATE); + Files.write(Paths.get(path.toString(), "image3.jpg"), data, StandardOpenOption.CREATE); // Invalid extension + + Service service = Service.GIF_REPOSITORY; + assertTrue(service.isValidationRequired()); + + // There is an index file in the root + assertEquals(ValidationResult.INVALID_FILE_EXTENSION, service.validate(path)); + } + } From aead9cfcbfe23747d60ce1a59d05be9354ac22fa Mon Sep 17 00:00:00 2001 From: catbref Date: Tue, 1 Nov 2022 08:55:57 +0000 Subject: [PATCH 15/19] Proof of concept: speed up QORT buying When users buy QORT ("Alice"-side), most of the API time is spent computing mempow for the MESSAGE sent to Bob's AT. This is the final stage startResponse() and after Alice's P2SH is already broadcast. To speed this up, the MESSAGE part is moved into its own thread allowing startResponse() to return sooner, improving the user experience. Caveats: If MESSAGE importAsUnconfirmed() somehow fails the the buy won't complete and Alice will have to wait for P2SH refund. If Alice shuts down her node while MESSAGE mempow is being computed then it's possible the shutdown will be blocked until mempow is complete. Currently only implemented in LitecoinACCTv3TradeBot as this is only proof-of-concept. Tested with multiple buys in the same block. --- .../tradebot/LitecoinACCTv3TradeBot.java | 30 ++++++++++++------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/src/main/java/org/qortal/controller/tradebot/LitecoinACCTv3TradeBot.java b/src/main/java/org/qortal/controller/tradebot/LitecoinACCTv3TradeBot.java index a31a1a28..a4ae921e 100644 --- a/src/main/java/org/qortal/controller/tradebot/LitecoinACCTv3TradeBot.java +++ b/src/main/java/org/qortal/controller/tradebot/LitecoinACCTv3TradeBot.java @@ -19,6 +19,7 @@ import org.qortal.data.transaction.MessageTransactionData; import org.qortal.group.Group; import org.qortal.repository.DataException; import org.qortal.repository.Repository; +import org.qortal.repository.RepositoryManager; import org.qortal.transaction.DeployAtTransaction; import org.qortal.transaction.MessageTransaction; import org.qortal.transaction.Transaction.ValidationResult; @@ -317,20 +318,27 @@ public class LitecoinACCTv3TradeBot implements AcctTradeBot { boolean isMessageAlreadySent = repository.getMessageRepository().exists(tradeBotData.getTradeNativePublicKey(), messageRecipient, messageData); if (!isMessageAlreadySent) { - PrivateKeyAccount sender = new PrivateKeyAccount(repository, tradeBotData.getTradePrivateKey()); - MessageTransaction messageTransaction = MessageTransaction.build(repository, sender, Group.NO_GROUP, messageRecipient, messageData, false, false); + // Do this in a new thread so caller doesn't have to wait for computeNonce() + // In the unlikely event that the transaction doesn't validate then the buy won't happen and eventually Alice's AT will be refunded + new Thread(() -> { + try (final Repository threadsRepository = RepositoryManager.getRepository()) { + PrivateKeyAccount sender = new PrivateKeyAccount(threadsRepository, tradeBotData.getTradePrivateKey()); + MessageTransaction messageTransaction = MessageTransaction.build(threadsRepository, sender, Group.NO_GROUP, messageRecipient, messageData, false, false); - messageTransaction.computeNonce(); - messageTransaction.sign(sender); + messageTransaction.computeNonce(); + messageTransaction.sign(sender); - // reset repository state to prevent deadlock - repository.discardChanges(); - ValidationResult result = messageTransaction.importAsUnconfirmed(); + // reset repository state to prevent deadlock + threadsRepository.discardChanges(); + ValidationResult result = messageTransaction.importAsUnconfirmed(); - if (result != ValidationResult.OK) { - LOGGER.warn(() -> String.format("Unable to send MESSAGE to Bob's trade-bot %s: %s", messageRecipient, result.name())); - return ResponseResult.NETWORK_ISSUE; - } + if (result != ValidationResult.OK) { + LOGGER.warn(() -> String.format("Unable to send MESSAGE to Bob's trade-bot %s: %s", messageRecipient, result.name())); + } + } catch (DataException e) { + LOGGER.warn(() -> String.format("Unable to send MESSAGE to Bob's trade-bot %s: %s", messageRecipient, e.getMessage())); + } + }, "TradeBot response").start(); } TradeBot.updateTradeBotState(repository, tradeBotData, () -> String.format("Funding P2SH-A %s. Messaged Bob. Waiting for AT-lock", p2shAddress)); From 9c68f1038ab899e69831345f10916e9ea6732115 Mon Sep 17 00:00:00 2001 From: CalDescent Date: Sat, 5 Nov 2022 14:02:04 +0000 Subject: [PATCH 16/19] Bump AT version to 1.4.0 --- lib/org/ciyam/AT/1.4.0/AT-1.4.0.jar | Bin 0 -> 161850 bytes lib/org/ciyam/AT/1.4.0/AT-1.4.0.pom | 9 +++++++++ lib/org/ciyam/AT/maven-metadata-local.xml | 5 +++-- pom.xml | 2 +- 4 files changed, 13 insertions(+), 3 deletions(-) create mode 100644 lib/org/ciyam/AT/1.4.0/AT-1.4.0.jar create mode 100644 lib/org/ciyam/AT/1.4.0/AT-1.4.0.pom diff --git a/lib/org/ciyam/AT/1.4.0/AT-1.4.0.jar b/lib/org/ciyam/AT/1.4.0/AT-1.4.0.jar new file mode 100644 index 0000000000000000000000000000000000000000..c2c3d3556575dd7793cbd08ba3013003e8eaf621 GIT binary patch literal 161850 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 GNcMjLnE!kL literal 0 HcmV?d00001 diff --git a/lib/org/ciyam/AT/1.4.0/AT-1.4.0.pom b/lib/org/ciyam/AT/1.4.0/AT-1.4.0.pom new file mode 100644 index 00000000..0dc1aedc --- /dev/null +++ b/lib/org/ciyam/AT/1.4.0/AT-1.4.0.pom @@ -0,0 +1,9 @@ + + + 4.0.0 + org.ciyam + AT + 1.4.0 + POM was created from install:install-file + diff --git a/lib/org/ciyam/AT/maven-metadata-local.xml b/lib/org/ciyam/AT/maven-metadata-local.xml index 8f8b1f6e..063c735d 100644 --- a/lib/org/ciyam/AT/maven-metadata-local.xml +++ b/lib/org/ciyam/AT/maven-metadata-local.xml @@ -3,14 +3,15 @@ org.ciyam AT - 1.3.8 + 1.4.0 1.3.4 1.3.5 1.3.6 1.3.7 1.3.8 + 1.4.0 - 20200925114415 + 20221105114346 diff --git a/pom.xml b/pom.xml index eb306420..860cdce5 100644 --- a/pom.xml +++ b/pom.xml @@ -11,7 +11,7 @@ 0.15.10 1.69 ${maven.build.timestamp} - 1.3.8 + 1.4.0 3.6 1.8 2.6 From db2244594836dcd952e97746b2ec529483ddfd98 Mon Sep 17 00:00:00 2001 From: CalDescent Date: Sun, 6 Nov 2022 14:52:14 +0000 Subject: [PATCH 17/19] Include API key automatically in publish-auto-update-v5.pl --- tools/publish-auto-update-v5.pl | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tools/publish-auto-update-v5.pl b/tools/publish-auto-update-v5.pl index aad49d4e..f97fe115 100755 --- a/tools/publish-auto-update-v5.pl +++ b/tools/publish-auto-update-v5.pl @@ -4,6 +4,7 @@ use strict; use warnings; use POSIX; use Getopt::Std; +use File::Slurp; sub usage() { die("usage: $0 [-p api-port] dev-private-key [short-commit-hash]\n"); @@ -34,6 +35,8 @@ while () { } close(POM); +my $apikey = read_file('apikey.txt'); + # Do we need to determine commit hash? unless ($commit_hash) { # determine git branch @@ -124,7 +127,7 @@ my $raw_tx = `curl --silent --url http://localhost:${port}/utils/tobase58/${raw_ die("Can't convert raw transaction hex to base58:\n$raw_tx\n") unless $raw_tx =~ m/^\w{300,320}$/; # Roughly 305 to 320 base58 chars printf "\nRaw transaction (base58):\n%s\n", $raw_tx; -my $computed_tx = `curl --silent -X POST --url http://localhost:${port}/arbitrary/compute -d "${raw_tx}"`; +my $computed_tx = `curl --silent -X POST --url http://localhost:${port}/arbitrary/compute -H "X-API-KEY: ${apikey}" -d "${raw_tx}"`; die("Can't compute nonce for transaction:\n$computed_tx\n") unless $computed_tx =~ m/^\w{300,320}$/; # Roughly 300 to 320 base58 chars printf "\nRaw computed transaction (base58):\n%s\n", $computed_tx; From 9255df46cf1d3724fa6484fe532e37d74b07a8ce Mon Sep 17 00:00:00 2001 From: CalDescent Date: Sun, 6 Nov 2022 19:46:12 +0000 Subject: [PATCH 18/19] Script updates to support add/remove dev group admins --- tools/approve-dev-transaction.sh | 97 ++++++++++++++++++++++++++++++++ tools/tx.pl | 7 ++- 2 files changed, 103 insertions(+), 1 deletion(-) create mode 100755 tools/approve-dev-transaction.sh diff --git a/tools/approve-dev-transaction.sh b/tools/approve-dev-transaction.sh new file mode 100755 index 00000000..6b611b59 --- /dev/null +++ b/tools/approve-dev-transaction.sh @@ -0,0 +1,97 @@ +#!/usr/bin/env bash + +port=12391 +if [ $# -gt 0 -a "$1" = "-t" ]; then + port=62391 +fi + +printf "Searching for auto-update transactions to approve...\n"; + +tx=$( curl --silent --url "http://localhost:${port}/transactions/search?txGroupId=1&txType=ADD_GROUP_ADMIN&txType=REMOVE_GROUP_ADMIN&confirmationStatus=CONFIRMED&limit=1&reverse=true" ); +if fgrep --silent '"approvalStatus":"PENDING"' <<< "${tx}"; then + true +else + echo "Can't find any pending transactions" + exit +fi + +sig=$( perl -n -e 'print $1 if m/"signature":"(\w+)"/' <<< "${tx}" ) +if [ -z "${sig}" ]; then + printf "Can't find transaction signature in JSON:\n%s\n" "${tx}" + exit +fi + +printf "Found transaction %s\n" $sig; + +printf "\nPaste your dev account private key:\n"; +IFS= +read -s privkey +printf "\n" + +# Convert to public key +pubkey=$( curl --silent --url "http://localhost:${port}/utils/publickey" --data @- <<< "${privkey}" ); +if egrep -v --silent '^\w{44,46}$' <<< "${pubkey}"; then + printf "Invalid response from API - was your private key correct?\n%s\n" "${pubkey}" + exit +fi +printf "Your public key: %s\n" ${pubkey} + +# Convert to address +address=$( curl --silent --url "http://localhost:${port}/addresses/convert/${pubkey}" ); +printf "Your address: %s\n" ${address} + +# Grab last reference +lastref=$( curl --silent --url "http://localhost:${port}/addresses/lastreference/{$address}" ); +printf "Your last reference: %s\n" ${lastref} + +# Build GROUP_APPROVAL transaction +timestamp=$( date +%s )000 +tx_json=$( cat < 0; seconds--)); do + if [ "${seconds}" = "1" ]; then + plural="" + fi + printf "\rBroadcasting in %d second%s...(CTRL-C) to abort " $seconds $plural + sleep 1 +done + +printf "\rBroadcasting signed GROUP_APPROVAL transaction... \n" +result=$( curl --silent --url "http://localhost:${port}/transactions/process" --data @- <<< "${signed_tx}" ) +printf "API response:\n%s\n" "${result}" diff --git a/tools/tx.pl b/tools/tx.pl index db6958e2..fe3cd872 100755 --- a/tools/tx.pl +++ b/tools/tx.pl @@ -71,9 +71,14 @@ our %TRANSACTION_TYPES = ( }, add_group_admin => { url => 'groups/addadmin', - required => [qw(groupId member)], + required => [qw(groupId txGroupId member)], key_name => 'ownerPublicKey', }, + remove_group_admin => { + url => 'groups/removeadmin', + required => [qw(groupId txGroupId admin)], + key_name => 'ownerPublicKey', + }, group_approval => { url => 'groups/approval', required => [qw(pendingSignature approval)], From 4e829a2d05819aa8f694e4bcff9667bc16b42ab2 Mon Sep 17 00:00:00 2001 From: CalDescent Date: Mon, 7 Nov 2022 21:12:34 +0000 Subject: [PATCH 19/19] Bump version to 3.7.0 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 860cdce5..52c574b0 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 org.qortal qortal - 3.6.4 + 3.7.0 jar true