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