mirror of
https://github.com/Qortal/qortal.git
synced 2025-02-12 02:05:50 +00:00
interim commit with proxy forging repository/transaction support
no block validity/generator support yet
This commit is contained in:
parent
2dc1720af8
commit
3c06d358b7
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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
|
||||
|
50
src/main/java/org/qora/data/account/ProxyForgerData.java
Normal file
50
src/main/java/org/qora/data/account/ProxyForgerData.java
Normal file
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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)
|
||||
|
@ -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.
|
||||
* <p>
|
||||
@ -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.
|
||||
* <p>
|
||||
* 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;
|
||||
|
||||
}
|
||||
|
@ -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.
|
||||
*
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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");
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
198
src/main/java/org/qora/transaction/EnableForgingTransaction.java
Normal file
198
src/main/java/org/qora/transaction/EnableForgingTransaction.java
Normal file
@ -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<Account> 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());
|
||||
}
|
||||
|
||||
}
|
174
src/main/java/org/qora/transaction/ProxyForgingTransaction.java
Normal file
174
src/main/java/org/qora/transaction/ProxyForgingTransaction.java
Normal file
@ -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<Account> 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());
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
|
@ -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())
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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));
|
||||
|
Loading…
x
Reference in New Issue
Block a user