From 3c06d358b754fd24bc038eb8f995d484bed8dbf9 Mon Sep 17 00:00:00 2001 From: catbref Date: Fri, 1 Mar 2019 17:18:06 +0000 Subject: [PATCH] interim commit with proxy forging repository/transaction support no block validity/generator support yet --- src/main/java/org/qora/account/Account.java | 13 ++ .../qora/api/resource/AddressesResource.java | 53 +++++ .../org/qora/api/resource/BlocksResource.java | 54 +++++ .../org/qora/data/account/AccountData.java | 14 +- .../qora/data/account/ProxyForgerData.java | 50 +++++ .../EnableForgingTransactionData.java | 53 +++++ .../ProxyForgingTransactionData.java | 81 +++++++ .../data/transaction/TransactionData.java | 2 +- .../qora/repository/AccountRepository.java | 22 ++ .../org/qora/repository/BlockRepository.java | 9 + .../hsqldb/HSQLDBAccountRepository.java | 91 +++++++- .../hsqldb/HSQLDBBlockRepository.java | 9 + .../hsqldb/HSQLDBDatabaseUpdates.java | 21 ++ ...LDBEnableForgingTransactionRepository.java | 49 +++++ ...QLDBProxyForgingTransactionRepository.java | 53 +++++ .../transaction/EnableForgingTransaction.java | 198 ++++++++++++++++++ .../transaction/ProxyForgingTransaction.java | 174 +++++++++++++++ .../org/qora/transaction/Transaction.java | 11 +- .../transform/block/BlockTransformer.java | 4 +- .../EnableForgingTransactionTransformer.java | 83 ++++++++ .../ProxyForgingTransactionTransformer.java | 93 ++++++++ .../java/org/qora/test/TransactionTests.java | 4 +- 22 files changed, 1131 insertions(+), 10 deletions(-) create mode 100644 src/main/java/org/qora/data/account/ProxyForgerData.java create mode 100644 src/main/java/org/qora/data/transaction/EnableForgingTransactionData.java create mode 100644 src/main/java/org/qora/data/transaction/ProxyForgingTransactionData.java create mode 100644 src/main/java/org/qora/repository/hsqldb/transaction/HSQLDBEnableForgingTransactionRepository.java create mode 100644 src/main/java/org/qora/repository/hsqldb/transaction/HSQLDBProxyForgingTransactionRepository.java create mode 100644 src/main/java/org/qora/transaction/EnableForgingTransaction.java create mode 100644 src/main/java/org/qora/transaction/ProxyForgingTransaction.java create mode 100644 src/main/java/org/qora/transform/transaction/EnableForgingTransactionTransformer.java create mode 100644 src/main/java/org/qora/transform/transaction/ProxyForgingTransactionTransformer.java diff --git a/src/main/java/org/qora/account/Account.java b/src/main/java/org/qora/account/Account.java index 1f760fb9..4664fc1e 100644 --- a/src/main/java/org/qora/account/Account.java +++ b/src/main/java/org/qora/account/Account.java @@ -21,6 +21,11 @@ public class Account { private static final Logger LOGGER = LogManager.getLogger(Account.class); + public static final int TIER1_FORGING_MASK = 0x1; + public static final int TIER2_FORGING_MASK = 0x2; + public static final int TIER3_FORGING_MASK = 0x4; + public static final int FORGING_MASK = TIER1_FORGING_MASK | TIER2_FORGING_MASK | TIER3_FORGING_MASK; + public static final int ADDRESS_LENGTH = 25; protected Repository repository; @@ -238,4 +243,12 @@ public class Account { this.repository.getAccountRepository().setFlags(accountData); } + // Forging Enabler + + public void setForgingEnabler(String address) throws DataException { + AccountData accountData = this.buildAccountData(); + accountData.setForgingEnabler(address); + this.repository.getAccountRepository().setForgingEnabler(accountData); + } + } diff --git a/src/main/java/org/qora/api/resource/AddressesResource.java b/src/main/java/org/qora/api/resource/AddressesResource.java index ddff4312..ea91728d 100644 --- a/src/main/java/org/qora/api/resource/AddressesResource.java +++ b/src/main/java/org/qora/api/resource/AddressesResource.java @@ -3,6 +3,7 @@ package org.qora.api.resource; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.media.Content; import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.parameters.RequestBody; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.tags.Tag; @@ -10,6 +11,7 @@ import java.math.BigDecimal; import javax.servlet.http.HttpServletRequest; import javax.ws.rs.GET; +import javax.ws.rs.POST; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; @@ -24,12 +26,17 @@ import org.qora.api.ApiExceptionFactory; import org.qora.asset.Asset; import org.qora.crypto.Crypto; import org.qora.data.account.AccountData; +import org.qora.data.transaction.ProxyForgingTransactionData; import org.qora.group.Group; import org.qora.repository.DataException; import org.qora.repository.Repository; import org.qora.repository.RepositoryManager; import org.qora.settings.Settings; +import org.qora.transaction.Transaction; +import org.qora.transaction.Transaction.ValidationResult; +import org.qora.transform.TransformationException; import org.qora.transform.Transformer; +import org.qora.transform.transaction.ProxyForgingTransactionTransformer; import org.qora.utils.Base58; @Path("/addresses") @@ -261,4 +268,50 @@ public class AddressesResource { } } + @POST + @Path("/proxyforging") + @Operation( + summary = "Build raw, unsigned, PROXY_FORGING transaction", + requestBody = @RequestBody( + required = true, + content = @Content( + mediaType = MediaType.APPLICATION_JSON, + schema = @Schema( + implementation = ProxyForgingTransactionData.class + ) + ) + ), + responses = { + @ApiResponse( + description = "raw, unsigned, PROXY_FORGING transaction encoded in Base58", + content = @Content( + mediaType = MediaType.TEXT_PLAIN, + schema = @Schema( + type = "string" + ) + ) + ) + } + ) + @ApiErrors({ApiError.NON_PRODUCTION, ApiError.TRANSACTION_INVALID, ApiError.TRANSFORMATION_ERROR, ApiError.REPOSITORY_ISSUE}) + public String proxyForging(ProxyForgingTransactionData transactionData) { + if (Settings.getInstance().isApiRestricted()) + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.NON_PRODUCTION); + + try (final Repository repository = RepositoryManager.getRepository()) { + Transaction transaction = Transaction.fromData(repository, transactionData); + + ValidationResult result = transaction.isValidUnconfirmed(); + if (result != ValidationResult.OK) + throw TransactionsResource.createTransactionInvalidException(request, result); + + byte[] bytes = ProxyForgingTransactionTransformer.toBytes(transactionData); + return Base58.encode(bytes); + } catch (TransformationException e) { + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.TRANSFORMATION_ERROR, e); + } catch (DataException e) { + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e); + } + } + } diff --git a/src/main/java/org/qora/api/resource/BlocksResource.java b/src/main/java/org/qora/api/resource/BlocksResource.java index 68aac5be..415f94ee 100644 --- a/src/main/java/org/qora/api/resource/BlocksResource.java +++ b/src/main/java/org/qora/api/resource/BlocksResource.java @@ -5,6 +5,7 @@ import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.media.ArraySchema; import io.swagger.v3.oas.annotations.media.Content; import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.parameters.RequestBody; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.tags.Tag; @@ -15,6 +16,7 @@ import java.util.List; import javax.servlet.http.HttpServletRequest; import javax.ws.rs.GET; +import javax.ws.rs.POST; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; @@ -28,10 +30,16 @@ import org.qora.api.ApiException; import org.qora.api.ApiExceptionFactory; import org.qora.block.Block; import org.qora.data.block.BlockData; +import org.qora.data.transaction.EnableForgingTransactionData; import org.qora.data.transaction.TransactionData; import org.qora.repository.DataException; import org.qora.repository.Repository; import org.qora.repository.RepositoryManager; +import org.qora.settings.Settings; +import org.qora.transaction.Transaction; +import org.qora.transaction.Transaction.ValidationResult; +import org.qora.transform.TransformationException; +import org.qora.transform.transaction.EnableForgingTransactionTransformer; import org.qora.utils.Base58; @Path("/blocks") @@ -518,4 +526,50 @@ public class BlocksResource { } } + @POST + @Path("/enableforging") + @Operation( + summary = "Build raw, unsigned, ENABLE_FORGING transaction", + requestBody = @RequestBody( + required = true, + content = @Content( + mediaType = MediaType.APPLICATION_JSON, + schema = @Schema( + implementation = EnableForgingTransactionData.class + ) + ) + ), + responses = { + @ApiResponse( + description = "raw, unsigned, ENABLE_FORGING transaction encoded in Base58", + content = @Content( + mediaType = MediaType.TEXT_PLAIN, + schema = @Schema( + type = "string" + ) + ) + ) + } + ) + @ApiErrors({ApiError.NON_PRODUCTION, ApiError.TRANSACTION_INVALID, ApiError.TRANSFORMATION_ERROR, ApiError.REPOSITORY_ISSUE}) + public String enableForging(EnableForgingTransactionData transactionData) { + if (Settings.getInstance().isApiRestricted()) + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.NON_PRODUCTION); + + try (final Repository repository = RepositoryManager.getRepository()) { + Transaction transaction = Transaction.fromData(repository, transactionData); + + ValidationResult result = transaction.isValidUnconfirmed(); + if (result != ValidationResult.OK) + throw TransactionsResource.createTransactionInvalidException(request, result); + + byte[] bytes = EnableForgingTransactionTransformer.toBytes(transactionData); + return Base58.encode(bytes); + } catch (TransformationException e) { + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.TRANSFORMATION_ERROR, e); + } catch (DataException e) { + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e); + } + } + } diff --git a/src/main/java/org/qora/data/account/AccountData.java b/src/main/java/org/qora/data/account/AccountData.java index 71de7c3b..7cb92604 100644 --- a/src/main/java/org/qora/data/account/AccountData.java +++ b/src/main/java/org/qora/data/account/AccountData.java @@ -15,6 +15,7 @@ public class AccountData { protected byte[] publicKey; protected int defaultGroupId; protected int flags; + protected String forgingEnabler; // Constructors @@ -22,16 +23,17 @@ public class AccountData { protected AccountData() { } - public AccountData(String address, byte[] reference, byte[] publicKey, int defaultGroupId, int flags) { + public AccountData(String address, byte[] reference, byte[] publicKey, int defaultGroupId, int flags, String forgingEnabler) { this.address = address; this.reference = reference; this.publicKey = publicKey; this.defaultGroupId = defaultGroupId; this.flags = flags; + this.forgingEnabler = forgingEnabler; } public AccountData(String address) { - this(address, null, null, Group.NO_GROUP, 0); + this(address, null, null, Group.NO_GROUP, 0, null); } // Getters/Setters @@ -72,6 +74,14 @@ public class AccountData { this.flags = flags; } + public String getForgingEnabler() { + return this.forgingEnabler; + } + + public void setForgingEnabler(String forgingEnabler) { + this.forgingEnabler = forgingEnabler; + } + // Comparison @Override diff --git a/src/main/java/org/qora/data/account/ProxyForgerData.java b/src/main/java/org/qora/data/account/ProxyForgerData.java new file mode 100644 index 00000000..714565e4 --- /dev/null +++ b/src/main/java/org/qora/data/account/ProxyForgerData.java @@ -0,0 +1,50 @@ +package org.qora.data.account; + +import java.math.BigDecimal; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; + +// All properties to be converted to JSON via JAXB +@XmlAccessorType(XmlAccessType.FIELD) +public class ProxyForgerData { + + // Properties + private byte[] forgerPublicKey; + private String recipient; + private byte[] proxyPublicKey; + private BigDecimal share; + + // Constructors + + // For JAXB + protected ProxyForgerData() { + } + + // Used when fetching from repository + public ProxyForgerData(byte[] forgerPublicKey, String recipient, byte[] proxyPublicKey, BigDecimal share) { + this.forgerPublicKey = forgerPublicKey; + this.recipient = recipient; + this.proxyPublicKey = proxyPublicKey; + this.share = share; + } + + // Getters / setters + + public byte[] getForgerPublicKey() { + return this.forgerPublicKey; + } + + public String getRecipient() { + return this.recipient; + } + + public byte[] getProxyPublicKey() { + return this.proxyPublicKey; + } + + public BigDecimal getShare() { + return this.share; + } + +} diff --git a/src/main/java/org/qora/data/transaction/EnableForgingTransactionData.java b/src/main/java/org/qora/data/transaction/EnableForgingTransactionData.java new file mode 100644 index 00000000..2e76d0da --- /dev/null +++ b/src/main/java/org/qora/data/transaction/EnableForgingTransactionData.java @@ -0,0 +1,53 @@ +package org.qora.data.transaction; + +import java.math.BigDecimal; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlElement; + +import org.qora.transaction.Transaction.TransactionType; + +import io.swagger.v3.oas.annotations.media.Schema; + +// All properties to be converted to JSON via JAXB +@XmlAccessorType(XmlAccessType.FIELD) +@Schema(allOf = {TransactionData.class}) +public class EnableForgingTransactionData extends TransactionData { + + private String target; + + // Constructors + + // For JAXB + protected EnableForgingTransactionData() { + super(TransactionType.ENABLE_FORGING); + } + + public EnableForgingTransactionData(long timestamp, int groupId, byte[] reference, byte[] creatorPublicKey, String target, BigDecimal fee, byte[] signature) { + super(TransactionType.ENABLE_FORGING, timestamp, groupId, reference, creatorPublicKey, fee, signature); + + this.target = target; + } + + // Getters / setters + + public String getTarget() { + return this.target; + } + + // Re-expose to JAXB + + @Override + @XmlElement + public byte[] getCreatorPublicKey() { + return super.getCreatorPublicKey(); + } + + @Override + @XmlElement + public void setCreatorPublicKey(byte[] creatorPublicKey) { + super.setCreatorPublicKey(creatorPublicKey); + } + +} diff --git a/src/main/java/org/qora/data/transaction/ProxyForgingTransactionData.java b/src/main/java/org/qora/data/transaction/ProxyForgingTransactionData.java new file mode 100644 index 00000000..2e645cac --- /dev/null +++ b/src/main/java/org/qora/data/transaction/ProxyForgingTransactionData.java @@ -0,0 +1,81 @@ +package org.qora.data.transaction; + +import java.math.BigDecimal; + +import javax.xml.bind.Unmarshaller; +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlTransient; + +import org.qora.transaction.Transaction.TransactionType; + +import io.swagger.v3.oas.annotations.media.Schema; + +// All properties to be converted to JSON via JAXB +@XmlAccessorType(XmlAccessType.FIELD) +@Schema(allOf = {TransactionData.class}) +public class ProxyForgingTransactionData extends TransactionData { + + private byte[] forgerPublicKey; + private String recipient; + private byte[] proxyPublicKey; + private BigDecimal share; + // No need to ever expose this via API + @XmlTransient + @Schema(hidden = true) + private BigDecimal previousShare; + + // Constructors + + // For JAXB + protected ProxyForgingTransactionData() { + super(TransactionType.PROXY_FORGING); + } + + public void afterUnmarshal(Unmarshaller u, Object parent) { + this.creatorPublicKey = this.forgerPublicKey; + } + + + public ProxyForgingTransactionData(long timestamp, int groupId, byte[] reference, byte[] forgerPublicKey, String recipient, byte[] proxyPublicKey, BigDecimal share, BigDecimal previousShare, BigDecimal fee, byte[] signature) { + super(TransactionType.PROXY_FORGING, timestamp, groupId, reference, forgerPublicKey, fee, signature); + + this.forgerPublicKey = forgerPublicKey; + this.recipient = recipient; + this.proxyPublicKey = proxyPublicKey; + this.share = share; + this.previousShare = previousShare; + } + + // Used in deserialization context + public ProxyForgingTransactionData(long timestamp, int groupId, byte[] reference, byte[] forgerPublicKey, String recipient, byte[] proxyPublicKey, BigDecimal share, BigDecimal fee, byte[] signature) { + this(timestamp, groupId, reference, forgerPublicKey, recipient, proxyPublicKey, share, null, fee, signature); + } + + // Getters / setters + + public byte[] getForgerPublicKey() { + return this.forgerPublicKey; + } + + public String getRecipient() { + return this.recipient; + } + + public byte[] getProxyPublicKey() { + return this.proxyPublicKey; + } + + public BigDecimal getShare() { + return this.share; + } + + public BigDecimal getPreviousShare() { + return this.previousShare; + } + + public void setPreviousShare(BigDecimal previousShare) { + this.previousShare = previousShare; + } + +} diff --git a/src/main/java/org/qora/data/transaction/TransactionData.java b/src/main/java/org/qora/data/transaction/TransactionData.java index aaa20e6e..aff493a0 100644 --- a/src/main/java/org/qora/data/transaction/TransactionData.java +++ b/src/main/java/org/qora/data/transaction/TransactionData.java @@ -38,7 +38,7 @@ import io.swagger.v3.oas.annotations.media.Schema.AccessMode; JoinGroupTransactionData.class, LeaveGroupTransactionData.class, GroupApprovalTransactionData.class, SetGroupTransactionData.class, UpdateAssetTransactionData.class, - AccountFlagsTransactionData.class + AccountFlagsTransactionData.class, EnableForgingTransactionData.class, ProxyForgingTransactionData.class }) //All properties to be converted to JSON via JAXB @XmlAccessorType(XmlAccessType.FIELD) diff --git a/src/main/java/org/qora/repository/AccountRepository.java b/src/main/java/org/qora/repository/AccountRepository.java index 50950441..f7b6f3dc 100644 --- a/src/main/java/org/qora/repository/AccountRepository.java +++ b/src/main/java/org/qora/repository/AccountRepository.java @@ -4,6 +4,7 @@ import java.util.List; import org.qora.data.account.AccountBalanceData; import org.qora.data.account.AccountData; +import org.qora.data.account.ProxyForgerData; public interface AccountRepository { @@ -21,6 +22,9 @@ public interface AccountRepository { /** Returns account's flags or null if account not found. */ public Integer getFlags(String address) throws DataException; + /** Returns number of accounts enabled to forge by given address. */ + public int countForgingAccountsEnabledByAddress(String address) throws DataException; + /** * Ensures at least minimal account info in repository. *

@@ -49,6 +53,14 @@ public interface AccountRepository { */ public void setFlags(AccountData accountData) throws DataException; + /** + * Saves account's forging enabler, and public key if present, in repository. + *

+ * Note: ignores other fields like last reference, default groupID. + */ + public void setForgingEnabler(AccountData accountData) throws DataException; + + /** Delete account from repository. */ public void delete(String address) throws DataException; // Account balances @@ -67,4 +79,14 @@ public interface AccountRepository { public void delete(String address, long assetId) throws DataException; + // Proxy forging + + public ProxyForgerData getProxyForgeData(byte[] forgerPublicKey, String recipient) throws DataException; + + public ProxyForgerData getProxyForgeData(byte[] proxyPublicKey) throws DataException; + + public void save(ProxyForgerData proxyForgerData) throws DataException; + + public void delete(byte[] forgerPublickey, String recipient) throws DataException; + } diff --git a/src/main/java/org/qora/repository/BlockRepository.java b/src/main/java/org/qora/repository/BlockRepository.java index be1e683f..2ca0260f 100644 --- a/src/main/java/org/qora/repository/BlockRepository.java +++ b/src/main/java/org/qora/repository/BlockRepository.java @@ -92,6 +92,15 @@ public interface BlockRepository { return getTransactionsFromSignature(signature, null, null, null); } + /** + * Returns number of blocks forged by account with given public key. + * + * @param publicKey + * @return number of blocks + * @throws DataException + */ + public int countForgedBlocks(byte[] publicKey) throws DataException; + /** * Saves block into repository. * diff --git a/src/main/java/org/qora/repository/hsqldb/HSQLDBAccountRepository.java b/src/main/java/org/qora/repository/hsqldb/HSQLDBAccountRepository.java index d7d1bf1f..e3335e73 100644 --- a/src/main/java/org/qora/repository/hsqldb/HSQLDBAccountRepository.java +++ b/src/main/java/org/qora/repository/hsqldb/HSQLDBAccountRepository.java @@ -11,6 +11,7 @@ import java.util.stream.Collectors; import org.qora.data.account.AccountBalanceData; import org.qora.data.account.AccountData; +import org.qora.data.account.ProxyForgerData; import org.qora.repository.AccountRepository; import org.qora.repository.DataException; @@ -26,7 +27,8 @@ public class HSQLDBAccountRepository implements AccountRepository { @Override public AccountData getAccount(String address) throws DataException { - try (ResultSet resultSet = this.repository.checkedExecute("SELECT reference, public_key, default_group_id, flags FROM Accounts WHERE account = ?", address)) { + try (ResultSet resultSet = this.repository + .checkedExecute("SELECT reference, public_key, default_group_id, flags, forging_enabler FROM Accounts WHERE account = ?", address)) { if (resultSet == null) return null; @@ -34,8 +36,9 @@ public class HSQLDBAccountRepository implements AccountRepository { byte[] publicKey = resultSet.getBytes(2); int defaultGroupId = resultSet.getInt(3); int flags = resultSet.getInt(4); + String forgingEnabler = resultSet.getString(5); - return new AccountData(address, reference, publicKey, defaultGroupId, flags); + return new AccountData(address, reference, publicKey, defaultGroupId, flags, forgingEnabler); } catch (SQLException e) { throw new DataException("Unable to fetch account info from repository", e); } @@ -79,6 +82,15 @@ public class HSQLDBAccountRepository implements AccountRepository { } } + @Override + public int countForgingAccountsEnabledByAddress(String address) throws DataException { + try (ResultSet resultSet = this.repository.checkedExecute("SELECT COUNT(*) FROM Accounts WHERE forging_enabler = ? LIMIT 1", address)) { + return resultSet.getInt(1); + } catch (SQLException e) { + throw new DataException("Unable to count forging accounts enabled in repository", e); + } + } + @Override public void ensureAccount(AccountData accountData) throws DataException { HSQLDBSaver saveHelper = new HSQLDBSaver("Accounts"); @@ -147,6 +159,23 @@ public class HSQLDBAccountRepository implements AccountRepository { } } + @Override + public void setForgingEnabler(AccountData accountData) throws DataException { + HSQLDBSaver saveHelper = new HSQLDBSaver("Accounts"); + + saveHelper.bind("account", accountData.getAddress()).bind("forging_enabler", accountData.getForgingEnabler()); + + byte[] publicKey = accountData.getPublicKey(); + if (publicKey != null) + saveHelper.bind("public_key", publicKey); + + try { + saveHelper.execute(this.repository); + } catch (SQLException e) { + throw new DataException("Unable to save account's forging enabler into repository", e); + } + } + @Override public void delete(String address) throws DataException { // NOTE: Account balances are deleted automatically by the database thanks to "ON DELETE CASCADE" in AccountBalances' FOREIGN KEY @@ -267,4 +296,62 @@ public class HSQLDBAccountRepository implements AccountRepository { } } + // Proxy forging + + @Override + public ProxyForgerData getProxyForgeData(byte[] forgerPublicKey, String recipient) throws DataException { + try (ResultSet resultSet = this.repository.checkedExecute("SELECT proxy_public_key, share FROM ProxyForgers WHERE forger = ? AND recipient = ?", + forgerPublicKey, recipient)) { + if (resultSet == null) + return null; + + byte[] proxyPublicKey = resultSet.getBytes(1); + BigDecimal share = resultSet.getBigDecimal(2); + + return new ProxyForgerData(forgerPublicKey, recipient, proxyPublicKey, share); + } catch (SQLException e) { + throw new DataException("Unable to fetch proxy forge info from repository", e); + } + } + + @Override + public ProxyForgerData getProxyForgeData(byte[] proxyPublicKey) throws DataException { + try (ResultSet resultSet = this.repository.checkedExecute("SELECT forger, recipient, share FROM ProxyForgers WHERE proxy_public_key = ?", + proxyPublicKey)) { + if (resultSet == null) + return null; + + byte[] forgerPublicKey = resultSet.getBytes(1); + String recipient = resultSet.getString(2); + BigDecimal share = resultSet.getBigDecimal(3); + + return new ProxyForgerData(forgerPublicKey, recipient, proxyPublicKey, share); + } catch (SQLException e) { + throw new DataException("Unable to fetch proxy forge info from repository", e); + } + } + + @Override + public void save(ProxyForgerData proxyForgerData) throws DataException { + HSQLDBSaver saveHelper = new HSQLDBSaver("ProxyForgers"); + + saveHelper.bind("forger", proxyForgerData.getForgerPublicKey()).bind("recipient", proxyForgerData.getRecipient()) + .bind("proxy_public_key", proxyForgerData.getProxyPublicKey()).bind("share", proxyForgerData.getShare()); + + try { + saveHelper.execute(this.repository); + } catch (SQLException e) { + throw new DataException("Unable to save proxy forge info into repository", e); + } + } + + @Override + public void delete(byte[] forgerPublickey, String recipient) throws DataException { + try { + this.repository.delete("ProxyForgers", "forger = ? and recipient = ?", forgerPublickey, recipient); + } catch (SQLException e) { + throw new DataException("Unable to delete proxy forge info from repository", e); + } + } + } diff --git a/src/main/java/org/qora/repository/hsqldb/HSQLDBBlockRepository.java b/src/main/java/org/qora/repository/hsqldb/HSQLDBBlockRepository.java index b7a23ff4..74982a17 100644 --- a/src/main/java/org/qora/repository/hsqldb/HSQLDBBlockRepository.java +++ b/src/main/java/org/qora/repository/hsqldb/HSQLDBBlockRepository.java @@ -149,6 +149,15 @@ public class HSQLDBBlockRepository implements BlockRepository { return transactions; } + @Override + public int countForgedBlocks(byte[] publicKey) throws DataException { + try (ResultSet resultSet = this.repository.checkedExecute("SELECT COUNT(*) FROM Blocks WHERE generator = ? LIMIT 1", publicKey)) { + return resultSet.getInt(1); + } catch (SQLException e) { + throw new DataException("Unable to fetch forged blocks count from repository", e); + } + } + @Override public void save(BlockData blockData) throws DataException { HSQLDBSaver saveHelper = new HSQLDBSaver("Blocks"); diff --git a/src/main/java/org/qora/repository/hsqldb/HSQLDBDatabaseUpdates.java b/src/main/java/org/qora/repository/hsqldb/HSQLDBDatabaseUpdates.java index 536de9d0..aca6f3e4 100644 --- a/src/main/java/org/qora/repository/hsqldb/HSQLDBDatabaseUpdates.java +++ b/src/main/java/org/qora/repository/hsqldb/HSQLDBDatabaseUpdates.java @@ -698,6 +698,27 @@ public class HSQLDBDatabaseUpdates { + "previous_flags INT, PRIMARY KEY (signature), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)"); break; + case 38: + // Enabling other accounts to forge + // Transaction to allow one account to enable other account to forge + stmt.execute("CREATE TABLE EnableForgingTransactions (signature Signature, creator QoraPublicKey NOT NULL, target QoraAddress NOT NULL, " + + "PRIMARY KEY (signature), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)"); + // Modification to accounts to record who enabled them to forge (useful for counting accounts and potentially orphaning) + stmt.execute("ALTER TABLE Accounts ADD COLUMN forging_enabler QoraAddress"); + break; + + case 39: + // Proxy forging + // Transaction emitted by forger announcing they are forging on behalf of recipient + stmt.execute("CREATE TABLE ProxyForgingTransactions (signature Signature, forger QoraPublicKey NOT NULL, recipient QoraAddress NOT NULL, proxy_public_key QoraPublicKey NOT NULL, share DECIMAL(5,2) NOT NULL, " + + "previous_share DECIMAL(5,2), PRIMARY KEY (signature), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)"); + // Table of current shares + stmt.execute("CREATE TABLE ProxyForgers (forger QoraPublicKey NOT NULL, recipient QoraAddress NOT NULL, proxy_public_key QoraPublicKey NOT NULL, share DECIMAL(5,2) NOT NULL, " + + "PRIMARY KEY (forger, recipient))"); + // Proxy-forged blocks will contain proxy public key, which will be used to look up block reward sharing, so create index for those lookups + stmt.execute("CREATE INDEX ProxyForgersProxyPublicKeyIndex ON ProxyForgers (proxy_public_key)"); + break; + default: // nothing to do return false; diff --git a/src/main/java/org/qora/repository/hsqldb/transaction/HSQLDBEnableForgingTransactionRepository.java b/src/main/java/org/qora/repository/hsqldb/transaction/HSQLDBEnableForgingTransactionRepository.java new file mode 100644 index 00000000..061397d8 --- /dev/null +++ b/src/main/java/org/qora/repository/hsqldb/transaction/HSQLDBEnableForgingTransactionRepository.java @@ -0,0 +1,49 @@ +package org.qora.repository.hsqldb.transaction; + +import java.math.BigDecimal; +import java.sql.ResultSet; +import java.sql.SQLException; + +import org.qora.data.transaction.EnableForgingTransactionData; +import org.qora.data.transaction.TransactionData; +import org.qora.repository.DataException; +import org.qora.repository.hsqldb.HSQLDBRepository; +import org.qora.repository.hsqldb.HSQLDBSaver; + +public class HSQLDBEnableForgingTransactionRepository extends HSQLDBTransactionRepository { + + public HSQLDBEnableForgingTransactionRepository(HSQLDBRepository repository) { + this.repository = repository; + } + + TransactionData fromBase(long timestamp, int txGroupId, byte[] reference, byte[] creatorPublicKey, BigDecimal fee, byte[] signature) throws DataException { + try (ResultSet resultSet = this.repository + .checkedExecute("SELECT target FROM EnableForgingTransactions WHERE signature = ?", signature)) { + if (resultSet == null) + return null; + + String target = resultSet.getString(1); + + return new EnableForgingTransactionData(timestamp, txGroupId, reference, creatorPublicKey, target, fee, signature); + } catch (SQLException e) { + throw new DataException("Unable to fetch account flags transaction from repository", e); + } + } + + @Override + public void save(TransactionData transactionData) throws DataException { + EnableForgingTransactionData enableForgingTransactionData = (EnableForgingTransactionData) transactionData; + + HSQLDBSaver saveHelper = new HSQLDBSaver("EnableForgingTransactions"); + + saveHelper.bind("signature", enableForgingTransactionData.getSignature()).bind("creator", enableForgingTransactionData.getCreatorPublicKey()) + .bind("target", enableForgingTransactionData.getTarget()); + + try { + saveHelper.execute(this.repository); + } catch (SQLException e) { + throw new DataException("Unable to save account flags transaction into repository", e); + } + } + +} diff --git a/src/main/java/org/qora/repository/hsqldb/transaction/HSQLDBProxyForgingTransactionRepository.java b/src/main/java/org/qora/repository/hsqldb/transaction/HSQLDBProxyForgingTransactionRepository.java new file mode 100644 index 00000000..6643c03f --- /dev/null +++ b/src/main/java/org/qora/repository/hsqldb/transaction/HSQLDBProxyForgingTransactionRepository.java @@ -0,0 +1,53 @@ +package org.qora.repository.hsqldb.transaction; + +import java.math.BigDecimal; +import java.sql.ResultSet; +import java.sql.SQLException; + +import org.qora.data.transaction.ProxyForgingTransactionData; +import org.qora.data.transaction.TransactionData; +import org.qora.repository.DataException; +import org.qora.repository.hsqldb.HSQLDBRepository; +import org.qora.repository.hsqldb.HSQLDBSaver; + +public class HSQLDBProxyForgingTransactionRepository extends HSQLDBTransactionRepository { + + public HSQLDBProxyForgingTransactionRepository(HSQLDBRepository repository) { + this.repository = repository; + } + + TransactionData fromBase(long timestamp, int txGroupId, byte[] reference, byte[] creatorPublicKey, BigDecimal fee, byte[] signature) throws DataException { + try (ResultSet resultSet = this.repository + .checkedExecute("SELECT recipient, proxy_public_key, share, previous_share FROM ProxyForgingTransactions WHERE signature = ?", signature)) { + if (resultSet == null) + return null; + + String recipient = resultSet.getString(1); + byte[] proxyPublicKey = resultSet.getBytes(2); + BigDecimal share = resultSet.getBigDecimal(3); + BigDecimal previousShare = resultSet.getBigDecimal(4); + + return new ProxyForgingTransactionData(timestamp, txGroupId, reference, creatorPublicKey, recipient, proxyPublicKey, share, previousShare, fee, signature); + } catch (SQLException e) { + throw new DataException("Unable to fetch proxy forging transaction from repository", e); + } + } + + @Override + public void save(TransactionData transactionData) throws DataException { + ProxyForgingTransactionData proxyForgingTransactionData = (ProxyForgingTransactionData) transactionData; + + HSQLDBSaver saveHelper = new HSQLDBSaver("ProxyForgingTransactions"); + + saveHelper.bind("signature", proxyForgingTransactionData.getSignature()).bind("forger", proxyForgingTransactionData.getForgerPublicKey()) + .bind("recipient", proxyForgingTransactionData.getRecipient()).bind("proxy_public_key", proxyForgingTransactionData.getProxyPublicKey()) + .bind("share", proxyForgingTransactionData.getShare()).bind("previous_share", proxyForgingTransactionData.getPreviousShare()); + + try { + saveHelper.execute(this.repository); + } catch (SQLException e) { + throw new DataException("Unable to save proxy forging transaction into repository", e); + } + } + +} diff --git a/src/main/java/org/qora/transaction/EnableForgingTransaction.java b/src/main/java/org/qora/transaction/EnableForgingTransaction.java new file mode 100644 index 00000000..4a9b0804 --- /dev/null +++ b/src/main/java/org/qora/transaction/EnableForgingTransaction.java @@ -0,0 +1,198 @@ +package org.qora.transaction; + +import java.math.BigDecimal; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import org.qora.account.Account; +import org.qora.account.PublicKeyAccount; +import org.qora.asset.Asset; +import org.qora.data.transaction.EnableForgingTransactionData; +import org.qora.data.transaction.TransactionData; +import org.qora.repository.DataException; +import org.qora.repository.Repository; + +public class EnableForgingTransaction extends Transaction { + + public static final int TIER1_MIN_FORGED_BLOCKS = 50; + public static final int TIER1_MAX_ENABLED_ACCOUNTS = 5; + + public static final int TIER2_MIN_FORGED_BLOCKS = 5; + public static final int TIER2_MAX_ENABLED_ACCOUNTS = 5; + + // Properties + private EnableForgingTransactionData enableForgingTransactionData; + + // Constructors + + public EnableForgingTransaction(Repository repository, TransactionData transactionData) { + super(repository, transactionData); + + this.enableForgingTransactionData = (EnableForgingTransactionData) this.transactionData; + } + + // More information + + @Override + public List getRecipientAccounts() throws DataException { + return Collections.emptyList(); + } + + @Override + public boolean isInvolved(Account account) throws DataException { + String address = account.getAddress(); + + if (address.equals(this.getCreator().getAddress())) + return true; + + if (address.equals(this.getTarget().getAddress())) + return true; + + return false; + } + + @Override + public BigDecimal getAmount(Account account) throws DataException { + String address = account.getAddress(); + BigDecimal amount = BigDecimal.ZERO.setScale(8); + + if (address.equals(this.getCreator().getAddress())) + amount = amount.subtract(this.transactionData.getFee()); + + return amount; + } + + // Navigation + + public Account getTarget() { + return new Account(this.repository, this.enableForgingTransactionData.getTarget()); + } + + // Processing + + @Override + public ValidationResult isValid() throws DataException { + PublicKeyAccount creator = getCreator(); + + // Creator needs to have at least one forging-enabled account flag set + Integer creatorFlags = creator.getFlags(); + if (creatorFlags == null) + return ValidationResult.INVALID_ADDRESS; + + if ((creatorFlags & Account.FORGING_MASK) == 0) + return ValidationResult.NO_FORGING_PERMISSION; + + // Tier3 forgers can't enable further accounts + if ((creatorFlags & Account.TIER3_FORGING_MASK) != 0) + return ValidationResult.FORGING_ENABLE_LIMIT; + + Account target = getTarget(); + + // Target needs to NOT have ANY forging-enabled account flags set + Integer targetFlags = target.getFlags(); + if (targetFlags != null && (targetFlags & Account.FORGING_MASK) != 0) + return ValidationResult.FORGING_ALREADY_ENABLED; + + // Has creator reached minimum requirements? + int numberForgedBlocks = this.repository.getBlockRepository().countForgedBlocks(creator.getPublicKey()); + int numberEnabledAccounts = this.repository.getAccountRepository().countForgingAccountsEnabledByAddress(creator.getAddress()); + + if ((creatorFlags & Account.TIER1_FORGING_MASK) != 0) { + // Tier1: minimum 2,500 forged blocks & max 50 accounts + if (numberForgedBlocks < TIER1_MIN_FORGED_BLOCKS) + return ValidationResult.FORGE_MORE_BLOCKS; + + if (numberEnabledAccounts >= TIER1_MAX_ENABLED_ACCOUNTS) + return ValidationResult.FORGING_ENABLE_LIMIT; + } else if ((creatorFlags & Account.TIER2_FORGING_MASK) != 0) { + // Tier2: minimum 50 forged blocks & max 50 accounts + if (numberForgedBlocks < TIER2_MIN_FORGED_BLOCKS) + return ValidationResult.FORGE_MORE_BLOCKS; + + if (numberEnabledAccounts >= TIER2_MAX_ENABLED_ACCOUNTS) + return ValidationResult.FORGING_ENABLE_LIMIT; + } + + // Check fee is zero or positive + if (enableForgingTransactionData.getFee().compareTo(BigDecimal.ZERO) < 0) + return ValidationResult.NEGATIVE_FEE; + + // Check reference + if (!Arrays.equals(creator.getLastReference(), enableForgingTransactionData.getReference())) + return ValidationResult.INVALID_REFERENCE; + + // Check creator has enough funds + if (creator.getConfirmedBalance(Asset.QORA).compareTo(enableForgingTransactionData.getFee()) < 0) + return ValidationResult.NO_BALANCE; + + return ValidationResult.OK; + } + + @Override + public void process() throws DataException { + Account creator = getCreator(); + + int creatorFlags = creator.getFlags(); + + int forgeBit = 0; + + if ((creatorFlags & Account.TIER1_FORGING_MASK) != 0) + forgeBit = Account.TIER2_FORGING_MASK; + else + forgeBit = Account.TIER3_FORGING_MASK; + + Account target = getTarget(); + Integer targetFlags = target.getFlags(); + if (targetFlags == null) + targetFlags = 0; + + targetFlags |= forgeBit; + + target.setFlags(targetFlags); + target.setForgingEnabler(creator.getAddress()); + + // Save this transaction + this.repository.getTransactionRepository().save(enableForgingTransactionData); + + // Update creator's balance + creator.setConfirmedBalance(Asset.QORA, creator.getConfirmedBalance(Asset.QORA).subtract(enableForgingTransactionData.getFee())); + + // Update creator's reference + creator.setLastReference(enableForgingTransactionData.getSignature()); + } + + @Override + public void orphan() throws DataException { + // Revert + Account creator = getCreator(); + + int creatorFlags = creator.getFlags(); + + int forgeBit = 0; + + if ((creatorFlags & Account.TIER1_FORGING_MASK) != 0) + forgeBit = Account.TIER2_FORGING_MASK; + else + forgeBit = Account.TIER3_FORGING_MASK; + + Account target = getTarget(); + + int targetFlags = target.getFlags(); + + targetFlags &= ~forgeBit; + + target.setFlags(targetFlags); + target.setForgingEnabler(null); + + // Delete this transaction itself + this.repository.getTransactionRepository().delete(enableForgingTransactionData); + + // Update creator's balance + creator.setConfirmedBalance(Asset.QORA, creator.getConfirmedBalance(Asset.QORA).add(enableForgingTransactionData.getFee())); + + // Update creator's reference + creator.setLastReference(enableForgingTransactionData.getReference()); + } + +} diff --git a/src/main/java/org/qora/transaction/ProxyForgingTransaction.java b/src/main/java/org/qora/transaction/ProxyForgingTransaction.java new file mode 100644 index 00000000..65ba2fd7 --- /dev/null +++ b/src/main/java/org/qora/transaction/ProxyForgingTransaction.java @@ -0,0 +1,174 @@ +package org.qora.transaction; + +import java.math.BigDecimal; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import org.qora.account.Account; +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; +import org.qora.repository.DataException; +import org.qora.repository.Repository; +import org.qora.transform.Transformer; + +public class ProxyForgingTransaction extends Transaction { + + // Properties + private ProxyForgingTransactionData proxyForgingTransactionData; + + // Constructors + + public ProxyForgingTransaction(Repository repository, TransactionData transactionData) { + super(repository, transactionData); + + this.proxyForgingTransactionData = (ProxyForgingTransactionData) this.transactionData; + } + + // More information + + @Override + public List getRecipientAccounts() throws DataException { + return Collections.emptyList(); + } + + @Override + public boolean isInvolved(Account account) throws DataException { + String address = account.getAddress(); + + if (address.equals(this.getForger().getAddress())) + return true; + + if (address.equals(this.getRecipient().getAddress())) + return true; + + return false; + } + + @Override + public BigDecimal getAmount(Account account) throws DataException { + String address = account.getAddress(); + BigDecimal amount = BigDecimal.ZERO.setScale(8); + + if (address.equals(this.getForger().getAddress())) + amount = amount.subtract(this.transactionData.getFee()); + + return amount; + } + + // Navigation + + public PublicKeyAccount getForger() { + return new PublicKeyAccount(this.repository, this.proxyForgingTransactionData.getForgerPublicKey()); + } + + public Account getRecipient() { + return new Account(this.repository, this.proxyForgingTransactionData.getRecipient()); + } + + // Processing + + private static final BigDecimal MAX_SHARE = BigDecimal.valueOf(100).setScale(2); + + @Override + 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) + return ValidationResult.INVALID_FORGE_SHARE; + + PublicKeyAccount creator = getCreator(); + + // Creator needs to have at least one forging-enabled account flag set + Integer creatorFlags = creator.getFlags(); + if (creatorFlags == null) + return ValidationResult.INVALID_ADDRESS; + + if ((creatorFlags & Account.FORGING_MASK) == 0) + return ValidationResult.NO_FORGING_PERMISSION; + + // Check proxy public key is correct length + if (this.proxyForgingTransactionData.getProxyPublicKey().length != Transformer.PUBLIC_KEY_LENGTH) + return ValidationResult.INVALID_PUBLIC_KEY; + + Account recipient = getRecipient(); + if (!Crypto.isValidAddress(recipient.getAddress())) + return ValidationResult.INVALID_ADDRESS; + + // 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) + return ValidationResult.NEGATIVE_FEE; + + // Check reference + if (!Arrays.equals(creator.getLastReference(), proxyForgingTransactionData.getReference())) + return ValidationResult.INVALID_REFERENCE; + + // Check creator has enough funds + if (creator.getConfirmedBalance(Asset.QORA).compareTo(proxyForgingTransactionData.getFee()) < 0) + return ValidationResult.NO_BALANCE; + + return ValidationResult.OK; + } + + @Override + public void process() throws DataException { + PublicKeyAccount forger = getForger(); + + // Grab any previous share info for orphaning purposes + ProxyForgerData proxyForgerData = this.repository.getAccountRepository().getProxyForgeData(forger.getPublicKey(), + proxyForgingTransactionData.getRecipient()); + + if (proxyForgerData != null) + proxyForgingTransactionData.setPreviousShare(proxyForgerData.getShare()); + + // 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); + + // Update forger's balance + forger.setConfirmedBalance(Asset.QORA, forger.getConfirmedBalance(Asset.QORA).subtract(proxyForgingTransactionData.getFee())); + + // Update forger's reference + forger.setLastReference(proxyForgingTransactionData.getSignature()); + } + + @Override + public void orphan() throws DataException { + // Revert + PublicKeyAccount forger = getForger(); + + if (proxyForgingTransactionData.getPreviousShare() != null) { + // Revert previous sharing arrangement + ProxyForgerData proxyForgerData = new ProxyForgerData(forger.getPublicKey(), proxyForgingTransactionData.getRecipient(), + proxyForgingTransactionData.getProxyPublicKey(), proxyForgingTransactionData.getPreviousShare()); + + this.repository.getAccountRepository().save(proxyForgerData); + } else { + // No previous arrangement so simply delete + this.repository.getAccountRepository().delete(forger.getPublicKey(), proxyForgingTransactionData.getRecipient()); + } + + // Delete this transaction itself + this.repository.getTransactionRepository().delete(proxyForgingTransactionData); + + // Update forger's balance + forger.setConfirmedBalance(Asset.QORA, forger.getConfirmedBalance(Asset.QORA).add(proxyForgingTransactionData.getFee())); + + // Update forger's reference + forger.setLastReference(proxyForgingTransactionData.getReference()); + } + +} diff --git a/src/main/java/org/qora/transaction/Transaction.java b/src/main/java/org/qora/transaction/Transaction.java index 9f078f21..4b3258db 100644 --- a/src/main/java/org/qora/transaction/Transaction.java +++ b/src/main/java/org/qora/transaction/Transaction.java @@ -73,7 +73,9 @@ public abstract class Transaction { GROUP_APPROVAL(33, false), SET_GROUP(34, false), UPDATE_ASSET(35, true), - ACCOUNT_FLAGS(36, false); + ACCOUNT_FLAGS(36, false), + ENABLE_FORGING(37, false), + PROXY_FORGING(38, false); public final int value; public final boolean needsApproval; @@ -192,6 +194,13 @@ public abstract class Transaction { INVALID_ASSET_OWNER(70), AT_IS_FINISHED(71), NO_FLAG_PERMISSION(72), + NO_FORGING_PERMISSION(73), + FORGING_ALREADY_ENABLED(74), + FORGE_MORE_BLOCKS(75), + FORGING_ENABLE_LIMIT(76), + INVALID_FORGE_SHARE(77), + PUBLIC_KEY_UNKNOWN(78), + INVALID_PUBLIC_KEY(79), NOT_YET_RELEASED(1000); public final int value; diff --git a/src/main/java/org/qora/transform/block/BlockTransformer.java b/src/main/java/org/qora/transform/block/BlockTransformer.java index 5cb29e0a..baf68d85 100644 --- a/src/main/java/org/qora/transform/block/BlockTransformer.java +++ b/src/main/java/org/qora/transform/block/BlockTransformer.java @@ -311,8 +311,8 @@ public class BlockTransformer extends Transformer { bytes.write(block.getBlockData().getGeneratorSignature()); for (Transaction transaction : transactions) { - // For legacy blocks, we don't include AT-Transactions - if (block.getBlockData().getVersion() < 4 && transaction.getTransactionData().getType() == TransactionType.AT) + // We don't include AT-Transactions as AT-state/output is dealt with elsewhere in the block code + if (transaction.getTransactionData().getType() == TransactionType.AT) continue; if (!transaction.isSignatureValid()) diff --git a/src/main/java/org/qora/transform/transaction/EnableForgingTransactionTransformer.java b/src/main/java/org/qora/transform/transaction/EnableForgingTransactionTransformer.java new file mode 100644 index 00000000..d155e9dd --- /dev/null +++ b/src/main/java/org/qora/transform/transaction/EnableForgingTransactionTransformer.java @@ -0,0 +1,83 @@ +package org.qora.transform.transaction; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.math.BigDecimal; +import java.nio.ByteBuffer; + +import org.qora.block.BlockChain; +import org.qora.data.transaction.EnableForgingTransactionData; +import org.qora.data.transaction.TransactionData; +import org.qora.transaction.Transaction.TransactionType; +import org.qora.transform.TransformationException; +import org.qora.utils.Serialization; + +public class EnableForgingTransactionTransformer extends TransactionTransformer { + + // Property lengths + private static final int TARGET_LENGTH = ADDRESS_LENGTH; + + private static final int EXTRAS_LENGTH = TARGET_LENGTH; + + protected static final TransactionLayout layout; + + static { + layout = new TransactionLayout(); + layout.add("txType: " + TransactionType.GROUP_INVITE.valueString, TransformationType.INT); + layout.add("timestamp", TransformationType.TIMESTAMP); + layout.add("transaction's groupID", TransformationType.INT); + layout.add("reference", TransformationType.SIGNATURE); + layout.add("account's public key", TransformationType.PUBLIC_KEY); + layout.add("target account's address", TransformationType.ADDRESS); + layout.add("fee", TransformationType.AMOUNT); + layout.add("signature", TransformationType.SIGNATURE); + } + + public static TransactionData fromByteBuffer(ByteBuffer byteBuffer) throws TransformationException { + long timestamp = byteBuffer.getLong(); + + int txGroupId = 0; + if (timestamp >= BlockChain.getInstance().getQoraV2Timestamp()) + txGroupId = byteBuffer.getInt(); + + byte[] reference = new byte[REFERENCE_LENGTH]; + byteBuffer.get(reference); + + byte[] creatorPublicKey = Serialization.deserializePublicKey(byteBuffer); + + String target = Serialization.deserializeAddress(byteBuffer); + + BigDecimal fee = Serialization.deserializeBigDecimal(byteBuffer); + + byte[] signature = new byte[SIGNATURE_LENGTH]; + byteBuffer.get(signature); + + return new EnableForgingTransactionData(timestamp, txGroupId, reference, creatorPublicKey, target, fee, signature); + } + + public static int getDataLength(TransactionData transactionData) throws TransformationException { + return getBaseLength(transactionData) + EXTRAS_LENGTH; + } + + public static byte[] toBytes(TransactionData transactionData) throws TransformationException { + try { + EnableForgingTransactionData enableForgingTransactionData = (EnableForgingTransactionData) transactionData; + + ByteArrayOutputStream bytes = new ByteArrayOutputStream(); + + transformCommonBytes(transactionData, bytes); + + Serialization.serializeAddress(bytes, enableForgingTransactionData.getTarget()); + + Serialization.serializeBigDecimal(bytes, enableForgingTransactionData.getFee()); + + if (enableForgingTransactionData.getSignature() != null) + bytes.write(enableForgingTransactionData.getSignature()); + + return bytes.toByteArray(); + } catch (IOException | ClassCastException e) { + throw new TransformationException(e); + } + } + +} diff --git a/src/main/java/org/qora/transform/transaction/ProxyForgingTransactionTransformer.java b/src/main/java/org/qora/transform/transaction/ProxyForgingTransactionTransformer.java new file mode 100644 index 00000000..9fa261eb --- /dev/null +++ b/src/main/java/org/qora/transform/transaction/ProxyForgingTransactionTransformer.java @@ -0,0 +1,93 @@ +package org.qora.transform.transaction; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.math.BigDecimal; +import java.nio.ByteBuffer; + +import org.qora.block.BlockChain; +import org.qora.data.transaction.ProxyForgingTransactionData; +import org.qora.data.transaction.TransactionData; +import org.qora.transaction.Transaction.TransactionType; +import org.qora.transform.TransformationException; +import org.qora.utils.Serialization; + +public class ProxyForgingTransactionTransformer extends TransactionTransformer { + + // Property lengths + private static final int TARGET_LENGTH = ADDRESS_LENGTH; + + private static final int EXTRAS_LENGTH = TARGET_LENGTH; + + protected static final TransactionLayout layout; + + static { + layout = new TransactionLayout(); + layout.add("txType: " + TransactionType.GROUP_INVITE.valueString, TransformationType.INT); + layout.add("timestamp", TransformationType.TIMESTAMP); + layout.add("transaction's groupID", TransformationType.INT); + layout.add("reference", TransformationType.SIGNATURE); + layout.add("forger's public key", TransformationType.PUBLIC_KEY); + layout.add("recipient account's address", TransformationType.ADDRESS); + layout.add("proxy's public key", TransformationType.PUBLIC_KEY); + layout.add("recipient's share of block rewards", TransformationType.AMOUNT); + layout.add("fee", TransformationType.AMOUNT); + layout.add("signature", TransformationType.SIGNATURE); + } + + public static TransactionData fromByteBuffer(ByteBuffer byteBuffer) throws TransformationException { + long timestamp = byteBuffer.getLong(); + + int txGroupId = 0; + if (timestamp >= BlockChain.getInstance().getQoraV2Timestamp()) + txGroupId = byteBuffer.getInt(); + + byte[] reference = new byte[REFERENCE_LENGTH]; + byteBuffer.get(reference); + + byte[] forgerPublicKey = Serialization.deserializePublicKey(byteBuffer); + + String recipient = Serialization.deserializeAddress(byteBuffer); + + byte[] proxyPublicKey = Serialization.deserializePublicKey(byteBuffer); + + BigDecimal share = Serialization.deserializeBigDecimal(byteBuffer); + + BigDecimal fee = Serialization.deserializeBigDecimal(byteBuffer); + + byte[] signature = new byte[SIGNATURE_LENGTH]; + byteBuffer.get(signature); + + return new ProxyForgingTransactionData(timestamp, txGroupId, reference, forgerPublicKey, recipient, proxyPublicKey, share, fee, signature); + } + + public static int getDataLength(TransactionData transactionData) throws TransformationException { + return getBaseLength(transactionData) + EXTRAS_LENGTH; + } + + public static byte[] toBytes(TransactionData transactionData) throws TransformationException { + try { + ProxyForgingTransactionData proxyForgingTransactionData = (ProxyForgingTransactionData) transactionData; + + ByteArrayOutputStream bytes = new ByteArrayOutputStream(); + + transformCommonBytes(transactionData, bytes); + + Serialization.serializeAddress(bytes, proxyForgingTransactionData.getRecipient()); + + bytes.write(proxyForgingTransactionData.getProxyPublicKey()); + + Serialization.serializeBigDecimal(bytes, proxyForgingTransactionData.getShare()); + + Serialization.serializeBigDecimal(bytes, proxyForgingTransactionData.getFee()); + + if (proxyForgingTransactionData.getSignature() != null) + bytes.write(proxyForgingTransactionData.getSignature()); + + return bytes.toByteArray(); + } catch (IOException | ClassCastException e) { + throw new TransformationException(e); + } + } + +} diff --git a/src/test/java/org/qora/test/TransactionTests.java b/src/test/java/org/qora/test/TransactionTests.java index 3844ed95..8724301a 100644 --- a/src/test/java/org/qora/test/TransactionTests.java +++ b/src/test/java/org/qora/test/TransactionTests.java @@ -103,7 +103,7 @@ public class TransactionTests extends Common { // Create test generator account generator = new PrivateKeyAccount(repository, generatorSeed); - accountRepository.setLastReference(new AccountData(generator.getAddress(), generatorSeed, generator.getPublicKey(), Group.NO_GROUP, 0)); + accountRepository.setLastReference(new AccountData(generator.getAddress(), generatorSeed, generator.getPublicKey(), Group.NO_GROUP, 0, null)); accountRepository.save(new AccountBalanceData(generator.getAddress(), Asset.QORA, initialGeneratorBalance)); // Create test sender account @@ -111,7 +111,7 @@ public class TransactionTests extends Common { // Mock account reference = senderSeed; - accountRepository.setLastReference(new AccountData(sender.getAddress(), reference, sender.getPublicKey(), Group.NO_GROUP, 0)); + accountRepository.setLastReference(new AccountData(sender.getAddress(), reference, sender.getPublicKey(), Group.NO_GROUP, 0, null)); // Mock balance accountRepository.save(new AccountBalanceData(sender.getAddress(), Asset.QORA, initialSenderBalance));