diff --git a/src/main/java/org/qora/repository/AccountRepository.java b/src/main/java/org/qora/repository/AccountRepository.java index 913e8e1f..a3d4b296 100644 --- a/src/main/java/org/qora/repository/AccountRepository.java +++ b/src/main/java/org/qora/repository/AccountRepository.java @@ -99,6 +99,7 @@ public interface AccountRepository { public void save(ProxyForgerData proxyForgerData) throws DataException; + /** Delete proxy forging relationship from repository using passed forger's public key and recipient's address. */ public void delete(byte[] forgerPublickey, String recipient) throws DataException; // Forging accounts used by BlockGenerator @@ -107,6 +108,7 @@ public interface AccountRepository { public void save(ForgingAccountData forgingAccountData) throws DataException; + /** Delete forging account info, used by BlockGenerator, from repository using passed private key. */ public int delete(byte[] forgingAccountSeed) throws DataException; } diff --git a/src/main/java/org/qora/transaction/ProxyForgingTransaction.java b/src/main/java/org/qora/transaction/ProxyForgingTransaction.java index 4550e2fd..09b6c357 100644 --- a/src/main/java/org/qora/transaction/ProxyForgingTransaction.java +++ b/src/main/java/org/qora/transaction/ProxyForgingTransaction.java @@ -97,13 +97,19 @@ public class ProxyForgingTransaction extends Transaction { if (!Crypto.isValidAddress(recipient.getAddress())) return ValidationResult.INVALID_ADDRESS; - // If proxy public key aleady exists in repository, then it must be for the same forger-recipient combo + // If proxy public key already exists in repository, then it must be for the same forger-recipient combo ProxyForgerData proxyForgerData = this.repository.getAccountRepository().getProxyForgeData(this.proxyForgingTransactionData.getProxyPublicKey()); if (proxyForgerData != null) { if (!proxyForgerData.getRecipient().equals(recipient.getAddress()) || !Arrays.equals(proxyForgerData.getForgerPublicKey(), creator.getPublicKey())) return ValidationResult.INVALID_PUBLIC_KEY; } else { - // This is a new relationship - check that the generator hasn't reach maximum number of relationships + // This is a new relationship + + // No point starting a new relationship with 0% share (i.e. delete relationship) + if (this.proxyForgingTransactionData.getShare().compareTo(BigDecimal.ZERO) == 0) + return ValidationResult.INVALID_FORGE_SHARE; + + // Check that the generator hasn't reach maximum number of relationships int relationshipCount = this.repository.getAccountRepository().countProxyAccounts(creator.getPublicKey()); if (relationshipCount >= BlockChain.getInstance().getMaxProxyRelationships()) return ValidationResult.MAXIMUM_PROXY_RELATIONSHIPS; @@ -134,9 +140,14 @@ public class ProxyForgingTransaction extends Transaction { // Save this transaction, with previous share info this.repository.getTransactionRepository().save(proxyForgingTransactionData); - // Save proxy forging info - proxyForgerData = new ProxyForgerData(forger.getPublicKey(), proxyForgingTransactionData.getRecipient(), proxyForgingTransactionData.getProxyPublicKey(), proxyForgingTransactionData.getShare()); - this.repository.getAccountRepository().save(proxyForgerData); + // 0% share is actually a request to delete existing relationship + if (proxyForgingTransactionData.getShare().compareTo(BigDecimal.ZERO) == 0) { + this.repository.getAccountRepository().delete(forger.getPublicKey(), proxyForgingTransactionData.getRecipient()); + } else { + // Save proxy forging info + proxyForgerData = new ProxyForgerData(forger.getPublicKey(), proxyForgingTransactionData.getRecipient(), proxyForgingTransactionData.getProxyPublicKey(), proxyForgingTransactionData.getShare()); + this.repository.getAccountRepository().save(proxyForgerData); + } } @Override diff --git a/src/test/java/org/qora/test/common/AccountUtils.java b/src/test/java/org/qora/test/common/AccountUtils.java index 0b35361e..bd5fcfe4 100644 --- a/src/test/java/org/qora/test/common/AccountUtils.java +++ b/src/test/java/org/qora/test/common/AccountUtils.java @@ -33,7 +33,7 @@ public class AccountUtils { TransactionUtils.signAndForge(repository, transactionData, sendingAccount); } - public static byte[] proxyForging(Repository repository, String forger, String recipient, BigDecimal share) throws DataException { + public static TransactionData createProxyForging(Repository repository, String forger, String recipient, BigDecimal share) throws DataException { PrivateKeyAccount forgingAccount = Common.getTestAccount(repository, forger); PrivateKeyAccount recipientAccount = Common.getTestAccount(repository, recipient); @@ -46,8 +46,18 @@ public class AccountUtils { BaseTransactionData baseTransactionData = new BaseTransactionData(timestamp, txGroupId, reference, forgingAccount.getPublicKey(), fee, null); TransactionData transactionData = new ProxyForgingTransactionData(baseTransactionData, recipientAccount.getAddress(), proxyAccount.getPublicKey(), share); + return transactionData; + } + + public static byte[] proxyForging(Repository repository, String forger, String recipient, BigDecimal share) throws DataException { + TransactionData transactionData = createProxyForging(repository, forger, recipient, share); + + PrivateKeyAccount forgingAccount = Common.getTestAccount(repository, forger); TransactionUtils.signAndForge(repository, transactionData, forgingAccount); + PrivateKeyAccount recipientAccount = Common.getTestAccount(repository, recipient); + byte[] proxyPrivateKey = forgingAccount.getSharedSecret(recipientAccount.getPublicKey()); + return proxyPrivateKey; } diff --git a/src/test/java/org/qora/test/forging/ProxyForgingTests.java b/src/test/java/org/qora/test/forging/ProxyForgingTests.java new file mode 100644 index 00000000..b289c0e4 --- /dev/null +++ b/src/test/java/org/qora/test/forging/ProxyForgingTests.java @@ -0,0 +1,110 @@ +package org.qora.test.forging; + +import static org.junit.Assert.*; + +import java.math.BigDecimal; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.qora.account.PrivateKeyAccount; +import org.qora.data.account.ProxyForgerData; +import org.qora.repository.DataException; +import org.qora.repository.Repository; +import org.qora.repository.RepositoryManager; +import org.qora.test.common.AccountUtils; +import org.qora.test.common.BlockUtils; +import org.qora.test.common.Common; +import org.qora.utils.Base58; + +public class ProxyForgingTests extends Common { + + @Before + public void beforeTest() throws DataException { + Common.useDefaultSettings(); + } + + @After + public void afterTest() throws DataException { + Common.orphanCheck(); + } + + @Test + public void testCreateRelationship() throws DataException { + final BigDecimal share = new BigDecimal("12.8"); + + try (final Repository repository = RepositoryManager.getRepository()) { + PrivateKeyAccount aliceAccount = Common.getTestAccount(repository, "alice"); + PrivateKeyAccount bobAccount = Common.getTestAccount(repository, "bob"); + + // Create relationship + byte[] proxyPrivateKey = AccountUtils.proxyForging(repository, "alice", "bob", share); + PrivateKeyAccount proxyAccount = new PrivateKeyAccount(repository, proxyPrivateKey); + + // Confirm relationship info set correctly + + // Fetch using proxy public key + ProxyForgerData proxyForgerData = repository.getAccountRepository().getProxyForgeData(proxyAccount.getPublicKey()); + assertEquals("Incorrect generator public key", Base58.encode(aliceAccount.getPublicKey()), Base58.encode(proxyForgerData.getForgerPublicKey())); + assertEquals("Incorrect recipient", bobAccount.getAddress(), proxyForgerData.getRecipient()); + assertEqualBigDecimals("Incorrect reward share", share, proxyForgerData.getShare()); + + // Fetch using generator public key and recipient address combination + proxyForgerData = repository.getAccountRepository().getProxyForgeData(aliceAccount.getPublicKey(), bobAccount.getAddress()); + assertEquals("Incorrect generator public key", Base58.encode(aliceAccount.getPublicKey()), Base58.encode(proxyForgerData.getForgerPublicKey())); + assertEquals("Incorrect recipient", bobAccount.getAddress(), proxyForgerData.getRecipient()); + assertEqualBigDecimals("Incorrect reward share", share, proxyForgerData.getShare()); + + // Delete relationship + byte[] newProxyPrivateKey = AccountUtils.proxyForging(repository, "alice", "bob", BigDecimal.ZERO); + PrivateKeyAccount newProxyAccount = new PrivateKeyAccount(repository, newProxyPrivateKey); + + // Confirm proxy keys match + assertEquals("Proxy private keys differ", Base58.encode(proxyPrivateKey), Base58.encode(newProxyPrivateKey)); + assertEquals("Proxy public keys differ", Base58.encode(proxyAccount.getPublicKey()), Base58.encode(newProxyAccount.getPublicKey())); + + // Confirm relationship no longer exists in repository + + // Fetch using proxy public key + ProxyForgerData newProxyForgerData = repository.getAccountRepository().getProxyForgeData(proxyAccount.getPublicKey()); + assertNull("Proxy relationship data shouldn't exist", newProxyForgerData); + + // Fetch using generator public key and recipient address combination + newProxyForgerData = repository.getAccountRepository().getProxyForgeData(aliceAccount.getPublicKey(), bobAccount.getAddress()); + assertNull("Proxy relationship data shouldn't exist", newProxyForgerData); + + // Orphan last block to restore prior proxy relationship + BlockUtils.orphanLastBlock(repository); + + // Confirm proxy relationship restored correctly + + // Fetch using proxy public key + newProxyForgerData = repository.getAccountRepository().getProxyForgeData(proxyAccount.getPublicKey()); + assertNotNull("Proxy relationship data should have been restored", newProxyForgerData); + assertEquals("Incorrect generator public key", Base58.encode(aliceAccount.getPublicKey()), Base58.encode(newProxyForgerData.getForgerPublicKey())); + assertEquals("Incorrect recipient", bobAccount.getAddress(), newProxyForgerData.getRecipient()); + assertEqualBigDecimals("Incorrect reward share", share, newProxyForgerData.getShare()); + + // Fetch using generator public key and recipient address combination + newProxyForgerData = repository.getAccountRepository().getProxyForgeData(aliceAccount.getPublicKey(), bobAccount.getAddress()); + assertNotNull("Proxy relationship data should have been restored", newProxyForgerData); + assertEquals("Incorrect generator public key", Base58.encode(aliceAccount.getPublicKey()), Base58.encode(newProxyForgerData.getForgerPublicKey())); + assertEquals("Incorrect recipient", bobAccount.getAddress(), newProxyForgerData.getRecipient()); + assertEqualBigDecimals("Incorrect reward share", share, newProxyForgerData.getShare()); + + // Orphan another block to remove initial proxy relationship + BlockUtils.orphanLastBlock(repository); + + // Confirm proxy relationship no longer exists + + // Fetch using proxy public key + newProxyForgerData = repository.getAccountRepository().getProxyForgeData(proxyAccount.getPublicKey()); + assertNull("Proxy relationship data shouldn't exist", newProxyForgerData); + + // Fetch using generator public key and recipient address combination + newProxyForgerData = repository.getAccountRepository().getProxyForgeData(aliceAccount.getPublicKey(), bobAccount.getAddress()); + assertNull("Proxy relationship data shouldn't exist", newProxyForgerData); + } + } + +} diff --git a/src/test/resources/test-chain-v2.json b/src/test/resources/test-chain-v2.json index 18597782..19928abb 100644 --- a/src/test/resources/test-chain-v2.json +++ b/src/test/resources/test-chain-v2.json @@ -8,6 +8,7 @@ "maxBytesPerUnitFee": 1024, "unitFee": "0.1", "requireGroupForApproval": false, + "maxProxyRelationships": 8, "genesisInfo": { "version": 4, "timestamp": 0,