mirror of
https://github.com/Qortal/qortal.git
synced 2025-03-29 16:55:52 +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);
|
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;
|
public static final int ADDRESS_LENGTH = 25;
|
||||||
|
|
||||||
protected Repository repository;
|
protected Repository repository;
|
||||||
@ -238,4 +243,12 @@ public class Account {
|
|||||||
this.repository.getAccountRepository().setFlags(accountData);
|
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.Operation;
|
||||||
import io.swagger.v3.oas.annotations.media.Content;
|
import io.swagger.v3.oas.annotations.media.Content;
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
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.responses.ApiResponse;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
|
||||||
@ -10,6 +11,7 @@ import java.math.BigDecimal;
|
|||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.ws.rs.GET;
|
import javax.ws.rs.GET;
|
||||||
|
import javax.ws.rs.POST;
|
||||||
import javax.ws.rs.Path;
|
import javax.ws.rs.Path;
|
||||||
import javax.ws.rs.PathParam;
|
import javax.ws.rs.PathParam;
|
||||||
import javax.ws.rs.Produces;
|
import javax.ws.rs.Produces;
|
||||||
@ -24,12 +26,17 @@ import org.qora.api.ApiExceptionFactory;
|
|||||||
import org.qora.asset.Asset;
|
import org.qora.asset.Asset;
|
||||||
import org.qora.crypto.Crypto;
|
import org.qora.crypto.Crypto;
|
||||||
import org.qora.data.account.AccountData;
|
import org.qora.data.account.AccountData;
|
||||||
|
import org.qora.data.transaction.ProxyForgingTransactionData;
|
||||||
import org.qora.group.Group;
|
import org.qora.group.Group;
|
||||||
import org.qora.repository.DataException;
|
import org.qora.repository.DataException;
|
||||||
import org.qora.repository.Repository;
|
import org.qora.repository.Repository;
|
||||||
import org.qora.repository.RepositoryManager;
|
import org.qora.repository.RepositoryManager;
|
||||||
import org.qora.settings.Settings;
|
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.Transformer;
|
||||||
|
import org.qora.transform.transaction.ProxyForgingTransactionTransformer;
|
||||||
import org.qora.utils.Base58;
|
import org.qora.utils.Base58;
|
||||||
|
|
||||||
@Path("/addresses")
|
@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.ArraySchema;
|
||||||
import io.swagger.v3.oas.annotations.media.Content;
|
import io.swagger.v3.oas.annotations.media.Content;
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
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.responses.ApiResponse;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
|
||||||
@ -15,6 +16,7 @@ import java.util.List;
|
|||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
|
||||||
import javax.ws.rs.GET;
|
import javax.ws.rs.GET;
|
||||||
|
import javax.ws.rs.POST;
|
||||||
import javax.ws.rs.Path;
|
import javax.ws.rs.Path;
|
||||||
import javax.ws.rs.PathParam;
|
import javax.ws.rs.PathParam;
|
||||||
import javax.ws.rs.Produces;
|
import javax.ws.rs.Produces;
|
||||||
@ -28,10 +30,16 @@ import org.qora.api.ApiException;
|
|||||||
import org.qora.api.ApiExceptionFactory;
|
import org.qora.api.ApiExceptionFactory;
|
||||||
import org.qora.block.Block;
|
import org.qora.block.Block;
|
||||||
import org.qora.data.block.BlockData;
|
import org.qora.data.block.BlockData;
|
||||||
|
import org.qora.data.transaction.EnableForgingTransactionData;
|
||||||
import org.qora.data.transaction.TransactionData;
|
import org.qora.data.transaction.TransactionData;
|
||||||
import org.qora.repository.DataException;
|
import org.qora.repository.DataException;
|
||||||
import org.qora.repository.Repository;
|
import org.qora.repository.Repository;
|
||||||
import org.qora.repository.RepositoryManager;
|
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;
|
import org.qora.utils.Base58;
|
||||||
|
|
||||||
@Path("/blocks")
|
@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 byte[] publicKey;
|
||||||
protected int defaultGroupId;
|
protected int defaultGroupId;
|
||||||
protected int flags;
|
protected int flags;
|
||||||
|
protected String forgingEnabler;
|
||||||
|
|
||||||
// Constructors
|
// Constructors
|
||||||
|
|
||||||
@ -22,16 +23,17 @@ public class AccountData {
|
|||||||
protected 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.address = address;
|
||||||
this.reference = reference;
|
this.reference = reference;
|
||||||
this.publicKey = publicKey;
|
this.publicKey = publicKey;
|
||||||
this.defaultGroupId = defaultGroupId;
|
this.defaultGroupId = defaultGroupId;
|
||||||
this.flags = flags;
|
this.flags = flags;
|
||||||
|
this.forgingEnabler = forgingEnabler;
|
||||||
}
|
}
|
||||||
|
|
||||||
public AccountData(String address) {
|
public AccountData(String address) {
|
||||||
this(address, null, null, Group.NO_GROUP, 0);
|
this(address, null, null, Group.NO_GROUP, 0, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Getters/Setters
|
// Getters/Setters
|
||||||
@ -72,6 +74,14 @@ public class AccountData {
|
|||||||
this.flags = flags;
|
this.flags = flags;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getForgingEnabler() {
|
||||||
|
return this.forgingEnabler;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setForgingEnabler(String forgingEnabler) {
|
||||||
|
this.forgingEnabler = forgingEnabler;
|
||||||
|
}
|
||||||
|
|
||||||
// Comparison
|
// Comparison
|
||||||
|
|
||||||
@Override
|
@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,
|
JoinGroupTransactionData.class, LeaveGroupTransactionData.class,
|
||||||
GroupApprovalTransactionData.class, SetGroupTransactionData.class,
|
GroupApprovalTransactionData.class, SetGroupTransactionData.class,
|
||||||
UpdateAssetTransactionData.class,
|
UpdateAssetTransactionData.class,
|
||||||
AccountFlagsTransactionData.class
|
AccountFlagsTransactionData.class, EnableForgingTransactionData.class, ProxyForgingTransactionData.class
|
||||||
})
|
})
|
||||||
//All properties to be converted to JSON via JAXB
|
//All properties to be converted to JSON via JAXB
|
||||||
@XmlAccessorType(XmlAccessType.FIELD)
|
@XmlAccessorType(XmlAccessType.FIELD)
|
||||||
|
@ -4,6 +4,7 @@ import java.util.List;
|
|||||||
|
|
||||||
import org.qora.data.account.AccountBalanceData;
|
import org.qora.data.account.AccountBalanceData;
|
||||||
import org.qora.data.account.AccountData;
|
import org.qora.data.account.AccountData;
|
||||||
|
import org.qora.data.account.ProxyForgerData;
|
||||||
|
|
||||||
public interface AccountRepository {
|
public interface AccountRepository {
|
||||||
|
|
||||||
@ -21,6 +22,9 @@ public interface AccountRepository {
|
|||||||
/** Returns account's flags or null if account not found. */
|
/** Returns account's flags or null if account not found. */
|
||||||
public Integer getFlags(String address) throws DataException;
|
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.
|
* Ensures at least minimal account info in repository.
|
||||||
* <p>
|
* <p>
|
||||||
@ -49,6 +53,14 @@ public interface AccountRepository {
|
|||||||
*/
|
*/
|
||||||
public void setFlags(AccountData accountData) throws DataException;
|
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;
|
public void delete(String address) throws DataException;
|
||||||
|
|
||||||
// Account balances
|
// Account balances
|
||||||
@ -67,4 +79,14 @@ public interface AccountRepository {
|
|||||||
|
|
||||||
public void delete(String address, long assetId) throws DataException;
|
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);
|
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.
|
* Saves block into repository.
|
||||||
*
|
*
|
||||||
|
@ -11,6 +11,7 @@ import java.util.stream.Collectors;
|
|||||||
|
|
||||||
import org.qora.data.account.AccountBalanceData;
|
import org.qora.data.account.AccountBalanceData;
|
||||||
import org.qora.data.account.AccountData;
|
import org.qora.data.account.AccountData;
|
||||||
|
import org.qora.data.account.ProxyForgerData;
|
||||||
import org.qora.repository.AccountRepository;
|
import org.qora.repository.AccountRepository;
|
||||||
import org.qora.repository.DataException;
|
import org.qora.repository.DataException;
|
||||||
|
|
||||||
@ -26,7 +27,8 @@ public class HSQLDBAccountRepository implements AccountRepository {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public AccountData getAccount(String address) throws DataException {
|
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)
|
if (resultSet == null)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
@ -34,8 +36,9 @@ public class HSQLDBAccountRepository implements AccountRepository {
|
|||||||
byte[] publicKey = resultSet.getBytes(2);
|
byte[] publicKey = resultSet.getBytes(2);
|
||||||
int defaultGroupId = resultSet.getInt(3);
|
int defaultGroupId = resultSet.getInt(3);
|
||||||
int flags = resultSet.getInt(4);
|
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) {
|
} catch (SQLException e) {
|
||||||
throw new DataException("Unable to fetch account info from repository", 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
|
@Override
|
||||||
public void ensureAccount(AccountData accountData) throws DataException {
|
public void ensureAccount(AccountData accountData) throws DataException {
|
||||||
HSQLDBSaver saveHelper = new HSQLDBSaver("Accounts");
|
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
|
@Override
|
||||||
public void delete(String address) throws DataException {
|
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
|
// 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;
|
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
|
@Override
|
||||||
public void save(BlockData blockData) throws DataException {
|
public void save(BlockData blockData) throws DataException {
|
||||||
HSQLDBSaver saveHelper = new HSQLDBSaver("Blocks");
|
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)");
|
+ "previous_flags INT, PRIMARY KEY (signature), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
|
||||||
break;
|
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:
|
default:
|
||||||
// nothing to do
|
// nothing to do
|
||||||
return false;
|
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),
|
GROUP_APPROVAL(33, false),
|
||||||
SET_GROUP(34, false),
|
SET_GROUP(34, false),
|
||||||
UPDATE_ASSET(35, true),
|
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 int value;
|
||||||
public final boolean needsApproval;
|
public final boolean needsApproval;
|
||||||
@ -192,6 +194,13 @@ public abstract class Transaction {
|
|||||||
INVALID_ASSET_OWNER(70),
|
INVALID_ASSET_OWNER(70),
|
||||||
AT_IS_FINISHED(71),
|
AT_IS_FINISHED(71),
|
||||||
NO_FLAG_PERMISSION(72),
|
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);
|
NOT_YET_RELEASED(1000);
|
||||||
|
|
||||||
public final int value;
|
public final int value;
|
||||||
|
@ -311,8 +311,8 @@ public class BlockTransformer extends Transformer {
|
|||||||
bytes.write(block.getBlockData().getGeneratorSignature());
|
bytes.write(block.getBlockData().getGeneratorSignature());
|
||||||
|
|
||||||
for (Transaction transaction : transactions) {
|
for (Transaction transaction : transactions) {
|
||||||
// For legacy blocks, we don't include AT-Transactions
|
// We don't include AT-Transactions as AT-state/output is dealt with elsewhere in the block code
|
||||||
if (block.getBlockData().getVersion() < 4 && transaction.getTransactionData().getType() == TransactionType.AT)
|
if (transaction.getTransactionData().getType() == TransactionType.AT)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (!transaction.isSignatureValid())
|
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
|
// Create test generator account
|
||||||
generator = new PrivateKeyAccount(repository, generatorSeed);
|
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));
|
accountRepository.save(new AccountBalanceData(generator.getAddress(), Asset.QORA, initialGeneratorBalance));
|
||||||
|
|
||||||
// Create test sender account
|
// Create test sender account
|
||||||
@ -111,7 +111,7 @@ public class TransactionTests extends Common {
|
|||||||
|
|
||||||
// Mock account
|
// Mock account
|
||||||
reference = senderSeed;
|
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
|
// Mock balance
|
||||||
accountRepository.save(new AccountBalanceData(sender.getAddress(), Asset.QORA, initialSenderBalance));
|
accountRepository.save(new AccountBalanceData(sender.getAddress(), Asset.QORA, initialSenderBalance));
|
||||||
|
Loading…
x
Reference in New Issue
Block a user