From 7a318c9fc7020ddf226590e35a52264faf4ed8cb Mon Sep 17 00:00:00 2001 From: catbref Date: Wed, 15 May 2019 10:40:13 +0100 Subject: [PATCH] Proxy forging improvements + account flags fixes Proxy forging recipient no longer needs a public key on the blockchain at the point PROXY_FORGING transaction is submitted. Proxy forging recipient is given a last-reference, if they don't have one, when they receive their first block rewards. Split block fees in proxy forging scenario, using same share proportion. 100% proxy sharing is now allowed. Fixed account flags processing for accounts in genesis block. --- src/main/java/org/qora/block/Block.java | 66 ++++++++++++++++--- .../hsqldb/HSQLDBAccountRepository.java | 1 - .../transaction/AccountFlagsTransaction.java | 14 +++- .../transaction/ProxyForgingTransaction.java | 15 ++++- 4 files changed, 81 insertions(+), 15 deletions(-) diff --git a/src/main/java/org/qora/block/Block.java b/src/main/java/org/qora/block/Block.java index e52020ef..f7253483 100644 --- a/src/main/java/org/qora/block/Block.java +++ b/src/main/java/org/qora/block/Block.java @@ -1050,10 +1050,8 @@ public class Block { for (Transaction transaction : transactions) transaction.process(); - // If fees are non-zero then add fees to generator's balance - BigDecimal blockFee = this.blockData.getTotalFees(); - if (blockFee.compareTo(BigDecimal.ZERO) > 0) - this.generator.setConfirmedBalance(Asset.QORA, this.generator.getConfirmedBalance(Asset.QORA).add(blockFee)); + // Give transaction fees to generator/proxy + rewardTransactionFees(); // Process AT fees and save AT states into repository ATRepository atRepository = this.repository.getATRepository(); @@ -1102,7 +1100,7 @@ public class Block { // Is generator public key actually a proxy forge key? ProxyForgerData proxyForgerData = this.repository.getAccountRepository().getProxyForgeData(this.blockData.getGeneratorPublicKey()); if (proxyForgerData != null) { - // Split reward to forger and recipient; + // Split reward between forger and recipient Account recipient = new Account(this.repository, proxyForgerData.getRecipient()); BigDecimal recipientShare = reward.multiply(proxyForgerData.getShare().movePointLeft(2)).setScale(8, RoundingMode.DOWN); recipient.setConfirmedBalance(Asset.QORA, recipient.getConfirmedBalance(Asset.QORA).add(recipientShare)); @@ -1117,6 +1115,31 @@ public class Block { this.generator.setConfirmedBalance(Asset.QORA, this.generator.getConfirmedBalance(Asset.QORA).add(reward)); } + protected void rewardTransactionFees() throws DataException { + BigDecimal blockFees = this.blockData.getTotalFees(); + + // No transaction fees? + if (blockFees.compareTo(BigDecimal.ZERO) <= 0) + return; + + // Is generator public key actually a proxy forge key? + ProxyForgerData proxyForgerData = this.repository.getAccountRepository().getProxyForgeData(this.blockData.getGeneratorPublicKey()); + if (proxyForgerData != null) { + // Split fees between forger and recipient + Account recipient = new Account(this.repository, proxyForgerData.getRecipient()); + BigDecimal recipientShare = blockFees.multiply(proxyForgerData.getShare().movePointLeft(2)).setScale(8, RoundingMode.DOWN); + recipient.setConfirmedBalance(Asset.QORA, recipient.getConfirmedBalance(Asset.QORA).add(recipientShare)); + + Account forger = new PublicKeyAccount(this.repository, proxyForgerData.getForgerPublicKey()); + BigDecimal forgerShare = blockFees.subtract(recipientShare); + forger.setConfirmedBalance(Asset.QORA, forger.getConfirmedBalance(Asset.QORA).add(forgerShare)); + return; + } + + // Give transaction fees to generator + this.generator.setConfirmedBalance(Asset.QORA, this.generator.getConfirmedBalance(Asset.QORA).add(blockFees)); + } + /** * Removes block from blockchain undoing transactions and adding them to unconfirmed pile. * @@ -1145,10 +1168,8 @@ public class Block { // Block rewards removed after transactions undone orphanBlockRewards(); - // If fees are non-zero then remove fees from generator's balance - BigDecimal blockFee = this.blockData.getTotalFees(); - if (blockFee.compareTo(BigDecimal.ZERO) > 0) - this.generator.setConfirmedBalance(Asset.QORA, this.generator.getConfirmedBalance(Asset.QORA).subtract(blockFee)); + // Deduct any transaction fees from generator/proxy + deductTransactionFees(); // Return AT fees and delete AT states from repository ATRepository atRepository = this.repository.getATRepository(); @@ -1175,7 +1196,7 @@ public class Block { // Is generator public key actually a proxy forge key? ProxyForgerData proxyForgerData = this.repository.getAccountRepository().getProxyForgeData(this.blockData.getGeneratorPublicKey()); if (proxyForgerData != null) { - // Split reward from forger and recipient; + // Split reward between forger and recipient Account recipient = new Account(this.repository, proxyForgerData.getRecipient()); BigDecimal recipientShare = reward.multiply(proxyForgerData.getShare().movePointLeft(2)).setScale(8, RoundingMode.DOWN); recipient.setConfirmedBalance(Asset.QORA, recipient.getConfirmedBalance(Asset.QORA).subtract(recipientShare)); @@ -1190,6 +1211,31 @@ public class Block { this.generator.setConfirmedBalance(Asset.QORA, this.generator.getConfirmedBalance(Asset.QORA).subtract(reward)); } + protected void deductTransactionFees() throws DataException { + BigDecimal blockFees = this.blockData.getTotalFees(); + + // No transaction fees? + if (blockFees.compareTo(BigDecimal.ZERO) <= 0) + return; + + // Is generator public key actually a proxy forge key? + ProxyForgerData proxyForgerData = this.repository.getAccountRepository().getProxyForgeData(this.blockData.getGeneratorPublicKey()); + if (proxyForgerData != null) { + // Split fees between forger and recipient + Account recipient = new Account(this.repository, proxyForgerData.getRecipient()); + BigDecimal recipientShare = blockFees.multiply(proxyForgerData.getShare().movePointLeft(2)).setScale(8, RoundingMode.DOWN); + recipient.setConfirmedBalance(Asset.QORA, recipient.getConfirmedBalance(Asset.QORA).subtract(recipientShare)); + + Account forger = new PublicKeyAccount(this.repository, proxyForgerData.getForgerPublicKey()); + BigDecimal forgerShare = blockFees.subtract(recipientShare); + forger.setConfirmedBalance(Asset.QORA, forger.getConfirmedBalance(Asset.QORA).subtract(forgerShare)); + return; + } + + // Deduct transaction fees to generator + this.generator.setConfirmedBalance(Asset.QORA, this.generator.getConfirmedBalance(Asset.QORA).subtract(blockFees)); + } + protected BigDecimal getRewardAtHeight(int ourHeight) { List rewardsByHeight = BlockChain.getInstance().getBlockRewardsByHeight(); diff --git a/src/main/java/org/qora/repository/hsqldb/HSQLDBAccountRepository.java b/src/main/java/org/qora/repository/hsqldb/HSQLDBAccountRepository.java index 8c8ef013..943fb79f 100644 --- a/src/main/java/org/qora/repository/hsqldb/HSQLDBAccountRepository.java +++ b/src/main/java/org/qora/repository/hsqldb/HSQLDBAccountRepository.java @@ -78,7 +78,6 @@ public class HSQLDBAccountRepository implements AccountRepository { if (resultSet == null) return null; - // Column is NOT NULL so this should never implicitly convert to 0 return resultSet.getInt(1); } catch (SQLException e) { throw new DataException("Unable to fetch account's flags from repository", e); diff --git a/src/main/java/org/qora/transaction/AccountFlagsTransaction.java b/src/main/java/org/qora/transaction/AccountFlagsTransaction.java index 3ba20986..55d511aa 100644 --- a/src/main/java/org/qora/transaction/AccountFlagsTransaction.java +++ b/src/main/java/org/qora/transaction/AccountFlagsTransaction.java @@ -91,13 +91,17 @@ public class AccountFlagsTransaction extends Transaction { @Override public void process() throws DataException { Account target = getTarget(); - int previousFlags = target.getFlags(); + Integer previousFlags = target.getFlags(); accountFlagsTransactionData.setPreviousFlags(previousFlags); // Save this transaction with target account's previous flags value this.repository.getTransactionRepository().save(accountFlagsTransactionData); + // If account doesn't have entry in database yet (e.g. genesis block) then flags are zero + if (previousFlags == null) + previousFlags = 0; + // Set account's new flags int newFlags = previousFlags & accountFlagsTransactionData.getAndMask() | accountFlagsTransactionData.getOrMask() ^ accountFlagsTransactionData.getXorMask(); @@ -117,7 +121,13 @@ public class AccountFlagsTransaction extends Transaction { // Revert Account target = getTarget(); - target.setFlags(accountFlagsTransactionData.getPreviousFlags()); + Integer previousFlags = accountFlagsTransactionData.getPreviousFlags(); + + // If previousFlags are null then account didn't exist before this transaction + if (previousFlags == null) + this.repository.getAccountRepository().delete(target.getAddress()); + else + target.setFlags(previousFlags); // Delete this transaction itself this.repository.getTransactionRepository().delete(accountFlagsTransactionData); diff --git a/src/main/java/org/qora/transaction/ProxyForgingTransaction.java b/src/main/java/org/qora/transaction/ProxyForgingTransaction.java index 00d37fad..21f2aa62 100644 --- a/src/main/java/org/qora/transaction/ProxyForgingTransaction.java +++ b/src/main/java/org/qora/transaction/ProxyForgingTransaction.java @@ -10,7 +10,6 @@ import org.qora.account.Forging; import org.qora.account.PublicKeyAccount; import org.qora.asset.Asset; import org.qora.crypto.Crypto; -import org.qora.data.account.AccountData; import org.qora.data.account.ProxyForgerData; import org.qora.data.transaction.ProxyForgingTransactionData; import org.qora.data.transaction.TransactionData; @@ -80,7 +79,7 @@ public class ProxyForgingTransaction extends Transaction { public ValidationResult isValid() throws DataException { // Check reward share given to recipient if (this.proxyForgingTransactionData.getShare().compareTo(BigDecimal.ZERO) <= 0 - || this.proxyForgingTransactionData.getShare().compareTo(MAX_SHARE) >= 0) + || this.proxyForgingTransactionData.getShare().compareTo(MAX_SHARE) > 0) return ValidationResult.INVALID_FORGE_SHARE; PublicKeyAccount creator = getCreator(); @@ -97,10 +96,12 @@ public class ProxyForgingTransaction extends Transaction { if (!Crypto.isValidAddress(recipient.getAddress())) return ValidationResult.INVALID_ADDRESS; + /* Not needed? // Check recipient has known public key AccountData recipientData = this.repository.getAccountRepository().getAccount(recipient.getAddress()); if (recipientData == null || recipientData.getPublicKey() == null) return ValidationResult.PUBLIC_KEY_UNKNOWN; + */ // Check fee is positive if (proxyForgingTransactionData.getFee().compareTo(BigDecimal.ZERO) <= 0) @@ -140,6 +141,11 @@ public class ProxyForgingTransaction extends Transaction { // Update forger's reference forger.setLastReference(proxyForgingTransactionData.getSignature()); + + // If proxy recipient has no last-reference then use this transaction's signature as last-reference so they can spend their block rewards + Account recipient = new Account(this.repository, proxyForgingTransactionData.getRecipient()); + if (recipient.getLastReference() == null) + recipient.setLastReference(proxyForgingTransactionData.getSignature()); } @Override @@ -166,6 +172,11 @@ public class ProxyForgingTransaction extends Transaction { // Update forger's reference forger.setLastReference(proxyForgingTransactionData.getReference()); + + // If recipient didn't have a last-reference prior to this transaction then remove it + Account recipient = new Account(this.repository, proxyForgingTransactionData.getRecipient()); + if (Arrays.equals(recipient.getLastReference(), proxyForgingTransactionData.getSignature())) + recipient.setLastReference(null); } }