3
0
mirror of https://github.com/Qortal/qortal.git synced 2025-02-12 02:05:50 +00:00

ProxyForging->RewardShare massive refactor & more...

Unified terminology from block "generator", "forger", etc. to "minter"

Unified terminology "proxy forger" to "reward share" as it was
incorrect, or at least highly ambiguous, which account had which role.

AccountRepository.findRewardShares() has different arg order!

Account.canMint() now returns true if account has 'founder' flag set.
Added Account.canRewardShare() which returns whether acocunt can create
a reward-share (e.g. level 5+ or founder).

Fixed HSQLDBAssetRepository.getAllAssets() which had incorrect
resultSet column indexes.

Removed old traces of EnableForging transaction.

ACCOUNT_LEVEL and ACCOUNT_FLAGS (genesis-block-only transaction types)
now set target account's last-reference. This is allow later REWARD_SHARE
transactions in genesis block and post-genesis transactions by that account.

REWARD_SHARE transactions are now FREE, but only if minter is also recipient.
If a self-reward-share already exists, then unless share-percent is zero (to
terminate reward-share), any subsequent self-reward-share is invalid.

Updated SysTray i18n properties file.

BlockChain config file requires 'minAccountLevelToRewardShare' and optional
'minAccountLevelToMint'.

Added potential, but currently unused, memory-hard PoW algorithm.

Fixed/removed/disabled some unit tests.
BlockMinter.generateTestingBlock asks Controller to pretend mintingAccount is 'online'.
More testing needed!
This commit is contained in:
catbref 2019-10-29 17:46:55 +00:00
parent 843aad4930
commit 491e79b8e6
80 changed files with 1853 additions and 2044 deletions

View File

@ -8,11 +8,11 @@ import org.qora.account.PrivateKeyAccount;
import org.qora.account.PublicKeyAccount;
import org.qora.utils.Base58;
public class ProxyKeys {
public class RewardShareKeys {
private static void usage() {
System.err.println("Usage: ProxyKeys <private-key> <public-key>");
System.err.println("Example: ProxyKeys pYQ6DpQBJ2n72TCLJLScEvwhf3boxWy2kQEPynakwpj 6rNn9b3pYRrG9UKqzMWYZ9qa8F3Zgv2mVWrULGHUusb");
System.err.println("Usage: RewardShareKeys <private-key> <public-key>");
System.err.println("Example: RewardShareKeys pYQ6DpQBJ2n72TCLJLScEvwhf3boxWy2kQEPynakwpj 6rNn9b3pYRrG9UKqzMWYZ9qa8F3Zgv2mVWrULGHUusb");
System.exit(1);
}
@ -26,15 +26,14 @@ public class ProxyKeys {
PrivateKeyAccount privateAccount = new PrivateKeyAccount(null, Base58.decode(args[0]));
PublicKeyAccount publicAccount = new PublicKeyAccount(null, Base58.decode(args[1]));
byte[] proxyPrivateKey = privateAccount.getProxyPrivateKey(publicAccount.getPublicKey());
PrivateKeyAccount proxyAccount = new PrivateKeyAccount(null, proxyPrivateKey);
byte[] rewardSharePrivateKey = privateAccount.getRewardSharePrivateKey(publicAccount.getPublicKey());
byte[] rewardSharePublicKey = PrivateKeyAccount.toPublicKey(rewardSharePrivateKey);
System.out.println(String.format("Private key account: %s", privateAccount.getAddress()));
System.out.println(String.format("Public key account: %s", publicAccount.getAddress()));
System.out.println(String.format("Proxy private key: %s", Base58.encode(proxyAccount.getPrivateKey())));
System.out.println(String.format("Proxy public key: %s", Base58.encode(proxyAccount.getPublicKey())));
System.out.println(String.format("Reward-share private key: %s", Base58.encode(rewardSharePrivateKey)));
System.out.println(String.format("Reward-share public key: %s", Base58.encode(rewardSharePublicKey)));
}
}

View File

@ -6,6 +6,7 @@ import java.util.List;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.qora.block.Block;
import org.qora.block.BlockChain;
import org.qora.data.account.AccountBalanceData;
import org.qora.data.account.AccountData;
import org.qora.data.block.BlockData;
@ -212,11 +213,28 @@ public class Account {
return Account.isFounder(flags);
}
// Forging
// Minting blocks
public boolean canForge() throws DataException {
public boolean canMint() throws DataException {
Integer level = this.getLevel();
return level != null && level > 0;
if (level != null && level >= BlockChain.getInstance().getMinAccountLevelToMint())
return true;
if (this.isFounder())
return true;
return false;
}
public boolean canRewardShare() throws DataException {
Integer level = this.getLevel();
if (level != null && level >= BlockChain.getInstance().getMinAccountLevelToRewardShare())
return true;
if (this.isFounder())
return true;
return false;
}
// Account level

View File

@ -44,6 +44,10 @@ public class PrivateKeyAccount extends PublicKeyAccount {
return this.privateKey;
}
public static byte[] toPublicKey(byte[] seed) {
return new Ed25519PrivateKeyParameters(seed, 0).generatePublicKey().getEncoded();
}
public byte[] sign(byte[] message) {
byte[] signature = new byte[SIGNATURE_LENGTH];
@ -65,7 +69,7 @@ public class PrivateKeyAccount extends PublicKeyAccount {
return sharedSecret;
}
public byte[] getProxyPrivateKey(byte[] publicKey) {
public byte[] getRewardSharePrivateKey(byte[] publicKey) {
byte[] sharedSecret = this.getSharedSecret(publicKey);
return Crypto.digest(sharedSecret);

View File

@ -9,8 +9,8 @@ public class ApiOnlineAccount {
protected long timestamp;
protected byte[] signature;
protected byte[] publicKey;
protected String generatorAddress;
protected byte[] rewardSharePublicKey;
protected String minterAddress;
protected String recipientAddress;
// Constructors
@ -19,11 +19,11 @@ public class ApiOnlineAccount {
protected ApiOnlineAccount() {
}
public ApiOnlineAccount(long timestamp, byte[] signature, byte[] publicKey, String generatorAddress, String recipientAddress) {
public ApiOnlineAccount(long timestamp, byte[] signature, byte[] rewardSharePublicKey, String minterAddress, String recipientAddress) {
this.timestamp = timestamp;
this.signature = signature;
this.publicKey = publicKey;
this.generatorAddress = generatorAddress;
this.rewardSharePublicKey = rewardSharePublicKey;
this.minterAddress = minterAddress;
this.recipientAddress = recipientAddress;
}
@ -36,11 +36,11 @@ public class ApiOnlineAccount {
}
public byte[] getPublicKey() {
return this.publicKey;
return this.rewardSharePublicKey;
}
public String getGeneratorAddress() {
return this.generatorAddress;
public String getMinterAddress() {
return this.minterAddress;
}
public String getRecipientAddress() {

View File

@ -1,37 +0,0 @@
package org.qora.api.model;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import org.qora.crypto.Crypto;
@XmlAccessorType(XmlAccessType.FIELD)
public class BlockForgerSummary {
// Properties
public String generatorAddress;
public int blockCount;
public String forgedBy;
public String forgedFor;
public byte[] proxyPublicKey;
// Constructors
protected BlockForgerSummary() {
}
public BlockForgerSummary(byte[] generator, int blockCount, byte[] forger, String recipient) {
this.blockCount = blockCount;
if (recipient == null) {
this.generatorAddress = Crypto.toAddress(generator);
} else {
this.proxyPublicKey = generator;
this.forgedBy = Crypto.toAddress(forger);
this.forgedFor = recipient;
}
}
}

View File

@ -0,0 +1,42 @@
package org.qora.api.model;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import org.qora.crypto.Crypto;
@XmlAccessorType(XmlAccessType.FIELD)
public class BlockMinterSummary {
// Properties
public int blockCount;
public byte[] mintingAccountPublicKey;
public String mintingAccount;
public byte[] rewardSharePublicKey;
public String recipientAccount;
// Constructors
protected BlockMinterSummary() {
}
/** Constructs BlockMinterSummary in non-reward-share context. */
public BlockMinterSummary(byte[] blockMinterPublicKey, int blockCount) {
this.blockCount = blockCount;
this.mintingAccountPublicKey = blockMinterPublicKey;
this.mintingAccount = Crypto.toAddress(this.mintingAccountPublicKey);
}
/** Constructs BlockMinterSummary in reward-share context. */
public BlockMinterSummary(byte[] rewardSharePublicKey, int blockCount, byte[] mintingAccountPublicKey, String recipientAccount) {
this(mintingAccountPublicKey, blockCount);
this.rewardSharePublicKey = rewardSharePublicKey;
this.recipientAccount = recipientAccount;
}
}

View File

@ -6,15 +6,15 @@ import javax.xml.bind.annotation.XmlAccessorType;
import io.swagger.v3.oas.annotations.media.Schema;
@XmlAccessorType(XmlAccessType.FIELD)
public class ProxyKeyRequest {
public class RewardShareKeyRequest {
@Schema(example = "private_key")
public byte[] generatorPrivateKey;
public byte[] mintingAccountPrivateKey;
@Schema(example = "public_key")
public byte[] recipientPublicKey;
public byte[] recipientAccountPublicKey;
public ProxyKeyRequest() {
public RewardShareKeyRequest() {
}
}

View File

@ -29,14 +29,14 @@ import org.qora.api.ApiErrors;
import org.qora.api.ApiException;
import org.qora.api.ApiExceptionFactory;
import org.qora.api.model.ApiOnlineAccount;
import org.qora.api.model.ProxyKeyRequest;
import org.qora.api.model.RewardShareKeyRequest;
import org.qora.asset.Asset;
import org.qora.controller.Controller;
import org.qora.crypto.Crypto;
import org.qora.data.account.AccountData;
import org.qora.data.account.ProxyForgerData;
import org.qora.data.account.RewardShareData;
import org.qora.data.network.OnlineAccountData;
import org.qora.data.transaction.ProxyForgingTransactionData;
import org.qora.data.transaction.RewardShareTransactionData;
import org.qora.repository.DataException;
import org.qora.repository.Repository;
import org.qora.repository.RepositoryManager;
@ -45,7 +45,7 @@ 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.transform.transaction.RewardShareTransactionTransformer;
import org.qora.utils.Base58;
@Path("/addresses")
@ -168,18 +168,18 @@ public class AddressesResource {
public List<ApiOnlineAccount> getOnlineAccounts() {
List<OnlineAccountData> onlineAccounts = Controller.getInstance().getOnlineAccounts();
// Map OnlineAccountData entries to OnlineAccount via proxy-relationship data
// Map OnlineAccountData entries to OnlineAccount via reward-share data
try (final Repository repository = RepositoryManager.getRepository()) {
List<ApiOnlineAccount> apiOnlineAccounts = new ArrayList<>();
for (OnlineAccountData onlineAccountData : onlineAccounts) {
ProxyForgerData proxyForgerData = repository.getAccountRepository().getProxyForgeData(onlineAccountData.getPublicKey());
if (proxyForgerData == null)
RewardShareData rewardShareData = repository.getAccountRepository().getRewardShare(onlineAccountData.getPublicKey());
if (rewardShareData == null)
// This shouldn't happen?
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.PUBLIC_KEY_NOT_FOUND);
apiOnlineAccounts.add(new ApiOnlineAccount(onlineAccountData.getTimestamp(), onlineAccountData.getSignature(), onlineAccountData.getPublicKey(),
proxyForgerData.getForger(), proxyForgerData.getRecipient()));
rewardShareData.getMintingAccount(), rewardShareData.getRecipient()));
}
return apiOnlineAccounts;
@ -303,19 +303,19 @@ public class AddressesResource {
}
@GET
@Path("/proxying")
@Path("/rewardshares")
@Operation(
summary = "List proxy forging relationships",
description = "Returns list of accounts, with reward share percentage and proxy public key.",
summary = "List reward-share relationships",
description = "Returns list of accounts, with reward-share percentage and reward-share public key.",
responses = {
@ApiResponse(
content = @Content(mediaType = MediaType.APPLICATION_JSON, array = @ArraySchema(schema = @Schema(implementation = ProxyForgerData.class)))
content = @Content(mediaType = MediaType.APPLICATION_JSON, array = @ArraySchema(schema = @Schema(implementation = RewardShareData.class)))
)
}
)
@ApiErrors({ApiError.INVALID_ADDRESS, ApiError.INVALID_CRITERIA, ApiError.REPOSITORY_ISSUE})
public List<ProxyForgerData> getProxying(@QueryParam("proxiedFor") List<String> recipients,
@QueryParam("proxiedBy") List<String> forgers,
public List<RewardShareData> getRewardShares(@QueryParam("minters") List<String> mintingAccounts,
@QueryParam("recipients") List<String> recipientAccounts,
@QueryParam("involving") List<String> addresses,
@Parameter(
ref = "limit"
@ -325,23 +325,23 @@ public class AddressesResource {
ref = "reverse"
) @QueryParam("reverse") Boolean reverse) {
try (final Repository repository = RepositoryManager.getRepository()) {
return repository.getAccountRepository().findProxyAccounts(recipients, forgers, addresses, limit, offset, reverse);
return repository.getAccountRepository().findRewardShares(mintingAccounts, recipientAccounts, addresses, limit, offset, reverse);
} catch (DataException e) {
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e);
}
}
@POST
@Path("/proxykey")
@Path("/rewardsharekey")
@Operation(
summary = "Calculate proxy private key",
description = "Calculates proxy private key using passed generator's private key and recipient's public key",
summary = "Calculate reward-share private key",
description = "Calculates reward-share private key using passed minting account's private key and recipient account's public key",
requestBody = @RequestBody(
required = true,
content = @Content(
mediaType = MediaType.APPLICATION_JSON,
schema = @Schema(
implementation = ProxyKeyRequest.class
implementation = RewardShareKeyRequest.class
)
)
),
@ -352,39 +352,39 @@ public class AddressesResource {
}
)
@ApiErrors({ApiError.INVALID_PRIVATE_KEY, ApiError.INVALID_PUBLIC_KEY, ApiError.REPOSITORY_ISSUE})
public String calculateProxyKey(ProxyKeyRequest proxyKeyRequest) {
byte[] generatorKey = proxyKeyRequest.generatorPrivateKey;
byte[] recipientKey = proxyKeyRequest.recipientPublicKey;
public String calculateRewardShareKey(RewardShareKeyRequest rewardShareKeyRequest) {
byte[] mintingPrivateKey = rewardShareKeyRequest.mintingAccountPrivateKey;
byte[] recipientPublicKey = rewardShareKeyRequest.recipientAccountPublicKey;
if (generatorKey == null || generatorKey.length != Transformer.PRIVATE_KEY_LENGTH)
if (mintingPrivateKey == null || mintingPrivateKey.length != Transformer.PRIVATE_KEY_LENGTH)
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_PRIVATE_KEY);
if (recipientKey == null || recipientKey.length != Transformer.PUBLIC_KEY_LENGTH)
if (recipientPublicKey == null || recipientPublicKey.length != Transformer.PUBLIC_KEY_LENGTH)
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_PUBLIC_KEY);
PrivateKeyAccount generator = new PrivateKeyAccount(null, generatorKey);
PrivateKeyAccount mintingAccount = new PrivateKeyAccount(null, mintingPrivateKey);
byte[] proxyPrivateKey = generator.getProxyPrivateKey(recipientKey);
byte[] rewardSharePrivateKey = mintingAccount.getRewardSharePrivateKey(recipientPublicKey);
return Base58.encode(proxyPrivateKey);
return Base58.encode(rewardSharePrivateKey);
}
@POST
@Path("/proxyforging")
@Path("/rewardshare")
@Operation(
summary = "Build raw, unsigned, PROXY_FORGING transaction",
summary = "Build raw, unsigned, REWARD_SHARE transaction",
requestBody = @RequestBody(
required = true,
content = @Content(
mediaType = MediaType.APPLICATION_JSON,
schema = @Schema(
implementation = ProxyForgingTransactionData.class
implementation = RewardShareTransactionData.class
)
)
),
responses = {
@ApiResponse(
description = "raw, unsigned, PROXY_FORGING transaction encoded in Base58",
description = "raw, unsigned, REWARD_SHARE transaction encoded in Base58",
content = @Content(
mediaType = MediaType.TEXT_PLAIN,
schema = @Schema(
@ -395,7 +395,7 @@ public class AddressesResource {
}
)
@ApiErrors({ApiError.NON_PRODUCTION, ApiError.TRANSACTION_INVALID, ApiError.TRANSFORMATION_ERROR, ApiError.REPOSITORY_ISSUE})
public String proxyForging(ProxyForgingTransactionData transactionData) {
public String rewardShare(RewardShareTransactionData transactionData) {
if (Settings.getInstance().isApiRestricted())
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.NON_PRODUCTION);
@ -406,7 +406,7 @@ public class AddressesResource {
if (result != ValidationResult.OK)
throw TransactionsResource.createTransactionInvalidException(request, result);
byte[] bytes = ProxyForgingTransactionTransformer.toBytes(transactionData);
byte[] bytes = RewardShareTransactionTransformer.toBytes(transactionData);
return Base58.encode(bytes);
} catch (TransformationException e) {
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.TRANSFORMATION_ERROR, e);

View File

@ -50,8 +50,8 @@ import org.qora.controller.Synchronizer.SynchronizationResult;
import org.qora.repository.DataException;
import org.qora.repository.Repository;
import org.qora.repository.RepositoryManager;
import org.qora.data.account.ForgingAccountData;
import org.qora.data.account.ProxyForgerData;
import org.qora.data.account.MintingAccountData;
import org.qora.data.account.RewardShareData;
import org.qora.network.Network;
import org.qora.network.Peer;
import org.qora.network.PeerAddress;
@ -189,45 +189,45 @@ public class AdminResource {
}
@GET
@Path("/forgingaccounts")
@Path("/mintingaccounts")
@Operation(
summary = "List public keys of accounts used to forge by BlockGenerator",
summary = "List public keys of accounts used to mint blocks by BlockMinter",
description = "Returns PUBLIC keys of accounts for safety.",
responses = {
@ApiResponse(
content = @Content(mediaType = MediaType.APPLICATION_JSON, array = @ArraySchema(schema = @Schema(implementation = ForgingAccountData.class)))
content = @Content(mediaType = MediaType.APPLICATION_JSON, array = @ArraySchema(schema = @Schema(implementation = MintingAccountData.class)))
)
}
)
@ApiErrors({ApiError.REPOSITORY_ISSUE})
public List<ForgingAccountData> getForgingAccounts() {
public List<MintingAccountData> getMintingAccounts() {
try (final Repository repository = RepositoryManager.getRepository()) {
List<ForgingAccountData> forgingAccounts = repository.getAccountRepository().getForgingAccounts();
List<MintingAccountData> mintingAccounts = repository.getAccountRepository().getMintingAccounts();
// Expand with proxy forging data where appropriate
forgingAccounts = forgingAccounts.stream().map(forgingAccountData -> {
byte[] publicKey = forgingAccountData.getPublicKey();
// Expand with reward-share data where appropriate
mintingAccounts = mintingAccounts.stream().map(mintingAccountData -> {
byte[] publicKey = mintingAccountData.getPublicKey();
ProxyForgerData proxyForgerData = null;
RewardShareData rewardShareData = null;
try {
proxyForgerData = repository.getAccountRepository().getProxyForgeData(publicKey);
rewardShareData = repository.getAccountRepository().getRewardShare(publicKey);
} catch (DataException e) {
// ignore
}
return new ForgingAccountData(forgingAccountData.getSeed(), proxyForgerData);
return new MintingAccountData(mintingAccountData.getPrivateKey(), rewardShareData);
}).collect(Collectors.toList());
return forgingAccounts;
return mintingAccounts;
} catch (DataException e) {
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e);
}
}
@POST
@Path("/forgingaccounts")
@Path("/mintingaccounts")
@Operation(
summary = "Add private key of account to use to forge by BlockGenerator",
summary = "Add private key of account/reward-share for use by BlockMinter to mint blocks",
requestBody = @RequestBody(
required = true,
content = @Content(
@ -244,20 +244,20 @@ public class AdminResource {
}
)
@ApiErrors({ApiError.INVALID_PRIVATE_KEY, ApiError.REPOSITORY_ISSUE})
public String addForgingAccount(String seed58) {
public String addMintingAccount(String seed58) {
try (final Repository repository = RepositoryManager.getRepository()) {
byte[] seed = Base58.decode(seed58.trim());
// Check seed is valid
PrivateKeyAccount forgingAccount = new PrivateKeyAccount(repository, seed);
PrivateKeyAccount mintingAccount = new PrivateKeyAccount(repository, seed);
// Account must derive to known proxy forging public key
if (!repository.getAccountRepository().isProxyPublicKey(forgingAccount.getPublicKey()))
// Qortal: account must derive to known reward-share public key
if (!repository.getAccountRepository().isRewardSharePublicKey(mintingAccount.getPublicKey()))
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_PRIVATE_KEY);
ForgingAccountData forgingAccountData = new ForgingAccountData(seed);
MintingAccountData mintingAccountData = new MintingAccountData(seed);
repository.getAccountRepository().save(forgingAccountData);
repository.getAccountRepository().save(mintingAccountData);
repository.saveChanges();
} catch (IllegalArgumentException e) {
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_PRIVATE_KEY, e);
@ -269,9 +269,9 @@ public class AdminResource {
}
@DELETE
@Path("/forgingaccounts")
@Path("/mintingaccounts")
@Operation(
summary = "Remove account from use to forge by BlockGenerator, using private key",
summary = "Remove account/reward-share from use by BlockMinter, using private key",
requestBody = @RequestBody(
required = true,
content = @Content(
@ -288,7 +288,7 @@ public class AdminResource {
}
)
@ApiErrors({ApiError.INVALID_PRIVATE_KEY, ApiError.REPOSITORY_ISSUE})
public String deleteForgingAccount(String seed58) {
public String deleteMintingAccount(String seed58) {
try (final Repository repository = RepositoryManager.getRepository()) {
byte[] seed = Base58.decode(seed58.trim());

View File

@ -24,7 +24,7 @@ import org.qora.api.ApiError;
import org.qora.api.ApiErrors;
import org.qora.api.ApiException;
import org.qora.api.ApiExceptionFactory;
import org.qora.api.model.BlockForgerSummary;
import org.qora.api.model.BlockMinterSummary;
import org.qora.crypto.Crypto;
import org.qora.data.account.AccountData;
import org.qora.data.block.BlockData;
@ -421,9 +421,9 @@ public class BlocksResource {
}
@GET
@Path("/forger/{address}")
@Path("/minter/{address}")
@Operation(
summary = "Fetch blocks forged by address",
summary = "Fetch blocks minted by address",
responses = {
@ApiResponse(
description = "blocks",
@ -438,7 +438,7 @@ public class BlocksResource {
}
)
@ApiErrors({ApiError.INVALID_ADDRESS, ApiError.PUBLIC_KEY_NOT_FOUND, ApiError.REPOSITORY_ISSUE})
public List<BlockData> getBlocksByForger(@PathParam("address") String address, @Parameter(
public List<BlockData> getBlocksByMinter(@PathParam("address") String address, @Parameter(
ref = "limit"
) @QueryParam("limit") Integer limit, @Parameter(
ref = "offset"
@ -454,7 +454,7 @@ public class BlocksResource {
if (accountData == null || accountData.getPublicKey() == null)
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.PUBLIC_KEY_NOT_FOUND);
return repository.getBlockRepository().getBlocksWithGenerator(accountData.getPublicKey(), limit, offset, reverse);
return repository.getBlockRepository().getBlocksByMinter(accountData.getPublicKey(), limit, offset, reverse);
} catch (ApiException e) {
throw e;
} catch (DataException e) {
@ -463,23 +463,23 @@ public class BlocksResource {
}
@GET
@Path("/forgers")
@Path("/minters")
@Operation(
summary = "Show summary of block forgers",
description = "Returns count of blocks forged, optionally limited to generators/recipients in passed address(es).",
summary = "Show summary of block minters",
description = "Returns count of blocks minted, optionally limited to minters/recipients in passed address(es).",
responses = {
@ApiResponse(
content = @Content(
array = @ArraySchema(
schema = @Schema(
implementation = BlockForgerSummary.class
implementation = BlockMinterSummary.class
)
)
)
)
}
)
public List<BlockForgerSummary> getBlockForgers(@QueryParam("address") List<String> addresses,
public List<BlockMinterSummary> getBlockMinters(@QueryParam("address") List<String> addresses,
@Parameter(
ref = "limit"
) @QueryParam("limit") Integer limit, @Parameter(
@ -492,7 +492,7 @@ public class BlocksResource {
if (!Crypto.isValidAddress(address))
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_ADDRESS);
return repository.getBlockRepository().getBlockForgers(addresses, limit, offset, reverse);
return repository.getBlockRepository().getBlockMinters(addresses, limit, offset, reverse);
} catch (DataException e) {
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e);
}

View File

@ -28,7 +28,7 @@ import org.qora.controller.Controller;
import org.qora.crypto.Crypto;
import org.qora.data.account.AccountBalanceData;
import org.qora.data.account.AccountData;
import org.qora.data.account.ProxyForgerData;
import org.qora.data.account.RewardShareData;
import org.qora.data.at.ATData;
import org.qora.data.at.ATStateData;
import org.qora.data.block.BlockData;
@ -73,7 +73,7 @@ public class Block {
TIMESTAMP_INCORRECT(24),
VERSION_INCORRECT(30),
FEATURE_NOT_YET_RELEASED(31),
GENERATOR_NOT_ACCEPTED(41),
MINTER_NOT_ACCEPTED(41),
GENESIS_TRANSACTIONS_INVALID(50),
TRANSACTION_TIMESTAMP_INVALID(51),
TRANSACTION_INVALID(52),
@ -103,7 +103,7 @@ public class Block {
// Properties
protected Repository repository;
protected BlockData blockData;
protected PublicKeyAccount generator;
protected PublicKeyAccount minter;
// Other properties
private static final Logger LOGGER = LogManager.getLogger(Block.class);
@ -125,13 +125,13 @@ public class Block {
/** Lazy-instantiated expanded info on block's online accounts. */
class ExpandedAccount {
final ProxyForgerData proxyForgerData;
final boolean isRecipientAlsoForger;
final RewardShareData rewardShareData;
final boolean isRecipientAlsoMinter;
final Account forgerAccount;
final AccountData forgerAccountData;
final boolean isForgerFounder;
final BigDecimal forgerQoraAmount;
final Account mintingAccount;
final AccountData mintingAccountData;
final boolean isMinterFounder;
final BigDecimal minterQoraAmount;
final int shareBin;
final Account recipientAccount;
@ -141,25 +141,25 @@ public class Block {
ExpandedAccount(Repository repository, int accountIndex) throws DataException {
final List<ShareByLevel> sharesByLevel = BlockChain.getInstance().getBlockSharesByLevel();
this.proxyForgerData = repository.getAccountRepository().getProxyAccountByIndex(accountIndex);
this.rewardShareData = repository.getAccountRepository().getRewardShareByIndex(accountIndex);
this.forgerAccount = new PublicKeyAccount(repository, this.proxyForgerData.getForgerPublicKey());
this.recipientAccount = new Account(repository, this.proxyForgerData.getRecipient());
this.mintingAccount = new PublicKeyAccount(repository, this.rewardShareData.getMinterPublicKey());
this.recipientAccount = new Account(repository, this.rewardShareData.getRecipient());
AccountBalanceData qoraBalanceData = repository.getAccountRepository().getBalance(this.forgerAccount.getAddress(), Asset.LEGACY_QORA);
AccountBalanceData qoraBalanceData = repository.getAccountRepository().getBalance(this.mintingAccount.getAddress(), Asset.LEGACY_QORA);
if (qoraBalanceData != null && qoraBalanceData.getBalance() != null && qoraBalanceData.getBalance().compareTo(BigDecimal.ZERO) > 0)
this.forgerQoraAmount = qoraBalanceData.getBalance();
this.minterQoraAmount = qoraBalanceData.getBalance();
else
this.forgerQoraAmount = null;
this.minterQoraAmount = null;
this.forgerAccountData = repository.getAccountRepository().getAccount(this.forgerAccount.getAddress());
this.isForgerFounder = Account.isFounder(forgerAccountData.getFlags());
this.mintingAccountData = repository.getAccountRepository().getAccount(this.mintingAccount.getAddress());
this.isMinterFounder = Account.isFounder(mintingAccountData.getFlags());
int currentShareBin = -1;
if (!this.isForgerFounder)
if (!this.isMinterFounder)
for (int s = 0; s < sharesByLevel.size(); ++s)
if (sharesByLevel.get(s).levels.contains(this.forgerAccountData.getLevel())) {
if (sharesByLevel.get(s).levels.contains(this.mintingAccountData.getLevel())) {
currentShareBin = s;
break;
}
@ -169,23 +169,23 @@ public class Block {
this.recipientAccountData = repository.getAccountRepository().getAccount(this.recipientAccount.getAddress());
this.isRecipientFounder = Account.isFounder(recipientAccountData.getFlags());
this.isRecipientAlsoForger = this.forgerAccountData.getAddress().equals(this.recipientAccountData.getAddress());
this.isRecipientAlsoMinter = this.mintingAccountData.getAddress().equals(this.recipientAccountData.getAddress());
}
void distribute(BigDecimal accountAmount) throws DataException {
final BigDecimal oneHundred = BigDecimal.valueOf(100L);
if (this.forgerAccount.getAddress().equals(this.recipientAccount.getAddress())) {
// forger & recipient the same - simpler case
LOGGER.trace(() -> String.format("Forger/recipient account %s share: %s", this.forgerAccount.getAddress(), accountAmount.toPlainString()));
this.forgerAccount.setConfirmedBalance(Asset.QORT, this.forgerAccount.getConfirmedBalance(Asset.QORT).add(accountAmount));
if (this.mintingAccount.getAddress().equals(this.recipientAccount.getAddress())) {
// minter & recipient the same - simpler case
LOGGER.trace(() -> String.format("Minter/recipient account %s share: %s", this.mintingAccount.getAddress(), accountAmount.toPlainString()));
this.mintingAccount.setConfirmedBalance(Asset.QORT, this.mintingAccount.getConfirmedBalance(Asset.QORT).add(accountAmount));
} else {
// forger & recipient different - extra work needed
BigDecimal recipientAmount = accountAmount.multiply(this.proxyForgerData.getShare()).divide(oneHundred, RoundingMode.DOWN);
BigDecimal forgerAmount = accountAmount.subtract(recipientAmount);
// minter & recipient different - extra work needed
BigDecimal recipientAmount = accountAmount.multiply(this.rewardShareData.getSharePercent()).divide(oneHundred, RoundingMode.DOWN);
BigDecimal minterAmount = accountAmount.subtract(recipientAmount);
LOGGER.trace(() -> String.format("Forger account %s share: %s", this.forgerAccount.getAddress(), forgerAmount.toPlainString()));
this.forgerAccount.setConfirmedBalance(Asset.QORT, this.forgerAccount.getConfirmedBalance(Asset.QORT).add(forgerAmount));
LOGGER.trace(() -> String.format("Minter account %s share: %s", this.mintingAccount.getAddress(), minterAmount.toPlainString()));
this.mintingAccount.setConfirmedBalance(Asset.QORT, this.mintingAccount.getConfirmedBalance(Asset.QORT).add(minterAmount));
LOGGER.trace(() -> String.format("Recipient account %s share: %s", this.recipientAccount.getAddress(), recipientAmount.toPlainString()));
this.recipientAccount.setConfirmedBalance(Asset.QORT, this.recipientAccount.getConfirmedBalance(Asset.QORT).add(recipientAmount));
@ -220,7 +220,7 @@ public class Block {
public Block(Repository repository, BlockData blockData) throws DataException {
this.repository = repository;
this.blockData = blockData;
this.generator = new PublicKeyAccount(repository, blockData.getGeneratorPublicKey());
this.minter = new PublicKeyAccount(repository, blockData.getMinterPublicKey());
}
/**
@ -257,18 +257,18 @@ public class Block {
/**
* Constructs Block-handling object with basic, initial values.
* <p>
* This constructor typically used when generating a new block.
* This constructor typically used when minting a new block.
* <p>
* Note that CIYAM ATs will be executed and AT-Transactions prepended to this block, along with AT state data and fees.
*
* @param repository
* @param parentBlockData
* @param generator
* @param minter
* @throws DataException
*/
public Block(Repository repository, BlockData parentBlockData, PrivateKeyAccount generator) throws DataException {
public Block(Repository repository, BlockData parentBlockData, PrivateKeyAccount minter) throws DataException {
this.repository = repository;
this.generator = generator;
this.minter = minter;
Block parentBlock = new Block(repository, parentBlockData);
@ -287,14 +287,14 @@ public class Block {
onlineAccountsTimestamp = onlineAccountData.getTimestamp();
}
// Map using account index (in list of proxy forger accounts)
// Map using index into sorted list of reward-shares as key
Map<Integer, OnlineAccountData> indexedOnlineAccounts = new HashMap<>();
for (OnlineAccountData onlineAccountData : onlineAccounts) {
// Disregard online accounts with different timestamps
if (onlineAccountData.getTimestamp() != onlineAccountsTimestamp)
continue;
int accountIndex = repository.getAccountRepository().getProxyAccountIndex(onlineAccountData.getPublicKey());
int accountIndex = repository.getAccountRepository().getRewardShareIndex(onlineAccountData.getPublicKey());
indexedOnlineAccounts.put(accountIndex, onlineAccountData);
}
List<Integer> accountIndexes = new ArrayList<>(indexedOnlineAccounts.keySet());
@ -314,14 +314,14 @@ public class Block {
System.arraycopy(onlineAccountData.getSignature(), 0, onlineAccountsSignatures, i * Transformer.SIGNATURE_LENGTH, Transformer.SIGNATURE_LENGTH);
}
byte[] generatorSignature;
byte[] minterSignature;
try {
generatorSignature = generator.sign(BlockTransformer.getBytesForGeneratorSignature(parentBlockData.getGeneratorSignature(), generator, encodedOnlineAccounts));
minterSignature = minter.sign(BlockTransformer.getBytesForMinterSignature(parentBlockData.getMinterSignature(), minter, encodedOnlineAccounts));
} catch (TransformationException e) {
throw new DataException("Unable to calculate next block generator signature", e);
throw new DataException("Unable to calculate next block minter signature", e);
}
long timestamp = calcTimestamp(parentBlockData, generator.getPublicKey());
long timestamp = calcTimestamp(parentBlockData, minter.getPublicKey());
int transactionCount = 0;
byte[] transactionsSignature = null;
@ -335,7 +335,7 @@ public class Block {
// This instance used for AT processing
this.blockData = new BlockData(version, reference, transactionCount, totalFees, transactionsSignature, height, timestamp,
generator.getPublicKey(), generatorSignature, atCount, atFees,
minter.getPublicKey(), minterSignature, atCount, atFees,
encodedOnlineAccounts, onlineAccountsCount, onlineAccountsTimestamp, onlineAccountsSignatures);
// Requires this.blockData and this.transactions, sets this.ourAtStates and this.ourAtFees
@ -348,22 +348,22 @@ public class Block {
// Rebuild blockData using post-AT-execute data
this.blockData = new BlockData(version, reference, transactionCount, totalFees, transactionsSignature, height, timestamp,
generator.getPublicKey(), generatorSignature, atCount, atFees,
minter.getPublicKey(), minterSignature, atCount, atFees,
encodedOnlineAccounts, onlineAccountsCount, onlineAccountsTimestamp, onlineAccountsSignatures);
}
/**
* Construct another block using this block as template, but with different generator account.
* Construct another block using this block as template, but with different minting account.
* <p>
* NOTE: uses the same transactions list, AT states, etc.
*
* @param generator
* @param minter
* @return
* @throws DataException
*/
public Block regenerate(PrivateKeyAccount generator) throws DataException {
public Block newMinter(PrivateKeyAccount minter) throws DataException {
Block newBlock = new Block(this.repository, this.blockData);
newBlock.generator = generator;
newBlock.minter = minter;
BlockData parentBlockData = this.getParent();
@ -376,14 +376,14 @@ public class Block {
int version = this.blockData.getVersion();
byte[] reference = this.blockData.getReference();
byte[] generatorSignature;
byte[] minterSignature;
try {
generatorSignature = generator.sign(BlockTransformer.getBytesForGeneratorSignature(parentBlockData.getGeneratorSignature(), generator, this.blockData.getEncodedOnlineAccounts()));
minterSignature = minter.sign(BlockTransformer.getBytesForMinterSignature(parentBlockData.getMinterSignature(), minter, this.blockData.getEncodedOnlineAccounts()));
} catch (TransformationException e) {
throw new DataException("Unable to calculate next block generator signature", e);
throw new DataException("Unable to calculate next block's minter signature", e);
}
long timestamp = calcTimestamp(parentBlockData, generator.getPublicKey());
long timestamp = calcTimestamp(parentBlockData, minter.getPublicKey());
newBlock.transactions = this.transactions;
int transactionCount = this.blockData.getTransactionCount();
@ -400,7 +400,7 @@ public class Block {
byte[] onlineAccountsSignatures = this.blockData.getOnlineAccountsSignatures();
newBlock.blockData = new BlockData(version, reference, transactionCount, totalFees, transactionsSignature, height, timestamp,
generator.getPublicKey(), generatorSignature, atCount, atFees, encodedOnlineAccounts, onlineAccountsCount, onlineAccountsTimestamp, onlineAccountsSignatures);
minter.getPublicKey(), minterSignature, atCount, atFees, encodedOnlineAccounts, onlineAccountsCount, onlineAccountsTimestamp, onlineAccountsSignatures);
// Resign to update transactions signature
newBlock.sign();
@ -414,22 +414,22 @@ public class Block {
return this.blockData;
}
public PublicKeyAccount getGenerator() {
return this.generator;
public PublicKeyAccount getMinter() {
return this.minter;
}
// More information
/**
* Return composite block signature (generatorSignature + transactionsSignature).
* Return composite block signature (minterSignature + transactionsSignature).
*
* @return byte[], or null if either component signature is null.
*/
public byte[] getSignature() {
if (this.blockData.getGeneratorSignature() == null || this.blockData.getTransactionsSignature() == null)
if (this.blockData.getMinterSignature() == null || this.blockData.getTransactionsSignature() == null)
return null;
return Bytes.concat(this.blockData.getGeneratorSignature(), this.blockData.getTransactionsSignature());
return Bytes.concat(this.blockData.getMinterSignature(), this.blockData.getTransactionsSignature());
}
/**
@ -570,25 +570,25 @@ public class Block {
/**
* Add a transaction to the block.
* <p>
* Used when constructing a new block during forging.
* Used when constructing a new block during minting.
* <p>
* Requires block's {@code generator} being a {@code PrivateKeyAccount} so block's transactions signature can be recalculated.
* Requires block's {@code minter} being a {@code PrivateKeyAccount} so block's transactions signature can be recalculated.
*
* @param transactionData
* @return true if transaction successfully added to block, false otherwise
* @throws IllegalStateException
* if block's {@code generator} is not a {@code PrivateKeyAccount}.
* if block's {@code minter} is not a {@code PrivateKeyAccount}.
*/
public boolean addTransaction(TransactionData transactionData) {
// Can't add to transactions if we haven't loaded existing ones yet
if (this.transactions == null)
throw new IllegalStateException("Attempted to add transaction to partially loaded database Block");
if (!(this.generator instanceof PrivateKeyAccount))
throw new IllegalStateException("Block's generator has no private key");
if (!(this.minter instanceof PrivateKeyAccount))
throw new IllegalStateException("Block's minter is not PrivateKeyAccount - can't sign!");
if (this.blockData.getGeneratorSignature() == null)
throw new IllegalStateException("Cannot calculate transactions signature as block has no generator signature");
if (this.blockData.getMinterSignature() == null)
throw new IllegalStateException("Cannot calculate transactions signature as block has no minter signature");
// Already added? (Check using signature)
if (this.transactions.stream().anyMatch(transaction -> Arrays.equals(transaction.getTransactionData().getSignature(), transactionData.getSignature())))
@ -623,24 +623,24 @@ public class Block {
/**
* Remove a transaction from the block.
* <p>
* Used when constructing a new block during forging.
* Used when constructing a new block during minting.
* <p>
* Requires block's {@code generator} being a {@code PrivateKeyAccount} so block's transactions signature can be recalculated.
* Requires block's {@code minter} being a {@code PrivateKeyAccount} so block's transactions signature can be recalculated.
*
* @param transactionData
* @throws IllegalStateException
* if block's {@code generator} is not a {@code PrivateKeyAccount}.
* if block's {@code minter} is not a {@code PrivateKeyAccount}.
*/
public void deleteTransaction(TransactionData transactionData) {
// Can't add to transactions if we haven't loaded existing ones yet
if (this.transactions == null)
throw new IllegalStateException("Attempted to add transaction to partially loaded database Block");
if (!(this.generator instanceof PrivateKeyAccount))
throw new IllegalStateException("Block's generator has no private key");
if (!(this.minter instanceof PrivateKeyAccount))
throw new IllegalStateException("Block's minter is not a PrivateKeyAccount - can't sign!");
if (this.blockData.getGeneratorSignature() == null)
throw new IllegalStateException("Cannot calculate transactions signature as block has no generator signature");
if (this.blockData.getMinterSignature() == null)
throw new IllegalStateException("Cannot calculate transactions signature as block has no minter signature");
// Attempt to remove from block (Check using signature)
boolean wasElementRemoved = this.transactions.removeIf(transaction -> Arrays.equals(transaction.getTransactionData().getSignature(), transactionData.getSignature()));
@ -662,54 +662,54 @@ public class Block {
}
/**
* Recalculate block's generator signature.
* Recalculate block's minter signature.
* <p>
* Requires block's {@code generator} being a {@code PrivateKeyAccount}.
* Requires block's {@code minter} being a {@code PrivateKeyAccount}.
* <p>
* Generator signature is made by the generator signing the following data:
* Minter signature is made by the minter signing the following data:
* <p>
* previous block's generator signature + this block's generating balance + generator's public key
* previous block's minter signature + minter's public key + (encoded) online-accounts data
* <p>
* (Previous block's generator signature is extracted from this block's reference).
* (Previous block's minter signature is extracted from this block's reference).
*
* @throws IllegalStateException
* if block's {@code generator} is not a {@code PrivateKeyAccount}.
* if block's {@code minter} is not a {@code PrivateKeyAccount}.
* @throws RuntimeException
* if somehow the generator signature cannot be calculated
* if somehow the minter signature cannot be calculated
*/
protected void calcGeneratorSignature() {
if (!(this.generator instanceof PrivateKeyAccount))
throw new IllegalStateException("Block's generator has no private key");
protected void calcMinterSignature() {
if (!(this.minter instanceof PrivateKeyAccount))
throw new IllegalStateException("Block's minter is not a PrivateKeyAccount - can't sign!");
try {
this.blockData.setGeneratorSignature(((PrivateKeyAccount) this.generator).sign(BlockTransformer.getBytesForGeneratorSignature(this.blockData)));
this.blockData.setMinterSignature(((PrivateKeyAccount) this.minter).sign(BlockTransformer.getBytesForMinterSignature(this.blockData)));
} catch (TransformationException e) {
throw new RuntimeException("Unable to calculate block's generator signature", e);
throw new RuntimeException("Unable to calculate block's minter signature", e);
}
}
/**
* Recalculate block's transactions signature.
* <p>
* Requires block's {@code generator} being a {@code PrivateKeyAccount}.
* Requires block's {@code minter} being a {@code PrivateKeyAccount}.
*
* @throws IllegalStateException
* if block's {@code generator} is not a {@code PrivateKeyAccount}.
* if block's {@code minter} is not a {@code PrivateKeyAccount}.
* @throws RuntimeException
* if somehow the transactions signature cannot be calculated
*/
protected void calcTransactionsSignature() {
if (!(this.generator instanceof PrivateKeyAccount))
throw new IllegalStateException("Block's generator has no private key");
if (!(this.minter instanceof PrivateKeyAccount))
throw new IllegalStateException("Block's minter is not a PrivateKeyAccount - can't sign!");
try {
this.blockData.setTransactionsSignature(((PrivateKeyAccount) this.generator).sign(BlockTransformer.getBytesForTransactionsSignature(this)));
this.blockData.setTransactionsSignature(((PrivateKeyAccount) this.minter).sign(BlockTransformer.getBytesForTransactionsSignature(this)));
} catch (TransformationException e) {
throw new RuntimeException("Unable to calculate block's transactions signature", e);
}
}
public static byte[] calcIdealGeneratorPublicKey(int parentBlockHeight, byte[] parentBlockSignature) {
public static byte[] calcIdealMinterPublicKey(int parentBlockHeight, byte[] parentBlockSignature) {
return Crypto.digest(Bytes.concat(Longs.toByteArray(parentBlockHeight), parentBlockSignature));
}
@ -718,14 +718,14 @@ public class Block {
}
public static BigInteger calcKeyDistance(int parentHeight, byte[] parentBlockSignature, byte[] publicKey) {
byte[] idealKey = calcIdealGeneratorPublicKey(parentHeight, parentBlockSignature);
byte[] idealKey = calcIdealMinterPublicKey(parentHeight, parentBlockSignature);
byte[] perturbedKey = calcHeightPerturbedPublicKey(parentHeight + 1, publicKey);
return MAX_DISTANCE.subtract(new BigInteger(idealKey).subtract(new BigInteger(perturbedKey)).abs());
}
public static BigInteger calcBlockWeight(int parentHeight, byte[] parentBlockSignature, BlockSummaryData blockSummaryData) {
BigInteger keyDistance = calcKeyDistance(parentHeight, parentBlockSignature, blockSummaryData.getGeneratorPublicKey());
BigInteger keyDistance = calcKeyDistance(parentHeight, parentBlockSignature, blockSummaryData.getMinterPublicKey());
return BigInteger.valueOf(blockSummaryData.getOnlineAccountsCount()).shiftLeft(ACCOUNTS_COUNT_SHIFT).add(keyDistance);
}
@ -744,19 +744,20 @@ public class Block {
}
/**
* Returns timestamp based on previous block and this block's generator.
* Returns timestamp based on previous block and this block's minter.
* <p>
* Uses same proportion of this block's generator from 'ideal' generator
* with min to max target block periods, added to previous block's timestamp.
* Uses distance of this block's minter from 'ideal' minter,
* along with min to max target block periods,
* added to previous block's timestamp.
* <p>
* Example:<br>
* This block's generator is 20% of max distance from 'ideal' generator.<br>
* This block's minter is 20% of max distance from 'ideal' minter.<br>
* Min/Max block periods are 30s and 90s respectively.<br>
* 20% of (90s - 30s) is 12s<br>
* So this block's timestamp is previous block's timestamp + 30s + 12s.
*/
public static long calcTimestamp(BlockData parentBlockData, byte[] generatorPublicKey) {
BigInteger distance = calcKeyDistance(parentBlockData.getHeight(), parentBlockData.getSignature(), generatorPublicKey);
public static long calcTimestamp(BlockData parentBlockData, byte[] minterPublicKey) {
BigInteger distance = calcKeyDistance(parentBlockData.getHeight(), parentBlockData.getSignature(), minterPublicKey);
final int thisHeight = parentBlockData.getHeight() + 1;
BlockTimingByHeight blockTiming = BlockChain.getInstance().getBlockTimingByHeight(thisHeight);
@ -777,15 +778,15 @@ public class Block {
}
/**
* Recalculate block's generator and transactions signatures, thus giving block full signature.
* Recalculate block's minter and transactions signatures, thus giving block full signature.
* <p>
* Note: Block instance must have been constructed with a <tt>PrivateKeyAccount generator</tt> or this call will throw an <tt>IllegalStateException</tt>.
* Note: Block instance must have been constructed with a <tt>PrivateKeyAccount</tt> minter or this call will throw an <tt>IllegalStateException</tt>.
*
* @throws IllegalStateException
* if block's {@code generator} is not a {@code PrivateKeyAccount}.
* if block's {@code minter} is not a {@code PrivateKeyAccount}.
*/
public void sign() {
this.calcGeneratorSignature();
this.calcMinterSignature();
this.calcTransactionsSignature();
this.blockData.setSignature(this.getSignature());
@ -794,16 +795,16 @@ public class Block {
/**
* Returns whether this block's signatures are valid.
*
* @return true if both generator and transaction signatures are valid, false otherwise
* @return true if both minter and transaction signatures are valid, false otherwise
*/
public boolean isSignatureValid() {
try {
// Check generator's signature first
if (!this.generator.verify(this.blockData.getGeneratorSignature(), BlockTransformer.getBytesForGeneratorSignature(this.blockData)))
// Check minter's signature first
if (!this.minter.verify(this.blockData.getMinterSignature(), BlockTransformer.getBytesForMinterSignature(this.blockData)))
return false;
// Check transactions signature
if (!this.generator.verify(this.blockData.getTransactionsSignature(), BlockTransformer.getBytesForTransactionsSignature(this)))
if (!this.minter.verify(this.blockData.getTransactionsSignature(), BlockTransformer.getBytesForTransactionsSignature(this)))
return false;
} catch (TransformationException e) {
return false;
@ -815,7 +816,7 @@ public class Block {
/**
* Returns whether Block's timestamp is valid.
* <p>
* Used by BlockGenerator to check whether it's time to forge new block,
* Used by BlockMinter to check whether it's time to mint a new block,
* and also used by Block.isValid for checks (if not a testchain).
*
* @return ValidationResult.OK if timestamp valid, or some other ValidationResult otherwise.
@ -839,7 +840,7 @@ public class Block {
if (this.blockData.getTimestamp() < Block.calcMinimumTimestamp(parentBlockData))
return ValidationResult.TIMESTAMP_TOO_SOON;
long expectedTimestamp = calcTimestamp(parentBlockData, this.blockData.getGeneratorPublicKey());
long expectedTimestamp = calcTimestamp(parentBlockData, this.blockData.getMinterPublicKey());
if (this.blockData.getTimestamp() != expectedTimestamp)
return ValidationResult.TIMESTAMP_INCORRECT;
@ -857,18 +858,18 @@ public class Block {
if (accountIndexes.size() != this.blockData.getOnlineAccountsCount())
return ValidationResult.ONLINE_ACCOUNTS_INVALID;
List<ProxyForgerData> expandedAccounts = new ArrayList<>();
List<RewardShareData> expandedAccounts = new ArrayList<>();
IntIterator iterator = accountIndexes.iterator();
while (iterator.hasNext()) {
int accountIndex = iterator.next();
ProxyForgerData proxyAccountData = repository.getAccountRepository().getProxyAccountByIndex(accountIndex);
RewardShareData rewardShareData = repository.getAccountRepository().getRewardShareByIndex(accountIndex);
// Check that claimed online account actually exists
if (proxyAccountData == null)
if (rewardShareData == null)
return ValidationResult.ONLINE_ACCOUNT_UNKNOWN;
expandedAccounts.add(proxyAccountData);
expandedAccounts.add(rewardShareData);
}
// Possibly check signatures if block is recent
@ -885,7 +886,7 @@ public class Block {
byte[] message = Longs.toByteArray(this.blockData.getOnlineAccountsTimestamp());
for (int i = 0; i < onlineAccountsSignatures.size(); ++i) {
PublicKeyAccount account = new PublicKeyAccount(null, expandedAccounts.get(i).getProxyPublicKey());
PublicKeyAccount account = new PublicKeyAccount(null, expandedAccounts.get(i).getRewardSharePublicKey());
byte[] signature = onlineAccountsSignatures.get(i);
if (!account.verify(signature, message))
@ -900,7 +901,7 @@ public class Block {
/**
* Returns whether Block is valid.
* <p>
* Performs various tests like checking for parent block, correct block timestamp, version, generating balance, etc.
* Performs various tests like checking for parent block, correct block timestamp, version, etc.
* <p>
* Checks block's transactions by testing their validity then processing them.<br>
* Hence uses a repository savepoint during execution.
@ -941,9 +942,9 @@ public class Block {
if (this.blockData.getVersion() < 2 && this.blockData.getATCount() != 0)
return ValidationResult.FEATURE_NOT_YET_RELEASED;
// Check generator is allowed to forge this block
if (!isGeneratorValidToForge(parentBlock))
return ValidationResult.GENERATOR_NOT_ACCEPTED;
// Check minter is allowed to mint this block
if (!isMinterValid(parentBlock))
return ValidationResult.MINTER_NOT_ACCEPTED;
// Online Accounts
ValidationResult onlineAccountsResult = this.areOnlineAccountsValid();
@ -1148,15 +1149,15 @@ public class Block {
calcTransactionsSignature();
}
/** Returns whether block's generator is actually allowed to forge this block. */
protected boolean isGeneratorValidToForge(Block parentBlock) throws DataException {
// Block's generator public key must be known proxy forging public key
ProxyForgerData proxyForgerData = this.repository.getAccountRepository().getProxyForgeData(this.blockData.getGeneratorPublicKey());
if (proxyForgerData == null)
/** Returns whether block's minter is actually allowed to mint this block. */
protected boolean isMinterValid(Block parentBlock) throws DataException {
// Qortal: block's minter public key must be known reward-share public key
RewardShareData rewardShareData = this.repository.getAccountRepository().getRewardShare(this.blockData.getMinterPublicKey());
if (rewardShareData == null)
return false;
Account forger = new PublicKeyAccount(this.repository, proxyForgerData.getForgerPublicKey());
return forger.canForge();
Account mintingAccount = new PublicKeyAccount(this.repository, rewardShareData.getMinterPublicKey());
return mintingAccount.canMint();
}
/**
@ -1184,7 +1185,7 @@ public class Block {
processGroupApprovalTransactions();
if (this.blockData.getHeight() > 1)
// Give transaction fees to generator/proxy
// Give transaction fees to minter/reward-share account(s)
rewardTransactionFees();
// Process AT fees and save AT states into repository
@ -1204,8 +1205,8 @@ public class Block {
}
protected void increaseAccountLevels() throws DataException {
// We need to do this for both forgers and recipients
this.increaseAccountLevels(expandedAccount -> expandedAccount.isForgerFounder, expandedAccount -> expandedAccount.forgerAccountData);
// We need to do this for both minters and recipients
this.increaseAccountLevels(expandedAccount -> expandedAccount.isMinterFounder, expandedAccount -> expandedAccount.mintingAccountData);
this.increaseAccountLevels(expandedAccount -> expandedAccount.isRecipientFounder, expandedAccount -> expandedAccount.recipientAccountData);
}
@ -1214,19 +1215,19 @@ public class Block {
final List<ExpandedAccount> expandedAccounts = this.getExpandedAccounts();
final boolean isProcessingRecipients = getAccountData.apply(expandedAccounts.get(0)) == expandedAccounts.get(0).recipientAccountData;
// Increase blocks generated count for all accounts
// Increase blocks-minted count for all accounts
for (int a = 0; a < expandedAccounts.size(); ++a) {
ExpandedAccount expandedAccount = expandedAccounts.get(a);
// Don't increase twice if recipient is also forger.
if (isProcessingRecipients && expandedAccount.isRecipientAlsoForger)
// Don't increase twice if recipient is also minter.
if (isProcessingRecipients && expandedAccount.isRecipientAlsoMinter)
continue;
AccountData accountData = getAccountData.apply(expandedAccount);
accountData.setBlocksGenerated(accountData.getBlocksGenerated() + 1);
repository.getAccountRepository().setBlocksGenerated(accountData);
LOGGER.trace(() -> String.format("Block generator %s up to %d generated block%s", accountData.getAddress(), accountData.getBlocksGenerated(), (accountData.getBlocksGenerated() != 1 ? "s" : "")));
accountData.setBlocksMinted(accountData.getBlocksMinted() + 1);
repository.getAccountRepository().setMintedBlockCount(accountData);
LOGGER.trace(() -> String.format("Block minted %s up to %d minted block%s", accountData.getAddress(), accountData.getBlocksMinted(), (accountData.getBlocksMinted() != 1 ? "s" : "")));
}
// We are only interested in accounts that are NOT founders and NOT already highest level
@ -1237,15 +1238,15 @@ public class Block {
ExpandedAccount expandedAccount = candidateAccounts.get(c);
final AccountData accountData = getAccountData.apply(expandedAccount);
final int effectiveBlocksGenerated = cumulativeBlocksByLevel.get(accountData.getInitialLevel()) + accountData.getBlocksGenerated();
final int effectiveBlocksMinted = cumulativeBlocksByLevel.get(accountData.getInitialLevel()) + accountData.getBlocksMinted();
for (int newLevel = maximumLevel; newLevel > 0; --newLevel)
if (effectiveBlocksGenerated >= cumulativeBlocksByLevel.get(newLevel)) {
if (effectiveBlocksMinted >= cumulativeBlocksByLevel.get(newLevel)) {
if (newLevel > accountData.getLevel()) {
// Account has increased in level!
accountData.setLevel(newLevel);
repository.getAccountRepository().setLevel(accountData);
LOGGER.trace(() -> String.format("Block generator %s bumped to level %d", accountData.getAddress(), accountData.getLevel()));
LOGGER.trace(() -> String.format("Block minter %s bumped to level %d", accountData.getAddress(), accountData.getLevel()));
}
break;
@ -1393,7 +1394,7 @@ public class Block {
*/
public void orphan() throws DataException {
if (this.blockData.getHeight() > 1)
// Deduct any transaction fees from generator/proxy
// Deduct any transaction fees from minter/reward-share account(s)
deductTransactionFees();
// Orphan, and unlink, transactions from this block
@ -1512,8 +1513,8 @@ public class Block {
}
protected void decreaseAccountLevels() throws DataException {
// We need to do this for both forgers and recipients
this.decreaseAccountLevels(expandedAccount -> expandedAccount.isForgerFounder, expandedAccount -> expandedAccount.forgerAccountData);
// We need to do this for both minters and recipients
this.decreaseAccountLevels(expandedAccount -> expandedAccount.isMinterFounder, expandedAccount -> expandedAccount.mintingAccountData);
this.decreaseAccountLevels(expandedAccount -> expandedAccount.isRecipientFounder, expandedAccount -> expandedAccount.recipientAccountData);
}
@ -1522,19 +1523,19 @@ public class Block {
final List<ExpandedAccount> expandedAccounts = this.getExpandedAccounts();
final boolean isProcessingRecipients = getAccountData.apply(expandedAccounts.get(0)) == expandedAccounts.get(0).recipientAccountData;
// Decrease blocks generated count for all accounts
// Decrease blocks minted count for all accounts
for (int a = 0; a < expandedAccounts.size(); ++a) {
ExpandedAccount expandedAccount = expandedAccounts.get(a);
// Don't decrease twice if recipient is also forger.
if (isProcessingRecipients && expandedAccount.isRecipientAlsoForger)
// Don't decrease twice if recipient is also minter.
if (isProcessingRecipients && expandedAccount.isRecipientAlsoMinter)
continue;
AccountData accountData = getAccountData.apply(expandedAccount);
accountData.setBlocksGenerated(accountData.getBlocksGenerated() - 1);
repository.getAccountRepository().setBlocksGenerated(accountData);
LOGGER.trace(() -> String.format("Block generator %s down to %d generated block%s", accountData.getAddress(), accountData.getBlocksGenerated(), (accountData.getBlocksGenerated() != 1 ? "s" : "")));
accountData.setBlocksMinted(accountData.getBlocksMinted() - 1);
repository.getAccountRepository().setMintedBlockCount(accountData);
LOGGER.trace(() -> String.format("Block minter %s down to %d minted block%s", accountData.getAddress(), accountData.getBlocksMinted(), (accountData.getBlocksMinted() != 1 ? "s" : "")));
}
// We are only interested in accounts that are NOT founders and NOT already lowest level
@ -1545,15 +1546,15 @@ public class Block {
ExpandedAccount expandedAccount = candidateAccounts.get(c);
final AccountData accountData = getAccountData.apply(expandedAccount);
final int effectiveBlocksGenerated = cumulativeBlocksByLevel.get(accountData.getInitialLevel()) + accountData.getBlocksGenerated();
final int effectiveBlocksMinted = cumulativeBlocksByLevel.get(accountData.getInitialLevel()) + accountData.getBlocksMinted();
for (int newLevel = maximumLevel; newLevel > 0; --newLevel)
if (effectiveBlocksGenerated >= cumulativeBlocksByLevel.get(newLevel)) {
if (effectiveBlocksMinted >= cumulativeBlocksByLevel.get(newLevel)) {
if (newLevel < accountData.getLevel()) {
// Account has decreased in level!
accountData.setLevel(newLevel);
repository.getAccountRepository().setLevel(accountData);
LOGGER.trace(() -> String.format("Block generator %s reduced to level %d", accountData.getAddress(), accountData.getLevel()));
LOGGER.trace(() -> String.format("Block minter %s reduced to level %d", accountData.getAddress(), accountData.getLevel()));
}
break;
@ -1574,7 +1575,7 @@ public class Block {
LOGGER.trace(() -> String.format("Bin %d share of %s: %s", binIndex, totalAmount.toPlainString(), binAmount.toPlainString()));
// Spread across all accounts in bin
List<ExpandedAccount> binnedAccounts = expandedAccounts.stream().filter(accountInfo -> !accountInfo.isForgerFounder && accountInfo.shareBin == binIndex).collect(Collectors.toList());
List<ExpandedAccount> binnedAccounts = expandedAccounts.stream().filter(accountInfo -> !accountInfo.isMinterFounder && accountInfo.shareBin == binIndex).collect(Collectors.toList());
if (binnedAccounts.isEmpty())
continue;
@ -1596,11 +1597,11 @@ public class Block {
BigDecimal totalQoraHeld = BigDecimal.ZERO;
for (int i = 0; i < expandedAccounts.size(); ++i) {
ExpandedAccount expandedAccount = expandedAccounts.get(i);
if (expandedAccount.forgerQoraAmount == null)
if (expandedAccount.minterQoraAmount == null)
continue;
qoraHolderAccounts.add(expandedAccount);
totalQoraHeld = totalQoraHeld.add(expandedAccount.forgerQoraAmount);
totalQoraHeld = totalQoraHeld.add(expandedAccount.minterQoraAmount);
}
final BigDecimal finalTotalQoraHeld = totalQoraHeld;
@ -1608,9 +1609,9 @@ public class Block {
for (int h = 0; h < qoraHolderAccounts.size(); ++h) {
ExpandedAccount expandedAccount = qoraHolderAccounts.get(h);
final BigDecimal holderAmount = qoraHoldersAmount.multiply(totalQoraHeld).divide(expandedAccount.forgerQoraAmount, RoundingMode.DOWN);
LOGGER.trace(() -> String.format("Forger account %s has %s / %s QORA so share: %s",
expandedAccount.forgerAccount.getAddress(), expandedAccount.forgerQoraAmount, finalTotalQoraHeld, holderAmount.toPlainString()));
final BigDecimal holderAmount = qoraHoldersAmount.multiply(totalQoraHeld).divide(expandedAccount.minterQoraAmount, RoundingMode.DOWN);
LOGGER.trace(() -> String.format("Minter account %s has %s / %s QORA so share: %s",
expandedAccount.mintingAccount.getAddress(), expandedAccount.minterQoraAmount, finalTotalQoraHeld, holderAmount.toPlainString()));
expandedAccount.distribute(holderAmount);
sharedAmount = sharedAmount.add(holderAmount);
@ -1620,7 +1621,7 @@ public class Block {
BigDecimal foundersAmount = totalAmount.subtract(sharedAmount);
LOGGER.debug(String.format("Shared %s of %s, remaining %s to founders", sharedAmount.toPlainString(), totalAmount.toPlainString(), foundersAmount.toPlainString()));
List<ExpandedAccount> founderAccounts = expandedAccounts.stream().filter(accountInfo -> accountInfo.isForgerFounder).collect(Collectors.toList());
List<ExpandedAccount> founderAccounts = expandedAccounts.stream().filter(accountInfo -> accountInfo.isMinterFounder).collect(Collectors.toList());
if (founderAccounts.isEmpty())
return;

View File

@ -61,8 +61,6 @@ public class BlockChain {
private BigDecimal maxBytesPerUnitFee;
private BigDecimal minFeePerByte;
/** Number of blocks between recalculating block's generating balance. */
private int blockDifficultyInterval;
/** Maximum acceptable timestamp disagreement offset in milliseconds. */
private long blockTimestampMargin;
/** Maximum block size, in bytes. */
@ -113,15 +111,23 @@ public class BlockChain {
BigDecimal qoraHoldersShare;
/**
* Number of generated blocks required to reach next level from previous.
* Number of minted blocks required to reach next level from previous.
* <p>
* Use account's current level as index.<br>
* If account's level isn't valid as an index, then account's level is at maximum.
* <p>
* Example: if <tt>blocksNeededByLevel[3]</tt> is 200,<br>
* then level 3 accounts need to mint 200 blocks to reach level 4.
*/
List<Integer> blocksNeededByLevel;
/**
* Cumulative number of generated blocks required to reach level from scratch.
* Cumulative number of minted blocks required to reach next level from scratch.
* <p>
* Use target level as index. <tt>cumulativeBlocksByLevel[0]</tt> should be 0.
* <p>
* Example; if <tt>cumulativeBlocksByLevel[2</tt>] is 1800,<br>
* the a <b>new</b> account will need to mint 1800 blocks to reach level 2.
* <p>
* Generated just after blockchain config is parsed and validated.
* <p>
@ -138,7 +144,9 @@ public class BlockChain {
}
List<BlockTimingByHeight> blockTimingsByHeight;
private int maxProxyRelationships;
private int minAccountLevelToMint = 1;
private int minAccountLevelToRewardShare;
private int maxRewardSharesPerMintingAccount;
/** Minimum time to retain online account signatures (ms) for block validity checks. */
private long onlineAccountSignaturesMinLifetime;
@ -263,10 +271,6 @@ public class BlockChain {
return this.transactionExpiryPeriod;
}
public int getBlockDifficultyInterval() {
return this.blockDifficultyInterval;
}
public long getBlockTimestampMargin() {
return this.blockTimestampMargin;
}
@ -308,8 +312,16 @@ public class BlockChain {
return this.qoraHoldersShare;
}
public int getMaxProxyRelationships() {
return this.maxProxyRelationships;
public int getMinAccountLevelToMint() {
return this.minAccountLevelToMint;
}
public int getMinAccountLevelToRewardShare() {
return this.minAccountLevelToRewardShare;
}
public int getMaxRewardSharesPerMintingAccount() {
return this.maxRewardSharesPerMintingAccount;
}
public long getOnlineAccountSignaturesMinLifetime() {
@ -406,6 +418,10 @@ public class BlockChain {
if (this.maxBlockSize <= 0)
Settings.throwValidationError("Invalid \"maxBlockSize\" in blockchain config");
if (this.minAccountLevelToRewardShare <= 0)
Settings.throwValidationError("Invalid/missing \"minAccountLevelToRewardShare\" in blockchain config");
if (this.featureTriggers == null)
Settings.throwValidationError("No \"featureTriggers\" entry found in blockchain config");

View File

@ -14,8 +14,8 @@ import org.qora.account.PrivateKeyAccount;
import org.qora.account.PublicKeyAccount;
import org.qora.block.Block.ValidationResult;
import org.qora.controller.Controller;
import org.qora.data.account.ForgingAccountData;
import org.qora.data.account.ProxyForgerData;
import org.qora.data.account.MintingAccountData;
import org.qora.data.account.RewardShareData;
import org.qora.data.block.BlockData;
import org.qora.data.transaction.TransactionData;
import org.qora.network.Network;
@ -29,28 +29,26 @@ import org.qora.transaction.Transaction;
import org.qora.utils.Base58;
import org.qora.utils.NTP;
// Forging new blocks
// Minting new blocks
// How is the private key going to be supplied?
public class BlockGenerator extends Thread {
public class BlockMinter extends Thread {
// Properties
private boolean running;
// Other properties
private static final Logger LOGGER = LogManager.getLogger(BlockGenerator.class);
private static final Logger LOGGER = LogManager.getLogger(BlockMinter.class);
// Constructors
public BlockGenerator() {
public BlockMinter() {
this.running = true;
}
// Main thread loop
@Override
public void run() {
Thread.currentThread().setName("BlockGenerator");
Thread.currentThread().setName("BlockMinter");
try (final Repository repository = RepositoryManager.getRepository()) {
if (Settings.getInstance().getWipeUnconfirmedOnStart()) {
@ -71,19 +69,19 @@ public class BlockGenerator extends Thread {
List<Block> newBlocks = new ArrayList<>();
// Flags that allow us to track whether generating is possible changes,
// Flags for tracking change in whether minting is possible,
// so we can notify Controller, and further update SysTray, etc.
boolean isGenerationPossible = false;
boolean wasGenerationPossible = isGenerationPossible;
boolean isMintingPossible = false;
boolean wasMintingPossible = isMintingPossible;
while (running) {
// Sleep for a while
try {
repository.discardChanges(); // Free repository locks, if any
if (isGenerationPossible != wasGenerationPossible)
Controller.getInstance().onGenerationPossibleChange(isGenerationPossible);
if (isMintingPossible != wasMintingPossible)
Controller.getInstance().onMintingPossibleChange(isMintingPossible);
wasGenerationPossible = isGenerationPossible;
wasMintingPossible = isMintingPossible;
Thread.sleep(1000);
} catch (InterruptedException e) {
@ -91,7 +89,7 @@ public class BlockGenerator extends Thread {
return;
}
isGenerationPossible = false;
isMintingPossible = false;
final Long now = NTP.getTime();
if (now == null)
@ -105,9 +103,9 @@ public class BlockGenerator extends Thread {
if (Controller.getInstance().getOnlineAccounts().isEmpty())
continue;
List<ForgingAccountData> forgingAccountsData = repository.getAccountRepository().getForgingAccounts();
// No forging accounts?
if (forgingAccountsData.isEmpty())
List<MintingAccountData> mintingAccountsData = repository.getAccountRepository().getMintingAccounts();
// No minting accounts?
if (mintingAccountsData.isEmpty())
continue;
List<Peer> peers = Network.getInstance().getUniqueHandshakedPeers();
@ -116,7 +114,7 @@ public class BlockGenerator extends Thread {
// Disregard peers that have "misbehaved" recently
peers.removeIf(Controller.hasMisbehaved);
// Don't generate if we don't have enough connected peers as where would the transactions/consensus come from?
// Don't mint if we don't have enough connected peers as where would the transactions/consensus come from?
if (peers.size() < Settings.getInstance().getMinBlockchainPeers())
continue;
@ -124,13 +122,13 @@ public class BlockGenerator extends Thread {
peers.removeIf(Controller.hasNoRecentBlock);
// If we have any peers with a recent block, but our latest block isn't recent
// then we need to synchronize instead of generating.
// then we need to synchronize instead of minting.
if (!peers.isEmpty() && lastBlockData.getTimestamp() < minLatestBlockTimestamp)
continue;
// There are no peers with a recent block and/or our latest block is recent
// so go ahead and generate a block if possible.
isGenerationPossible = true;
// so go ahead and mint a block if possible.
isMintingPossible = true;
// Check blockchain hasn't changed
if (previousBlock == null || !Arrays.equals(previousBlock.getSignature(), lastBlockData.getSignature())) {
@ -139,20 +137,20 @@ public class BlockGenerator extends Thread {
}
// Do we need to build any potential new blocks?
List<PrivateKeyAccount> forgingAccounts = forgingAccountsData.stream().map(accountData -> new PrivateKeyAccount(repository, accountData.getSeed())).collect(Collectors.toList());
List<PrivateKeyAccount> mintingAccounts = mintingAccountsData.stream().map(accountData -> new PrivateKeyAccount(repository, accountData.getPrivateKey())).collect(Collectors.toList());
// Discard accounts we have blocks for
forgingAccounts.removeIf(account -> newBlocks.stream().anyMatch(newBlock -> newBlock.getGenerator().getAddress().equals(account.getAddress())));
mintingAccounts.removeIf(account -> newBlocks.stream().anyMatch(newBlock -> newBlock.getMinter().getAddress().equals(account.getAddress())));
for (PrivateKeyAccount generator : forgingAccounts) {
for (PrivateKeyAccount mintingAccount : mintingAccounts) {
// First block does the AT heavy-lifting
if (newBlocks.isEmpty()) {
Block newBlock = new Block(repository, previousBlock.getBlockData(), generator);
Block newBlock = new Block(repository, previousBlock.getBlockData(), mintingAccount);
newBlocks.add(newBlock);
} else {
// The blocks for other generators require less effort...
// The blocks for other minters require less effort...
Block newBlock = newBlocks.get(0);
newBlocks.add(newBlock.regenerate(generator));
newBlocks.add(newBlock.newMinter(mintingAccount));
}
}
@ -165,7 +163,7 @@ public class BlockGenerator extends Thread {
if (!blockchainLock.tryLock())
continue;
boolean newBlockGenerated = false;
boolean newBlockMinted = false;
try {
// Clear repository's "in transaction" state so we don't cause a repository deadlock
@ -188,7 +186,8 @@ public class BlockGenerator extends Thread {
if (goodBlocks.isEmpty())
continue;
// Pick random generator
// Pick random block
// TODO/XXX - shouldn't this pick our BEST block instead?
int winningIndex = new Random().nextInt(goodBlocks.size());
Block newBlock = goodBlocks.get(winningIndex);
@ -205,7 +204,7 @@ public class BlockGenerator extends Thread {
ValidationResult validationResult = newBlock.isValid();
if (validationResult != ValidationResult.OK) {
// No longer valid? Report and discard
LOGGER.error(String.format("Valid, generated block now invalid '%s' after adding unconfirmed transactions?", validationResult.name()));
LOGGER.error(String.format("To-be-minted block now invalid '%s' after adding unconfirmed transactions?", validationResult.name()));
// Rebuild block candidates, just to be sure
newBlocks.clear();
@ -216,43 +215,43 @@ public class BlockGenerator extends Thread {
try {
newBlock.process();
LOGGER.info(String.format("Generated new block: %d", newBlock.getBlockData().getHeight()));
LOGGER.info(String.format("Minted new block: %d", newBlock.getBlockData().getHeight()));
repository.saveChanges();
ProxyForgerData proxyForgerData = repository.getAccountRepository().getProxyForgeData(newBlock.getBlockData().getGeneratorPublicKey());
RewardShareData rewardShareData = repository.getAccountRepository().getRewardShare(newBlock.getBlockData().getMinterPublicKey());
if (proxyForgerData != null) {
PublicKeyAccount forger = new PublicKeyAccount(repository, proxyForgerData.getForgerPublicKey());
LOGGER.info(String.format("Generated block %d, sig %.8s by %s on behalf of %s",
if (rewardShareData != null) {
PublicKeyAccount mintingAccount = new PublicKeyAccount(repository, rewardShareData.getMinterPublicKey());
LOGGER.info(String.format("Minted block %d, sig %.8s by %s on behalf of %s",
newBlock.getBlockData().getHeight(),
Base58.encode(newBlock.getBlockData().getSignature()),
forger.getAddress(),
proxyForgerData.getRecipient()));
mintingAccount.getAddress(),
rewardShareData.getRecipient()));
} else {
LOGGER.info(String.format("Generated block %d, sig %.8s by %s",
LOGGER.info(String.format("Minted block %d, sig %.8s by %s",
newBlock.getBlockData().getHeight(),
Base58.encode(newBlock.getBlockData().getSignature()),
newBlock.getGenerator().getAddress()));
newBlock.getMinter().getAddress()));
}
repository.saveChanges();
// Notify controller
newBlockGenerated = true;
newBlockMinted = true;
} catch (DataException e) {
// Unable to process block - report and discard
LOGGER.error("Unable to process newly generated block?", e);
LOGGER.error("Unable to process newly minted block?", e);
newBlocks.clear();
}
} finally {
blockchainLock.unlock();
}
if (newBlockGenerated)
Controller.getInstance().onGeneratedBlock();
if (newBlockMinted)
Controller.getInstance().onBlockMinted();
}
} catch (DataException e) {
LOGGER.warn("Repository issue while running block generator", e);
LOGGER.warn("Repository issue while running block minter", e);
}
}
@ -316,7 +315,7 @@ public class BlockGenerator extends Thread {
// If newBlock is no longer valid then we can't use transaction
ValidationResult validationResult = newBlock.isValid();
if (validationResult != ValidationResult.OK) {
LOGGER.debug(() -> String.format("Skipping invalid transaction %s during block generation", Base58.encode(transactionData.getSignature())));
LOGGER.debug(() -> String.format("Skipping invalid transaction %s during block minting", Base58.encode(transactionData.getSignature())));
newBlock.deleteTransaction(transactionData);
}
}
@ -328,15 +327,18 @@ public class BlockGenerator extends Thread {
this.interrupt();
}
public static void generateTestingBlock(Repository repository, PrivateKeyAccount generator) throws DataException {
public static void mintTestingBlock(Repository repository, PrivateKeyAccount mintingAccount) throws DataException {
if (!BlockChain.getInstance().isTestChain()) {
LOGGER.warn("Ignoring attempt to generate testing block for non-test chain!");
LOGGER.warn("Ignoring attempt to mint testing block for non-test chain!");
return;
}
// Ensure mintingAccount is 'online' so blocks can be minted
Controller.getInstance().ensureTestingAccountOnline(mintingAccount);
BlockData previousBlockData = repository.getBlockRepository().getLastBlock();
Block newBlock = new Block(repository, previousBlockData, generator);
Block newBlock = new Block(repository, previousBlockData, mintingAccount);
// Make sure we're the only thread modifying the blockchain
ReentrantLock blockchainLock = Controller.getInstance().getBlockchainLock();
@ -354,8 +356,7 @@ public class BlockGenerator extends Thread {
// Is newBlock still valid?
ValidationResult validationResult = newBlock.isValid();
if (validationResult != ValidationResult.OK)
throw new IllegalStateException(
"Valid, generated block now invalid '" + validationResult.name() + "' after adding unconfirmed transactions?");
throw new IllegalStateException(String.format("To-be-minted test block now invalid '%s' after adding unconfirmed transactions?", validationResult.name()));
// Add to blockchain
newBlock.process();

View File

@ -125,16 +125,16 @@ public class GenesisBlock extends Block {
byte[] reference = GENESIS_REFERENCE;
int transactionCount = transactionsData.size();
BigDecimal totalFees = BigDecimal.ZERO.setScale(8);
byte[] generatorPublicKey = GenesisAccount.PUBLIC_KEY;
byte[] bytesForSignature = getBytesForSignature(info.version, reference, generatorPublicKey);
byte[] generatorSignature = calcSignature(bytesForSignature);
byte[] transactionsSignature = generatorSignature;
byte[] minterPublicKey = GenesisAccount.PUBLIC_KEY;
byte[] bytesForSignature = getBytesForSignature(info.version, reference, minterPublicKey);
byte[] minterSignature = calcSignature(bytesForSignature);
byte[] transactionsSignature = minterSignature;
int height = 1;
int atCount = 0;
BigDecimal atFees = BigDecimal.ZERO.setScale(8);
genesisBlockData = new BlockData(info.version, reference, transactionCount, totalFees, transactionsSignature, height, info.timestamp,
generatorPublicKey, generatorSignature, atCount, atFees);
minterPublicKey, minterSignature, atCount, atFees);
}
// More information
@ -146,7 +146,7 @@ public class GenesisBlock extends Block {
byte[] signature = calcSignature(blockData);
// Validate block signature
if (!Arrays.equals(signature, blockData.getGeneratorSignature()))
if (!Arrays.equals(signature, blockData.getMinterSignature()))
return false;
// Validate transactions signature
@ -169,7 +169,7 @@ public class GenesisBlock extends Block {
}
/**
* Refuse to calculate genesis block's generator signature!
* Refuse to calculate genesis block's minter signature!
* <p>
* This is not possible as there is no private key for the genesis account and so no way to sign data.
* <p>
@ -178,7 +178,7 @@ public class GenesisBlock extends Block {
* @throws IllegalStateException
*/
@Override
public void calcGeneratorSignature() {
public void calcMinterSignature() {
throw new IllegalStateException("There is no private key for genesis account");
}
@ -197,7 +197,7 @@ public class GenesisBlock extends Block {
}
/**
* Generate genesis block generator/transactions signature.
* Generate genesis block minter/transactions signature.
* <p>
* This is handled differently as there is no private key for the genesis account and so no way to sign data.
* <p>
@ -210,7 +210,7 @@ public class GenesisBlock extends Block {
return Bytes.concat(digest, digest);
}
private static byte[] getBytesForSignature(int version, byte[] reference, byte[] generatorPublicKey) {
private static byte[] getBytesForSignature(int version, byte[] reference, byte[] minterPublicKey) {
try {
// Passing expected size to ByteArrayOutputStream avoids reallocation when adding more bytes than default 32.
// See below for explanation of some of the values used to calculated expected size.
@ -230,7 +230,7 @@ public class GenesisBlock extends Block {
bytes.write(Bytes.ensureCapacity(reference, 64, 0));
// NOTE: Genesis account's public key is only 8 bytes, not the usual 32, so we have to pad.
bytes.write(Bytes.ensureCapacity(generatorPublicKey, 32, 0));
bytes.write(Bytes.ensureCapacity(minterPublicKey, 32, 0));
return bytes.toByteArray();
} catch (IOException e) {
@ -240,7 +240,7 @@ public class GenesisBlock extends Block {
/** Convenience method for calculating genesis block signatures from block data */
private static byte[] calcSignature(BlockData blockData) {
byte[] bytes = getBytesForSignature(blockData.getVersion(), blockData.getReference(), blockData.getGeneratorPublicKey());
byte[] bytes = getBytesForSignature(blockData.getVersion(), blockData.getReference(), blockData.getMinterPublicKey());
return calcSignature(bytes);
}
@ -249,7 +249,7 @@ public class GenesisBlock extends Block {
byte[] signature = calcSignature(this.blockData);
// Validate block signature
if (!Arrays.equals(signature, this.blockData.getGeneratorSignature()))
if (!Arrays.equals(signature, this.blockData.getMinterSignature()))
return false;
// Validate transactions signature

View File

@ -31,10 +31,10 @@ import org.qora.api.ApiService;
import org.qora.block.Block;
import org.qora.block.BlockChain;
import org.qora.block.BlockChain.BlockTimingByHeight;
import org.qora.block.BlockGenerator;
import org.qora.block.BlockMinter;
import org.qora.controller.Synchronizer.SynchronizationResult;
import org.qora.crypto.Crypto;
import org.qora.data.account.ForgingAccountData;
import org.qora.data.account.MintingAccountData;
import org.qora.data.block.BlockData;
import org.qora.data.block.BlockSummaryData;
import org.qora.data.network.OnlineAccountData;
@ -114,7 +114,7 @@ public class Controller extends Thread {
private static final long LAST_SEEN_EXPIRY_PERIOD = (ONLINE_TIMESTAMP_MODULUS * 2) + (1 * 60 * 1000L);
private static volatile boolean isStopping = false;
private static BlockGenerator blockGenerator = null;
private static BlockMinter blockMinter = null;
private static volatile boolean requestSync = false;
private static volatile boolean requestSysTrayUpdate = false;
private static Controller instance;
@ -129,8 +129,8 @@ public class Controller extends Thread {
private long deleteExpiredTimestamp = startTime + DELETE_EXPIRED_INTERVAL; // ms
private long onlineAccountsTasksTimestamp = startTime + ONLINE_ACCOUNTS_TASKS_INTERVAL; // ms
/** Whether we can generate new blocks, as reported by BlockGenerator. */
private volatile boolean isGenerationPossible = false;
/** Whether we can mint new blocks, as reported by BlockMinter. */
private volatile boolean isMintingPossible = false;
/** Latest block signatures from other peers that we know are on inferior chains. */
List<ByteArray> inferiorChainSignatures = new ArrayList<>();
@ -154,7 +154,7 @@ public class Controller extends Thread {
*/
private Map<Integer, Triple<String, Peer, Long>> arbitraryDataRequests = Collections.synchronizedMap(new HashMap<>());
/** Lock for only allowing one blockchain-modifying codepath at a time. e.g. synchronization or newly generated block. */
/** Lock for only allowing one blockchain-modifying codepath at a time. e.g. synchronization or newly minted block. */
private final ReentrantLock blockchainLock = new ReentrantLock();
/** Cache of 'online accounts' */
@ -300,9 +300,9 @@ public class Controller extends Thread {
}
});
LOGGER.info("Starting block generator");
blockGenerator = new BlockGenerator();
blockGenerator.start();
LOGGER.info("Starting block minter");
blockMinter = new BlockMinter();
blockMinter.start();
// Arbitrary transaction data manager
// LOGGER.info("Starting arbitrary-transaction data manager");
@ -566,9 +566,9 @@ public class Controller extends Thread {
String connectionsText = Translator.INSTANCE.translate("SysTray", numberOfPeers != 1 ? "CONNECTIONS" : "CONNECTION");
String heightText = Translator.INSTANCE.translate("SysTray", "BLOCK_HEIGHT");
String generatingText = Translator.INSTANCE.translate("SysTray", isGenerationPossible ? "GENERATING_ENABLED" : "GENERATING_DISABLED");
String mintingText = Translator.INSTANCE.translate("SysTray", isMintingPossible ? "MINTING_ENABLED" : "MINTING_DISABLED");
String tooltip = String.format("%s - %d %s - %s %d", generatingText, numberOfPeers, connectionsText, heightText, height);
String tooltip = String.format("%s - %d %s - %s %d", mintingText, numberOfPeers, connectionsText, heightText, height);
SysTray.getInstance().setToolTipText(tooltip);
}
@ -616,11 +616,11 @@ public class Controller extends Thread {
// LOGGER.info("Shutting down arbitrary-transaction data manager");
// ArbitraryDataManager.getInstance().shutdown();
if (blockGenerator != null) {
LOGGER.info("Shutting down block generator");
blockGenerator.shutdown();
if (blockMinter != null) {
LOGGER.info("Shutting down block minter");
blockMinter.shutdown();
try {
blockGenerator.join();
blockMinter.join();
} catch (InterruptedException e) {
// We were interrupted while waiting for thread to join
}
@ -673,12 +673,12 @@ public class Controller extends Thread {
network.broadcast(network::buildGetUnconfirmedTransactionsMessage);
}
public void onGenerationPossibleChange(boolean isGenerationPossible) {
this.isGenerationPossible = isGenerationPossible;
public void onMintingPossibleChange(boolean isMintingPossible) {
this.isMintingPossible = isMintingPossible;
requestSysTrayUpdate = true;
}
public void onGeneratedBlock() {
public void onBlockMinted() {
// Broadcast our new height info
BlockData latestBlockData;
@ -686,7 +686,7 @@ public class Controller extends Thread {
latestBlockData = repository.getBlockRepository().getLastBlock();
this.setChainTip(latestBlockData);
} catch (DataException e) {
LOGGER.warn(String.format("Repository issue when trying to fetch post-generation chain tip: %s", e.getMessage()));
LOGGER.warn(String.format("Repository issue when trying to fetch post-mint chain tip: %s", e.getMessage()));
return;
}
@ -768,7 +768,7 @@ public class Controller extends Thread {
continue;
// Update peer chain tip data
PeerChainTipData newChainTipData = new PeerChainTipData(blockData.getHeight(), blockData.getSignature(), blockData.getTimestamp(), blockData.getGeneratorPublicKey());
PeerChainTipData newChainTipData = new PeerChainTipData(blockData.getHeight(), blockData.getSignature(), blockData.getTimestamp(), blockData.getMinterPublicKey());
connectedPeer.setChainTipData(newChainTipData);
}
@ -817,7 +817,7 @@ public class Controller extends Thread {
continue;
// Update peer chain tip data
PeerChainTipData newChainTipData = new PeerChainTipData(heightV2Message.getHeight(), heightV2Message.getSignature(), heightV2Message.getTimestamp(), heightV2Message.getGenerator());
PeerChainTipData newChainTipData = new PeerChainTipData(heightV2Message.getHeight(), heightV2Message.getSignature(), heightV2Message.getTimestamp(), heightV2Message.getMinterPublicKey());
connectedPeer.setChainTipData(newChainTipData);
}
@ -1280,6 +1280,31 @@ public class Controller extends Thread {
}
}
public void ensureTestingAccountOnline(PrivateKeyAccount mintingAccount) {
if (!BlockChain.getInstance().isTestChain()) {
LOGGER.warn("Ignoring attempt to ensure test account is online for non-test chain!");
return;
}
final Long now = NTP.getTime();
if (now == null)
return;
// Check mintingAccount is actually reward-share?
// Add reward-share & timestamp to online accounts
final long onlineAccountsTimestamp = Controller.toOnlineAccountTimestamp(now);
byte[] timestampBytes = Longs.toByteArray(onlineAccountsTimestamp);
byte[] signature = mintingAccount.sign(timestampBytes);
byte[] publicKey = mintingAccount.getPublicKey();
OnlineAccountData ourOnlineAccountData = new OnlineAccountData(onlineAccountsTimestamp, signature, publicKey);
synchronized (this.onlineAccounts) {
this.onlineAccounts.add(ourOnlineAccountData);
}
}
private void performOnlineAccountsTasks() {
final Long now = NTP.getTime();
if (now == null)
@ -1324,24 +1349,24 @@ public class Controller extends Thread {
if (now == null)
return;
List<ForgingAccountData> forgingAccounts;
List<MintingAccountData> mintingAccounts;
try (final Repository repository = RepositoryManager.getRepository()) {
forgingAccounts = repository.getAccountRepository().getForgingAccounts();
mintingAccounts = repository.getAccountRepository().getMintingAccounts();
// We have no accounts, but don't reset timestamp
if (forgingAccounts.isEmpty())
if (mintingAccounts.isEmpty())
return;
// Only proxy forging accounts allowed
Iterator<ForgingAccountData> iterator = forgingAccounts.iterator();
// Only reward-share accounts allowed
Iterator<MintingAccountData> iterator = mintingAccounts.iterator();
while (iterator.hasNext()) {
ForgingAccountData forgingAccountData = iterator.next();
MintingAccountData mintingAccountData = iterator.next();
if (!repository.getAccountRepository().isProxyPublicKey(forgingAccountData.getPublicKey()))
if (!repository.getAccountRepository().isRewardSharePublicKey(mintingAccountData.getPublicKey()))
iterator.remove();
}
} catch (DataException e) {
LOGGER.warn(String.format("Repository issue trying to fetch forging accounts: %s", e.getMessage()));
LOGGER.warn(String.format("Repository issue trying to fetch minting accounts: %s", e.getMessage()));
return;
}
@ -1352,12 +1377,12 @@ public class Controller extends Thread {
byte[] timestampBytes = Longs.toByteArray(onlineAccountsTimestamp);
List<OnlineAccountData> ourOnlineAccounts = new ArrayList<>();
FORGING_ACCOUNTS:
for (ForgingAccountData forgingAccountData : forgingAccounts) {
PrivateKeyAccount forgingAccount = new PrivateKeyAccount(null, forgingAccountData.getSeed());
MINTING_ACCOUNTS:
for (MintingAccountData mintingAccountData : mintingAccounts) {
PrivateKeyAccount mintingAccount = new PrivateKeyAccount(null, mintingAccountData.getPrivateKey());
byte[] signature = forgingAccount.sign(timestampBytes);
byte[] publicKey = forgingAccount.getPublicKey();
byte[] signature = mintingAccount.sign(timestampBytes);
byte[] publicKey = mintingAccount.getPublicKey();
// Our account is online
OnlineAccountData ourOnlineAccountData = new OnlineAccountData(onlineAccountsTimestamp, signature, publicKey);
@ -1367,9 +1392,9 @@ public class Controller extends Thread {
OnlineAccountData existingOnlineAccountData = iterator.next();
if (Arrays.equals(existingOnlineAccountData.getPublicKey(), ourOnlineAccountData.getPublicKey())) {
// If our online account is already present, with same timestamp, then move on to next forgingAccount
// If our online account is already present, with same timestamp, then move on to next mintingAccount
if (existingOnlineAccountData.getTimestamp() == onlineAccountsTimestamp)
continue FORGING_ACCOUNTS;
continue MINTING_ACCOUNTS;
// If our online account is already present, but with older timestamp, then remove it
iterator.remove();
@ -1380,7 +1405,7 @@ public class Controller extends Thread {
this.onlineAccounts.add(ourOnlineAccountData);
}
LOGGER.trace(() -> String.format("Added our online account %s with timestamp %d", forgingAccount.getAddress(), onlineAccountsTimestamp));
LOGGER.trace(() -> String.format("Added our online account %s with timestamp %d", mintingAccount.getAddress(), onlineAccountsTimestamp));
ourOnlineAccounts.add(ourOnlineAccountData);
hasInfoChanged = true;
}
@ -1398,6 +1423,7 @@ public class Controller extends Thread {
return (timestamp / ONLINE_TIMESTAMP_MODULUS) * ONLINE_TIMESTAMP_MODULUS;
}
/** Returns list of online accounts with timestamp recent enough to be considered currently online. */
public List<OnlineAccountData> getOnlineAccounts() {
final long onlineTimestamp = Controller.toOnlineAccountTimestamp(NTP.getTime());
@ -1513,7 +1539,7 @@ public class Controller extends Thread {
// Disregard peers that don't have a recent block
peers.removeIf(hasNoRecentBlock);
// Check we have enough peers to potentially synchronize/generate
// Check we have enough peers to potentially synchronize/mint
if (peers.size() < Settings.getInstance().getMinBlockchainPeers())
return false;

View File

@ -158,7 +158,7 @@ public class BTC {
checkpointsFileName = "checkpoints.txt";
}
directory = new File("Qora-BTC");
directory = new File("Qortal-BTC");
if (!directory.exists())
directory.mkdirs();
@ -191,7 +191,7 @@ public class BTC {
}
peerGroup = new PeerGroup(params, chain);
peerGroup.setUserAgent("qqq", "1.0");
peerGroup.setUserAgent("qortal", "1.0");
peerGroup.addPeerDiscovery(new DnsDiscovery(params));
peerGroup.start();
}

View File

@ -0,0 +1,83 @@
package org.qora.crypto;
import com.google.common.primitives.Bytes;
public class CiyamMemoryPoW {
private static final int WORK_BUFFER_LENGTH = 4 * 1024 * 1024;
private static final int WORK_BUFFER_LENGTH_MASK = WORK_BUFFER_LENGTH - 1;
private static final int HASH_LENGTH = 32;
private static final int HASH_LENGTH_MASK = HASH_LENGTH - 1;
public static Integer compute(byte[] data, int start, int range, int difficulty) {
if (range < 1)
throw new IllegalArgumentException("range must be at least 1");
if (difficulty < 1)
throw new IllegalArgumentException("difficulty must be at least 1");
// Hash data with SHA256
byte[] hash = Crypto.digest(data);
assert hash.length == HASH_LENGTH;
byte[] perturbedHash = new byte[HASH_LENGTH];
byte[] workBuffer = new byte[WORK_BUFFER_LENGTH];
byte[] bufferHash = new byte[HASH_LENGTH];
// For each nonce...
for (int nonce = start; nonce < start + range; ++nonce) {
// Perturb hash using nonce
int temp = nonce;
for (int hi = 0; hi < HASH_LENGTH; ++hi) {
perturbedHash[hi] = (byte) (hash[hi] ^ (temp & 0xff));
temp >>>= 1;
}
// Fill large working memory buffer using hash, further perturbing as we go
int wanderingBufferOffset = 0;
byte ch = 0;
int hashOffset = 0;
for (int workBufferOffset = 0; workBufferOffset < WORK_BUFFER_LENGTH; workBufferOffset += HASH_LENGTH) {
System.arraycopy(perturbedHash, 0, workBuffer, workBufferOffset, HASH_LENGTH);
hashOffset = ++hashOffset & HASH_LENGTH_MASK;
ch += perturbedHash[hashOffset];
for (byte hi = 0; hi < HASH_LENGTH; ++hi) {
byte hashByte = perturbedHash[hi];
wanderingBufferOffset = (wanderingBufferOffset << 3) ^ (hashByte & 0xff);
perturbedHash[hi] = (byte) (hashByte ^ (ch + hi));
}
workBuffer[wanderingBufferOffset & WORK_BUFFER_LENGTH_MASK] ^= 0xAA;
// final int finalWanderingBufferOffset = wanderingBufferOffset & WORK_BUFFER_LENGTH_MASK;
// System.out.println(String.format("wanderingBufferOffset: 0x%08x / 0x%08x - %02d%%", finalWanderingBufferOffset, WORK_BUFFER_LENGTH, finalWanderingBufferOffset * 100 / WORK_BUFFER_LENGTH));
}
Bytes.reverse(workBuffer);
// bufferHash = Crypto.digest(workBuffer);
System.arraycopy(workBuffer, 0, bufferHash, 0, HASH_LENGTH);
int hi = 0;
for (hi = 0; hi < difficulty; ++hi)
if (bufferHash[hi] != 0)
break;
if (hi == difficulty)
return nonce;
Thread.yield();
}
return null;
}
}

View File

@ -17,7 +17,7 @@ public class AccountData {
protected int flags;
protected int initialLevel;
protected int level;
protected int blocksGenerated;
protected int blocksMinted;
// Constructors
@ -25,7 +25,7 @@ public class AccountData {
protected AccountData() {
}
public AccountData(String address, byte[] reference, byte[] publicKey, int defaultGroupId, int flags, int initialLevel, int level, int blocksGenerated) {
public AccountData(String address, byte[] reference, byte[] publicKey, int defaultGroupId, int flags, int initialLevel, int level, int blocksMinted) {
this.address = address;
this.reference = reference;
this.publicKey = publicKey;
@ -33,7 +33,7 @@ public class AccountData {
this.flags = flags;
this.initialLevel = initialLevel;
this.level = level;
this.blocksGenerated = blocksGenerated;
this.blocksMinted = blocksMinted;
}
public AccountData(String address) {
@ -94,12 +94,12 @@ public class AccountData {
this.level = level;
}
public int getBlocksGenerated() {
return this.blocksGenerated;
public int getBlocksMinted() {
return this.blocksMinted;
}
public void setBlocksGenerated(int blocksGenerated) {
this.blocksGenerated = blocksGenerated;
public void setBlocksMinted(int blocksMinted) {
this.blocksMinted = blocksMinted;
}
// Comparison

View File

@ -13,38 +13,38 @@ import io.swagger.v3.oas.annotations.media.Schema.AccessMode;
// All properties to be converted to JSON via JAXB
@XmlAccessorType(XmlAccessType.FIELD)
public class ForgingAccountData {
public class MintingAccountData {
// Properties
@Schema(hidden = true)
@XmlTransient
protected byte[] seed;
protected byte[] privateKey;
// Not always present - used by API if not null
@XmlTransient
@Schema(hidden = true)
protected byte[] publicKey;
protected String proxiedBy;
protected String proxiedFor;
protected String mintingAccount;
protected String recipientAccount;
protected String address;
// Constructors
// For JAXB
protected ForgingAccountData() {
protected MintingAccountData() {
}
public ForgingAccountData(byte[] seed) {
this.seed = seed;
this.publicKey = new PrivateKeyAccount(null, seed).getPublicKey();
public MintingAccountData(byte[] privateKey) {
this.privateKey = privateKey;
this.publicKey = PrivateKeyAccount.toPublicKey(privateKey);
}
public ForgingAccountData(byte[] seed, ProxyForgerData proxyForgerData) {
this(seed);
public MintingAccountData(byte[] privateKey, RewardShareData rewardShareData) {
this(privateKey);
if (proxyForgerData != null) {
this.proxiedFor = proxyForgerData.getRecipient();
this.proxiedBy = Crypto.toAddress(proxyForgerData.getForgerPublicKey());
if (rewardShareData != null) {
this.recipientAccount = rewardShareData.getRecipient();
this.mintingAccount = Crypto.toAddress(rewardShareData.getMinterPublicKey());
} else {
this.address = Crypto.toAddress(this.publicKey);
}
@ -52,8 +52,8 @@ public class ForgingAccountData {
// Getters/Setters
public byte[] getSeed() {
return this.seed;
public byte[] getPrivateKey() {
return this.privateKey;
}
@XmlElement(name = "publicKey")

View File

@ -1,58 +0,0 @@
package org.qora.data.account;
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.crypto.Crypto;
// 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;
}
@XmlElement(name = "forger")
public String getForger() {
return Crypto.toAddress(this.forgerPublicKey);
}
}

View File

@ -0,0 +1,58 @@
package org.qora.data.account;
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.crypto.Crypto;
// All properties to be converted to JSON via JAXB
@XmlAccessorType(XmlAccessType.FIELD)
public class RewardShareData {
// Properties
private byte[] minterPublicKey;
private String recipient;
private byte[] rewardSharePublicKey;
private BigDecimal sharePercent;
// Constructors
// For JAXB
protected RewardShareData() {
}
// Used when fetching from repository
public RewardShareData(byte[] minterPublicKey, String recipient, byte[] rewardSharePublicKey, BigDecimal sharePercent) {
this.minterPublicKey = minterPublicKey;
this.recipient = recipient;
this.rewardSharePublicKey = rewardSharePublicKey;
this.sharePercent = sharePercent;
}
// Getters / setters
public byte[] getMinterPublicKey() {
return this.minterPublicKey;
}
public String getRecipient() {
return this.recipient;
}
public byte[] getRewardSharePublicKey() {
return this.rewardSharePublicKey;
}
public BigDecimal getSharePercent() {
return this.sharePercent;
}
@XmlElement(name = "mintingAccount")
public String getMintingAccount() {
return Crypto.toAddress(this.minterPublicKey);
}
}

View File

@ -27,8 +27,8 @@ public class BlockData implements Serializable {
private byte[] transactionsSignature;
private Integer height;
private long timestamp;
private byte[] generatorPublicKey;
private byte[] generatorSignature;
private byte[] minterPublicKey;
private byte[] minterSignature;
private int atCount;
private BigDecimal atFees;
private byte[] encodedOnlineAccounts;
@ -43,7 +43,7 @@ public class BlockData implements Serializable {
}
public BlockData(int version, byte[] reference, int transactionCount, BigDecimal totalFees, byte[] transactionsSignature, Integer height, long timestamp,
byte[] generatorPublicKey, byte[] generatorSignature, int atCount, BigDecimal atFees,
byte[] minterPublicKey, byte[] minterSignature, int atCount, BigDecimal atFees,
byte[] encodedOnlineAccounts, int onlineAccountsCount, Long onlineAccountsTimestamp, byte[] onlineAccountsSignatures) {
this.version = version;
this.reference = reference;
@ -52,8 +52,8 @@ public class BlockData implements Serializable {
this.transactionsSignature = transactionsSignature;
this.height = height;
this.timestamp = timestamp;
this.generatorPublicKey = generatorPublicKey;
this.generatorSignature = generatorSignature;
this.minterPublicKey = minterPublicKey;
this.minterSignature = minterSignature;
this.atCount = atCount;
this.atFees = atFees;
this.encodedOnlineAccounts = encodedOnlineAccounts;
@ -61,15 +61,15 @@ public class BlockData implements Serializable {
this.onlineAccountsTimestamp = onlineAccountsTimestamp;
this.onlineAccountsSignatures = onlineAccountsSignatures;
if (this.generatorSignature != null && this.transactionsSignature != null)
this.signature = Bytes.concat(this.generatorSignature, this.transactionsSignature);
if (this.minterSignature != null && this.transactionsSignature != null)
this.signature = Bytes.concat(this.minterSignature, this.transactionsSignature);
else
this.signature = null;
}
public BlockData(int version, byte[] reference, int transactionCount, BigDecimal totalFees, byte[] transactionsSignature, Integer height, long timestamp,
byte[] generatorPublicKey, byte[] generatorSignature, int atCount, BigDecimal atFees) {
this(version, reference, transactionCount, totalFees, transactionsSignature, height, timestamp, generatorPublicKey, generatorSignature, atCount, atFees,
byte[] minterPublicKey, byte[] minterSignature, int atCount, BigDecimal atFees) {
this(version, reference, transactionCount, totalFees, transactionsSignature, height, timestamp, minterPublicKey, minterSignature, atCount, atFees,
null, 0, null, null);
}
@ -131,16 +131,16 @@ public class BlockData implements Serializable {
return this.timestamp;
}
public byte[] getGeneratorPublicKey() {
return this.generatorPublicKey;
public byte[] getMinterPublicKey() {
return this.minterPublicKey;
}
public byte[] getGeneratorSignature() {
return this.generatorSignature;
public byte[] getMinterSignature() {
return this.minterSignature;
}
public void setGeneratorSignature(byte[] generatorSignature) {
this.generatorSignature = generatorSignature;
public void setMinterSignature(byte[] minterSignature) {
this.minterSignature = minterSignature;
}
public int getATCount() {
@ -181,9 +181,9 @@ public class BlockData implements Serializable {
// JAXB special
@XmlElement(name = "generatorAddress")
protected String getGeneratorAddress() {
return Crypto.toAddress(this.generatorPublicKey);
@XmlElement(name = "minterAddress")
protected String getMinterAddress() {
return Crypto.toAddress(this.minterPublicKey);
}
}

View File

@ -7,21 +7,21 @@ public class BlockSummaryData {
// Properties
private int height;
private byte[] signature;
private byte[] generatorPublicKey;
private byte[] minterPublicKey;
private int onlineAccountsCount;
// Constructors
public BlockSummaryData(int height, byte[] signature, byte[] generatorPublicKey, int onlineAccountsCount) {
public BlockSummaryData(int height, byte[] signature, byte[] minterPublicKey, int onlineAccountsCount) {
this.height = height;
this.signature = signature;
this.generatorPublicKey = generatorPublicKey;
this.minterPublicKey = minterPublicKey;
this.onlineAccountsCount = onlineAccountsCount;
}
public BlockSummaryData(BlockData blockData) {
this.height = blockData.getHeight();
this.signature = blockData.getSignature();
this.generatorPublicKey = blockData.getGeneratorPublicKey();
this.minterPublicKey = blockData.getMinterPublicKey();
byte[] encodedOnlineAccounts = blockData.getEncodedOnlineAccounts();
if (encodedOnlineAccounts != null) {
@ -41,8 +41,8 @@ public class BlockSummaryData {
return this.signature;
}
public byte[] getGeneratorPublicKey() {
return this.generatorPublicKey;
public byte[] getMinterPublicKey() {
return this.minterPublicKey;
}
public int getOnlineAccountsCount() {

View File

@ -8,14 +8,14 @@ public class PeerChainTipData {
private byte[] lastBlockSignature;
/** Latest block timestamp as reported by peer. */
private Long lastBlockTimestamp;
/** Latest block generator public key as reported by peer. */
private byte[] lastBlockGenerator;
/** Latest block minter public key as reported by peer. */
private byte[] lastBlockMinter;
public PeerChainTipData(Integer lastHeight, byte[] lastBlockSignature, Long lastBlockTimestamp, byte[] lastBlockGenerator) {
public PeerChainTipData(Integer lastHeight, byte[] lastBlockSignature, Long lastBlockTimestamp, byte[] lastBlockMinter) {
this.lastHeight = lastHeight;
this.lastBlockSignature = lastBlockSignature;
this.lastBlockTimestamp = lastBlockTimestamp;
this.lastBlockGenerator = lastBlockGenerator;
this.lastBlockMinter = lastBlockMinter;
}
public Integer getLastHeight() {
@ -30,8 +30,8 @@ public class PeerChainTipData {
return this.lastBlockTimestamp;
}
public byte[] getLastBlockGenerator() {
return this.lastBlockGenerator;
public byte[] getLastBlockMinter() {
return this.lastBlockMinter;
}
}

View File

@ -1,51 +0,0 @@
package org.qora.data.transaction;
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(BaseTransactionData baseTransactionData, String target) {
super(TransactionType.ENABLE_FORGING, baseTransactionData);
this.target = target;
}
// Getters / setters
public String getTarget() {
return this.target;
}
// Re-expose to JAXB
@XmlElement(name = "creatorPublicKey")
@Schema(name = "creatorPublicKey", description = "creator's public key", example = "2tiMr5LTpaWCgbRvkPK8TFd7k63DyHJMMFFsz9uBf1ZP")
public byte[] getEnableForgingCreatorPublicKey() {
return super.getCreatorPublicKey();
}
@XmlElement(name = "creatorPublicKey")
@Schema(name = "creatorPublicKey", description = "creator's public key", example = "2tiMr5LTpaWCgbRvkPK8TFd7k63DyHJMMFFsz9uBf1ZP")
public void setEnableForgingCreatorPublicKey(byte[] creatorPublicKey) {
super.setCreatorPublicKey(creatorPublicKey);
}
}

View File

@ -1,92 +0,0 @@
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.eclipse.persistence.oxm.annotations.XmlDiscriminatorValue;
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})
// JAXB: use this subclass if XmlDiscriminatorNode matches XmlDiscriminatorValue below:
@XmlDiscriminatorValue("PROXY_FORGING")
public class ProxyForgingTransactionData extends TransactionData {
@Schema(example = "forger_public_key")
private byte[] forgerPublicKey;
private String recipient;
@Schema(example = "proxy_public_key")
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;
}
/** From repository */
public ProxyForgingTransactionData(BaseTransactionData baseTransactionData,
String recipient, byte[] proxyPublicKey, BigDecimal share, BigDecimal previousShare) {
super(TransactionType.PROXY_FORGING, baseTransactionData);
this.forgerPublicKey = baseTransactionData.creatorPublicKey;
this.recipient = recipient;
this.proxyPublicKey = proxyPublicKey;
this.share = share;
this.previousShare = previousShare;
}
/** From network/API */
public ProxyForgingTransactionData(BaseTransactionData baseTransactionData,
String recipient, byte[] proxyPublicKey, BigDecimal share) {
this(baseTransactionData, recipient, proxyPublicKey, share, null);
}
// 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;
}
}

View File

@ -0,0 +1,92 @@
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.eclipse.persistence.oxm.annotations.XmlDiscriminatorValue;
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})
// JAXB: use this subclass if XmlDiscriminatorNode matches XmlDiscriminatorValue below:
@XmlDiscriminatorValue("REWARD_SHARE")
public class RewardShareTransactionData extends TransactionData {
@Schema(example = "minter_public_key")
private byte[] minterPublicKey;
private String recipient;
@Schema(example = "reward_share_public_key")
private byte[] rewardSharePublicKey;
private BigDecimal sharePercent;
// No need to ever expose this via API
@XmlTransient
@Schema(hidden = true)
private BigDecimal previousSharePercent;
// Constructors
// For JAXB
protected RewardShareTransactionData() {
super(TransactionType.REWARD_SHARE);
}
public void afterUnmarshal(Unmarshaller u, Object parent) {
this.creatorPublicKey = this.minterPublicKey;
}
/** From repository */
public RewardShareTransactionData(BaseTransactionData baseTransactionData,
String recipient, byte[] rewardSharePublicKey, BigDecimal sharePercent, BigDecimal previousSharePercent) {
super(TransactionType.REWARD_SHARE, baseTransactionData);
this.minterPublicKey = baseTransactionData.creatorPublicKey;
this.recipient = recipient;
this.rewardSharePublicKey = rewardSharePublicKey;
this.sharePercent = sharePercent;
this.previousSharePercent = previousSharePercent;
}
/** From network/API */
public RewardShareTransactionData(BaseTransactionData baseTransactionData,
String recipient, byte[] rewardSharePublicKey, BigDecimal sharePercent) {
this(baseTransactionData, recipient, rewardSharePublicKey, sharePercent, null);
}
// Getters / setters
public byte[] getMinterPublicKey() {
return this.minterPublicKey;
}
public String getRecipient() {
return this.recipient;
}
public byte[] getRewardSharePublicKey() {
return this.rewardSharePublicKey;
}
public BigDecimal getSharePercent() {
return this.sharePercent;
}
public BigDecimal getPreviousSharePercent() {
return this.previousSharePercent;
}
public void setPreviousSharePercent(BigDecimal previousSharePercent) {
this.previousSharePercent = previousSharePercent;
}
}

View File

@ -39,7 +39,7 @@ import io.swagger.v3.oas.annotations.media.Schema.AccessMode;
JoinGroupTransactionData.class, LeaveGroupTransactionData.class,
GroupApprovalTransactionData.class, SetGroupTransactionData.class,
UpdateAssetTransactionData.class,
AccountFlagsTransactionData.class, EnableForgingTransactionData.class, ProxyForgingTransactionData.class,
AccountFlagsTransactionData.class, RewardShareTransactionData.class,
AccountLevelTransactionData.class
})
//All properties to be converted to JSON via JAXB

View File

@ -1,182 +0,0 @@
package org.qora;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
public class mintsim {
private static final int NUMBER_BLOCKS = 5_000_000;
private static final double GRANT_PROB = 0.001;
private static final int BLOCK_HISTORY = 0;
private static final int WEIGHTING = 2;
private static final int TOP_MINTERS_SIZE = 200;
private static final Random random = new Random();
private static List<TierInfo> tiers = new ArrayList<>();
private static List<Account> accounts = new ArrayList<>();
private static List<Integer> blockMinters = new ArrayList<>();
private static List<Integer> accountsWithGrants = new ArrayList<>();
public static class TierInfo {
public final int maxAccounts;
public final int minBlocks;
public int numberAccounts;
public TierInfo(int maxAccounts, int minBlocks) {
this.maxAccounts = maxAccounts;
this.minBlocks = minBlocks;
this.numberAccounts = 0;
}
}
public static class Account {
public final int tierIndex;
public int blocksForged;
public int rightsGranted;
public Account(int tierIndex) {
this.tierIndex = tierIndex;
this.blocksForged = 0;
this.rightsGranted = 0;
}
}
public static void main(String[] args) {
if (args.length < 2 || (args.length % 2) != 0) {
System.err.println("usage: mintsim <tier1-min-blocks> <number-tier1-accounts> [<tier2-min-blocks> <max-tier2-per-tier1> [...]]");
System.exit(1);
}
try {
int argIndex = 0;
do {
int minBlocks = Integer.parseInt(args[argIndex++]);
int maxAccounts = Integer.parseInt(args[argIndex++]);
tiers.add(new TierInfo(maxAccounts, minBlocks));
} while (argIndex < args.length);
} catch (NumberFormatException e) {
System.err.println("Can't parse number?");
System.exit(2);
}
// Print summary
System.out.println(String.format("Number of tiers: %d", tiers.size()));
for (int i = 0; i < tiers.size(); ++i) {
TierInfo tier = tiers.get(i);
System.out.println(String.format("Tier %d:", i));
System.out.println(String.format("\tMinimum forged blocks to grant right: %d", tier.minBlocks));
System.out.println(String.format("\tMaximum tier%d grants: %d", i + 1, tier.maxAccounts));
}
TierInfo initialTier = tiers.get(0);
int totalAccounts = initialTier.maxAccounts;
for (int i = 1; i < tiers.size(); ++i)
totalAccounts *= 1 + tiers.get(i).maxAccounts;
System.out.println(String.format("Total accounts: %d", totalAccounts));
// Create initial accounts
initialTier.numberAccounts = initialTier.maxAccounts;
for (int i = 0; i < initialTier.maxAccounts; ++i)
accounts.add(new Account(0));
for (int height = 1; height < NUMBER_BLOCKS; ++height) {
int minterId = pickMinterId();
Account minter = accounts.get(minterId);
++minter.blocksForged;
blockMinters.add(minterId);
if (minter.tierIndex < tiers.size() - 1) {
TierInfo nextTier = tiers.get(minter.tierIndex + 1);
// Minter just reached threshold to grant rights
if (minter.blocksForged == nextTier.minBlocks)
accountsWithGrants.add(minterId);
}
List<Integer> accountsToRemove = new ArrayList<>();
// Do any account with spare grants want to grant?
for (int granterId : accountsWithGrants) {
if (random.nextDouble() >= GRANT_PROB)
continue;
Account granter = accounts.get(granterId);
TierInfo nextTier = tiers.get(granter.tierIndex + 1);
accounts.add(new Account(granter.tierIndex + 1));
++nextTier.numberAccounts;
++granter.rightsGranted;
if (granter.rightsGranted == nextTier.maxAccounts)
accountsToRemove.add(granterId);
}
// Remove granters that have used their allowance
accountsWithGrants.removeAll(accountsToRemove);
if (height % 100000 == 0) {
System.out.println(String.format("Summary after block %d:", height));
for (int i = 0; i < tiers.size(); ++i)
System.out.println(String.format("\tTier %d: number of accounts: %d", i, tiers.get(i).numberAccounts));
}
}
// Top minters
List<Integer> topMinters = new ArrayList<>();
for (int i = 0; i < accounts.size(); ++i) {
topMinters.add(i);
topMinters.sort((a, b) -> Integer.compare(accounts.get(b).blocksForged, accounts.get(a).blocksForged));
if (topMinters.size() > TOP_MINTERS_SIZE)
topMinters.remove(TOP_MINTERS_SIZE);
}
System.out.println(String.format("Top %d minters:", TOP_MINTERS_SIZE));
for (int i = 0; i < topMinters.size(); ++i) {
int topMinterId = topMinters.get(i);
Account topMinter = accounts.get(topMinterId);
System.out.println(String.format("\tAccount %d (tier %d) has minted %d blocks", topMinterId, topMinter.tierIndex, topMinter.blocksForged));
}
for (int i = 0; i < tiers.size(); ++i)
System.out.println(String.format("Tier %d: number of accounts: %d", i, tiers.get(i).numberAccounts));
}
private static int pickMinterId() {
// There might not be enough block history yet...
final int blockHistory = Math.min(BLOCK_HISTORY, blockMinters.size());
// Weighting (W)
// An account that HASN'T forged in the last X blocks has 1 standard chance to forge
// but an account that HAS forged Y in the last X blocks has 1 + (Y / X) * W chances to forge
// e.g. forged 25 in last 100 blocks, with weighting 8, gives (25 / 100) * 8 = 2 extra chances
// So in X blocks there will be X * W extra chances.
// We pick winning number from (number-of-accounts + X * W) chances
int totalChances = accounts.size() + blockHistory * WEIGHTING;
int winningNumber = random.nextInt(totalChances);
// Simple case if winning number is less than number of accounts,
// otherwise we need to handle extra chances for accounts that have forged in last X blocks.
if (winningNumber < accounts.size())
return winningNumber;
// Handling extra chances
// We can work out which block in last X blocks as each block is worth W chances
int blockOffset = (winningNumber - accounts.size()) / WEIGHTING;
int blockIndex = blockMinters.size() - 1 - blockOffset;
return blockMinters.get(blockIndex);
}
}

View File

@ -1006,7 +1006,7 @@ public class Network {
}
// HEIGHT_V2 contains way more useful info
return new HeightV2Message(blockData.getHeight(), blockData.getSignature(), blockData.getTimestamp(), blockData.getGeneratorPublicKey());
return new HeightV2Message(blockData.getHeight(), blockData.getSignature(), blockData.getTimestamp(), blockData.getMinterPublicKey());
}
public Message buildNewTransactionMessage(Peer peer, TransactionData transactionData) {

View File

@ -46,12 +46,12 @@ public class BlockSummariesMessage extends Message {
byte[] signature = new byte[BlockTransformer.BLOCK_SIGNATURE_LENGTH];
bytes.get(signature);
byte[] generatorPublicKey = new byte[Transformer.PUBLIC_KEY_LENGTH];
bytes.get(generatorPublicKey);
byte[] minterPublicKey = new byte[Transformer.PUBLIC_KEY_LENGTH];
bytes.get(minterPublicKey);
int onlineAccountsCount = bytes.getInt();
BlockSummaryData blockSummary = new BlockSummaryData(height, signature, generatorPublicKey, onlineAccountsCount);
BlockSummaryData blockSummary = new BlockSummaryData(height, signature, minterPublicKey, onlineAccountsCount);
blockSummaries.add(blockSummary);
}
@ -68,7 +68,7 @@ public class BlockSummariesMessage extends Message {
for (BlockSummaryData blockSummary : this.blockSummaries) {
bytes.write(Ints.toByteArray(blockSummary.getHeight()));
bytes.write(blockSummary.getSignature());
bytes.write(blockSummary.getGeneratorPublicKey());
bytes.write(blockSummary.getMinterPublicKey());
bytes.write(Ints.toByteArray(blockSummary.getOnlineAccountsCount()));
}

View File

@ -16,19 +16,19 @@ public class HeightV2Message extends Message {
private int height;
private byte[] signature;
private long timestamp;
private byte[] generator;
private byte[] minterPublicKey;
public HeightV2Message(int height, byte[] signature, long timestamp, byte[] generator) {
this(-1, height, signature, timestamp, generator);
public HeightV2Message(int height, byte[] signature, long timestamp, byte[] minterPublicKey) {
this(-1, height, signature, timestamp, minterPublicKey);
}
private HeightV2Message(int id, int height, byte[] signature, long timestamp, byte[] generator) {
private HeightV2Message(int id, int height, byte[] signature, long timestamp, byte[] minterPublicKey) {
super(id, MessageType.HEIGHT_V2);
this.height = height;
this.signature = signature;
this.timestamp = timestamp;
this.generator = generator;
this.minterPublicKey = minterPublicKey;
}
public int getHeight() {
@ -43,8 +43,8 @@ public class HeightV2Message extends Message {
return this.timestamp;
}
public byte[] getGenerator() {
return this.generator;
public byte[] getMinterPublicKey() {
return this.minterPublicKey;
}
public static Message fromByteBuffer(int id, ByteBuffer bytes) throws UnsupportedEncodingException {
@ -55,10 +55,10 @@ public class HeightV2Message extends Message {
long timestamp = bytes.getLong();
byte[] generator = new byte[Transformer.PUBLIC_KEY_LENGTH];
bytes.get(generator);
byte[] minterPublicKey = new byte[Transformer.PUBLIC_KEY_LENGTH];
bytes.get(minterPublicKey);
return new HeightV2Message(id, height, signature, timestamp, generator);
return new HeightV2Message(id, height, signature, timestamp, minterPublicKey);
}
@Override
@ -72,7 +72,7 @@ public class HeightV2Message extends Message {
bytes.write(Longs.toByteArray(this.timestamp));
bytes.write(this.generator);
bytes.write(this.minterPublicKey);
return bytes.toByteArray();
} catch (IOException e) {

View File

@ -4,8 +4,8 @@ import java.util.List;
import org.qora.data.account.AccountBalanceData;
import org.qora.data.account.AccountData;
import org.qora.data.account.ForgingAccountData;
import org.qora.data.account.ProxyForgerData;
import org.qora.data.account.MintingAccountData;
import org.qora.data.account.RewardShareData;
public interface AccountRepository {
@ -29,9 +29,6 @@ public interface AccountRepository {
/** Returns whether account exists. */
public boolean accountExists(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>
@ -75,11 +72,11 @@ public interface AccountRepository {
public void setInitialLevel(AccountData accountData) throws DataException;
/**
* Saves account's generated block count and public key if present, in repository.
* Saves account's minted block count and public key if present, in repository.
* <p>
* Note: ignores other fields like last reference, default groupID.
*/
public void setBlocksGenerated(AccountData accountData) throws DataException;
public void setMintedBlockCount(AccountData accountData) throws DataException;
/** Delete account from repository. */
public void delete(String address) throws DataException;
@ -100,44 +97,45 @@ public interface AccountRepository {
public void delete(String address, long assetId) throws DataException;
// Proxy forging
// Reward-shares
public ProxyForgerData getProxyForgeData(byte[] forgerPublicKey, String recipient) throws DataException;
public RewardShareData getRewardShare(byte[] mintingAccountPublicKey, String recipientAccount) throws DataException;
public ProxyForgerData getProxyForgeData(byte[] proxyPublicKey) throws DataException;
public RewardShareData getRewardShare(byte[] rewardSharePublicKey) throws DataException;
public boolean isProxyPublicKey(byte[] publicKey) throws DataException;
public boolean isRewardSharePublicKey(byte[] publicKey) throws DataException;
public int countProxyAccounts(byte[] forgerPublicKey) throws DataException;
/** Returns number of active reward-shares involving passed public key as the minting account only. */
public int countRewardShares(byte[] mintingAccountPublicKey) throws DataException;
public List<ProxyForgerData> getProxyAccounts() throws DataException;
public List<RewardShareData> getRewardShares() throws DataException;
public List<ProxyForgerData> findProxyAccounts(List<String> recipients, List<String> forgers, List<String> involvedAddresses, Integer limit, Integer offset, Boolean reverse) throws DataException;
public List<RewardShareData> findRewardShares(List<String> mintingAccounts, List<String> recipientAccounts, List<String> involvedAddresses, Integer limit, Integer offset, Boolean reverse) throws DataException;
/**
* Returns index in list of proxy accounts (sorted by public key).
* Returns index in list of reward-shares (sorted by reward-share public key).
* <p>
* @return index (from 0) or null if publicKey not found in repository.
*/
public Integer getProxyAccountIndex(byte[] publicKey) throws DataException;
public Integer getRewardShareIndex(byte[] rewardSharePublicKey) throws DataException;
/**
* Returns proxy forger data using index into list of proxy accounts.
* Returns reward-share data using index into list of reward-shares (sorted by reward-share public key).
*/
public ProxyForgerData getProxyAccountByIndex(int index) throws DataException;
public RewardShareData getRewardShareByIndex(int index) throws DataException;
public void save(ProxyForgerData proxyForgerData) throws DataException;
public void save(RewardShareData rewardShareData) throws DataException;
/** Delete proxy forging relationship from repository using passed forger's public key and recipient's address. */
public void delete(byte[] forgerPublickey, String recipient) throws DataException;
/** Delete reward-share from repository using passed minting account's public key and recipient's address. */
public void delete(byte[] mintingAccountPublickey, String recipient) throws DataException;
// Forging accounts used by BlockGenerator
// Minting accounts used by BlockMinter, potentially includes reward-shares
public List<ForgingAccountData> getForgingAccounts() throws DataException;
public List<MintingAccountData> getMintingAccounts() throws DataException;
public void save(ForgingAccountData forgingAccountData) throws DataException;
public void save(MintingAccountData mintingAccountData) throws DataException;
/** Delete forging account info, used by BlockGenerator, from repository using passed private key. */
public int delete(byte[] forgingAccountSeed) throws DataException;
/** Delete minting account info, used by BlockMinter, from repository using passed private key. */
public int delete(byte[] mintingAccountPrivateKey) throws DataException;
}

View File

@ -2,7 +2,7 @@ package org.qora.repository;
import java.util.List;
import org.qora.api.model.BlockForgerSummary;
import org.qora.api.model.BlockMinterSummary;
import org.qora.data.block.BlockData;
import org.qora.data.block.BlockSummaryData;
import org.qora.data.block.BlockTransactionData;
@ -100,23 +100,23 @@ public interface BlockRepository {
}
/**
* Returns number of blocks forged by account with given public key, including proxy-forged blocks.
* Returns number of blocks minted by account/reward-share with given public key.
*
* @param publicKey
* @return number of blocks
* @throws DataException
*/
public int countForgedBlocks(byte[] publicKey) throws DataException;
public int countMintedBlocks(byte[] publicKey) throws DataException;
/**
* Returns summaries of block forgers, optionally limited to passed addresses.
* Returns summaries of block minters, optionally limited to passed addresses.
*/
public List<BlockForgerSummary> getBlockForgers(List<String> addresses, Integer limit, Integer offset, Boolean reverse) throws DataException;
public List<BlockMinterSummary> getBlockMinters(List<String> addresses, Integer limit, Integer offset, Boolean reverse) throws DataException;
/**
* Returns blocks with passed generator public key.
* Returns blocks with passed minter public key.
*/
public List<BlockData> getBlocksWithGenerator(byte[] generatorPublicKey, Integer limit, Integer offset, Boolean reverse) throws DataException;
public List<BlockData> getBlocksByMinter(byte[] minterPublicKey, Integer limit, Integer offset, Boolean reverse) throws DataException;
/**
* Returns blocks within height range.

View File

@ -9,8 +9,8 @@ import java.util.List;
import org.qora.data.account.AccountBalanceData;
import org.qora.data.account.AccountData;
import org.qora.data.account.ForgingAccountData;
import org.qora.data.account.ProxyForgerData;
import org.qora.data.account.MintingAccountData;
import org.qora.data.account.RewardShareData;
import org.qora.repository.AccountRepository;
import org.qora.repository.DataException;
@ -26,7 +26,7 @@ public class HSQLDBAccountRepository implements AccountRepository {
@Override
public AccountData getAccount(String address) throws DataException {
String sql = "SELECT reference, public_key, default_group_id, flags, initial_level, level, blocks_generated FROM Accounts WHERE account = ?";
String sql = "SELECT reference, public_key, default_group_id, flags, initial_level, level, blocks_minted FROM Accounts WHERE account = ?";
try (ResultSet resultSet = this.repository.checkedExecute(sql, address)) {
if (resultSet == null)
@ -38,9 +38,9 @@ public class HSQLDBAccountRepository implements AccountRepository {
int flags = resultSet.getInt(4);
int initialLevel = resultSet.getInt(5);
int level = resultSet.getInt(6);
int blocksGenerated = resultSet.getInt(7);
int blocksMinted = resultSet.getInt(7);
return new AccountData(address, reference, publicKey, defaultGroupId, flags, initialLevel, level, blocksGenerated);
return new AccountData(address, reference, publicKey, defaultGroupId, flags, initialLevel, level, blocksMinted);
} catch (SQLException e) {
throw new DataException("Unable to fetch account info from repository", e);
}
@ -112,15 +112,6 @@ 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 {
byte[] publicKey = accountData.getPublicKey();
@ -236,10 +227,10 @@ public class HSQLDBAccountRepository implements AccountRepository {
}
@Override
public void setBlocksGenerated(AccountData accountData) throws DataException {
public void setMintedBlockCount(AccountData accountData) throws DataException {
HSQLDBSaver saveHelper = new HSQLDBSaver("Accounts");
saveHelper.bind("account", accountData.getAddress()).bind("blocks_generated", accountData.getBlocksGenerated());
saveHelper.bind("account", accountData.getAddress()).bind("blocks_minted", accountData.getBlocksMinted());
byte[] publicKey = accountData.getPublicKey();
if (publicKey != null)
@ -248,7 +239,7 @@ public class HSQLDBAccountRepository implements AccountRepository {
try {
saveHelper.execute(this.repository);
} catch (SQLException e) {
throw new DataException("Unable to save account's generated block count into repository", e);
throw new DataException("Unable to save account's minted block count into repository", e);
}
}
@ -401,102 +392,102 @@ public class HSQLDBAccountRepository implements AccountRepository {
}
}
// Proxy forging
// Reward-Share
@Override
public ProxyForgerData getProxyForgeData(byte[] forgerPublicKey, String recipient) throws DataException {
String sql = "SELECT proxy_public_key, share FROM ProxyForgers WHERE forger = ? AND recipient = ?";
public RewardShareData getRewardShare(byte[] minterPublicKey, String recipient) throws DataException {
String sql = "SELECT reward_share_public_key, share_percent FROM RewardShares WHERE minter_public_key = ? AND recipient = ?";
try (ResultSet resultSet = this.repository.checkedExecute(sql, forgerPublicKey, recipient)) {
try (ResultSet resultSet = this.repository.checkedExecute(sql, minterPublicKey, recipient)) {
if (resultSet == null)
return null;
byte[] proxyPublicKey = resultSet.getBytes(1);
BigDecimal share = resultSet.getBigDecimal(2);
byte[] rewardSharePublicKey = resultSet.getBytes(1);
BigDecimal sharePercent = resultSet.getBigDecimal(2);
return new ProxyForgerData(forgerPublicKey, recipient, proxyPublicKey, share);
return new RewardShareData(minterPublicKey, recipient, rewardSharePublicKey, sharePercent);
} catch (SQLException e) {
throw new DataException("Unable to fetch proxy forge info from repository", e);
throw new DataException("Unable to fetch reward-share info from repository", e);
}
}
@Override
public ProxyForgerData getProxyForgeData(byte[] proxyPublicKey) throws DataException {
String sql = "SELECT forger, recipient, share FROM ProxyForgers WHERE proxy_public_key = ?";
public RewardShareData getRewardShare(byte[] rewardSharePublicKey) throws DataException {
String sql = "SELECT minter_public_key, recipient, share_percent FROM RewardShares WHERE reward_share_public_key = ?";
try (ResultSet resultSet = this.repository.checkedExecute(sql, proxyPublicKey)) {
try (ResultSet resultSet = this.repository.checkedExecute(sql, rewardSharePublicKey)) {
if (resultSet == null)
return null;
byte[] forgerPublicKey = resultSet.getBytes(1);
byte[] minterPublicKey = resultSet.getBytes(1);
String recipient = resultSet.getString(2);
BigDecimal share = resultSet.getBigDecimal(3);
BigDecimal sharePercent = resultSet.getBigDecimal(3);
return new ProxyForgerData(forgerPublicKey, recipient, proxyPublicKey, share);
return new RewardShareData(minterPublicKey, recipient, rewardSharePublicKey, sharePercent);
} catch (SQLException e) {
throw new DataException("Unable to fetch proxy forge info from repository", e);
throw new DataException("Unable to fetch reward-share info from repository", e);
}
}
@Override
public boolean isProxyPublicKey(byte[] publicKey) throws DataException {
public boolean isRewardSharePublicKey(byte[] publicKey) throws DataException {
try {
return this.repository.exists("ProxyForgers", "proxy_public_key = ?", publicKey);
return this.repository.exists("RewardShares", "reward_share_public_key = ?", publicKey);
} catch (SQLException e) {
throw new DataException("Unable to check for proxy public key in repository", e);
throw new DataException("Unable to check for reward-share public key in repository", e);
}
}
@Override
public int countProxyAccounts(byte[] forgerPublicKey) throws DataException {
String sql = "SELECT COUNT(*) FROM ProxyForgers WHERE forger = ?";
public int countRewardShares(byte[] minterPublicKey) throws DataException {
String sql = "SELECT COUNT(*) FROM RewardShares WHERE minter_public_key = ?";
try (ResultSet resultSet = this.repository.checkedExecute(sql, forgerPublicKey)) {
try (ResultSet resultSet = this.repository.checkedExecute(sql, minterPublicKey)) {
return resultSet.getInt(1);
} catch (SQLException e) {
throw new DataException("Unable to count proxy forging relationships in repository", e);
throw new DataException("Unable to count reward-shares in repository", e);
}
}
@Override
public List<ProxyForgerData> getProxyAccounts() throws DataException {
String sql = "SELECT forger, recipient, share, proxy_public_key FROM ProxyForgers";
public List<RewardShareData> getRewardShares() throws DataException {
String sql = "SELECT minter_public_key, recipient, share_percent, reward_share_public_key FROM RewardShares";
List<ProxyForgerData> proxyAccounts = new ArrayList<>();
List<RewardShareData> rewardShares = new ArrayList<>();
try (ResultSet resultSet = this.repository.checkedExecute(sql)) {
if (resultSet == null)
return proxyAccounts;
return rewardShares;
do {
byte[] forgerPublicKey = resultSet.getBytes(1);
byte[] minterPublicKey = resultSet.getBytes(1);
String recipient = resultSet.getString(2);
BigDecimal share = resultSet.getBigDecimal(3);
byte[] proxyPublicKey = resultSet.getBytes(4);
BigDecimal sharePercent = resultSet.getBigDecimal(3);
byte[] rewardSharePublicKey = resultSet.getBytes(4);
proxyAccounts.add(new ProxyForgerData(forgerPublicKey, recipient, proxyPublicKey, share));
rewardShares.add(new RewardShareData(minterPublicKey, recipient, rewardSharePublicKey, sharePercent));
} while (resultSet.next());
return proxyAccounts;
return rewardShares;
} catch (SQLException e) {
throw new DataException("Unable to fetch proxy forge accounts from repository", e);
throw new DataException("Unable to fetch reward-shares from repository", e);
}
}
@Override
public List<ProxyForgerData> findProxyAccounts(List<String> recipients, List<String> forgers, List<String> involvedAddresses,
public List<RewardShareData> findRewardShares(List<String> minters, List<String> recipients, List<String> involvedAddresses,
Integer limit, Integer offset, Boolean reverse) throws DataException {
StringBuilder sql = new StringBuilder(1024);
sql.append("SELECT DISTINCT forger, recipient, share, proxy_public_key FROM ProxyForgers ");
sql.append("SELECT DISTINCT minter_public_key, recipient, share_percent, reward_share_public_key FROM RewardShares ");
List<Object> args = new ArrayList<>();
final boolean hasRecipients = recipients != null && !recipients.isEmpty();
final boolean hasForgers = forgers != null && !forgers.isEmpty();
final boolean hasMinters = minters != null && !minters.isEmpty();
final boolean hasInvolved = involvedAddresses != null && !involvedAddresses.isEmpty();
if (hasForgers || hasInvolved)
sql.append("JOIN Accounts ON Accounts.public_key = ProxyForgers.forger ");
if (hasMinters || hasInvolved)
sql.append("JOIN Accounts ON Accounts.public_key = RewardShares.minter_public_key ");
if (hasRecipients) {
sql.append("JOIN (VALUES ");
@ -509,29 +500,28 @@ public class HSQLDBAccountRepository implements AccountRepository {
sql.append("(?)");
}
sql.append(") AS Recipients (address) ON ProxyForgers.recipient = Recipients.address ");
sql.append(") AS Recipients (address) ON RewardShares.recipient = Recipients.address ");
args.addAll(recipients);
}
if (hasForgers) {
if (hasMinters) {
sql.append("JOIN (VALUES ");
final int forgersSize = forgers.size();
for (int fi = 0; fi < forgersSize; ++fi) {
final int mintersSize = minters.size();
for (int fi = 0; fi < mintersSize; ++fi) {
if (fi != 0)
sql.append(", ");
sql.append("(?)");
}
sql.append(") AS Forgers (address) ON Accounts.account = Forgers.address ");
args.addAll(forgers);
sql.append(") AS Minters (address) ON Accounts.account = Minters.address ");
args.addAll(minters);
}
if (hasInvolved) {
sql.append("JOIN (VALUES ");
final int involvedAddressesSize = involvedAddresses.size();
for (int iai = 0; iai < involvedAddressesSize; ++iai) {
if (iai != 0)
@ -540,40 +530,40 @@ public class HSQLDBAccountRepository implements AccountRepository {
sql.append("(?)");
}
sql.append(") AS Involved (address) ON Involved.address IN (ProxyForgers.recipient, Accounts.account) ");
sql.append(") AS Involved (address) ON Involved.address IN (RewardShares.recipient, Accounts.account) ");
args.addAll(involvedAddresses);
}
sql.append("ORDER BY recipient, share");
sql.append("ORDER BY recipient, share_percent");
if (reverse != null && reverse)
sql.append(" DESC");
HSQLDBRepository.limitOffsetSql(sql, limit, offset);
List<ProxyForgerData> proxyAccounts = new ArrayList<>();
List<RewardShareData> rewardShares = new ArrayList<>();
try (ResultSet resultSet = this.repository.checkedExecute(sql.toString(), args.toArray())) {
if (resultSet == null)
return proxyAccounts;
return rewardShares;
do {
byte[] forgerPublicKey = resultSet.getBytes(1);
byte[] minterPublicKey = resultSet.getBytes(1);
String recipient = resultSet.getString(2);
BigDecimal share = resultSet.getBigDecimal(3);
byte[] proxyPublicKey = resultSet.getBytes(4);
BigDecimal sharePercent = resultSet.getBigDecimal(3);
byte[] rewardSharePublicKey = resultSet.getBytes(4);
proxyAccounts.add(new ProxyForgerData(forgerPublicKey, recipient, proxyPublicKey, share));
rewardShares.add(new RewardShareData(minterPublicKey, recipient, rewardSharePublicKey, sharePercent));
} while (resultSet.next());
return proxyAccounts;
return rewardShares;
} catch (SQLException e) {
throw new DataException("Unable to find proxy forge accounts in repository", e);
throw new DataException("Unable to find reward-shares in repository", e);
}
}
@Override
public Integer getProxyAccountIndex(byte[] publicKey) throws DataException {
String sql = "SELECT COUNT(*) FROM ProxyForgers WHERE proxy_public_key < ?";
public Integer getRewardShareIndex(byte[] publicKey) throws DataException {
String sql = "SELECT COUNT(*) FROM RewardShares WHERE reward_share_public_key < ?";
try (ResultSet resultSet = this.repository.checkedExecute(sql, publicKey)) {
if (resultSet == null)
@ -581,92 +571,92 @@ public class HSQLDBAccountRepository implements AccountRepository {
return resultSet.getInt(1);
} catch (SQLException e) {
throw new DataException("Unable to determine account index in repository", e);
throw new DataException("Unable to determine reward-share index in repository", e);
}
}
@Override
public ProxyForgerData getProxyAccountByIndex(int index) throws DataException {
String sql = "SELECT forger, recipient, share, proxy_public_key FROM ProxyForgers "
+ "ORDER BY proxy_public_key ASC "
public RewardShareData getRewardShareByIndex(int index) throws DataException {
String sql = "SELECT minter_public_key, recipient, share_percent, reward_share_public_key FROM RewardShares "
+ "ORDER BY reward_share_public_key ASC "
+ "OFFSET ? LIMIT 1";
try (ResultSet resultSet = this.repository.checkedExecute(sql, index)) {
if (resultSet == null)
return null;
byte[] forgerPublicKey = resultSet.getBytes(1);
byte[] minterPublicKey = resultSet.getBytes(1);
String recipient = resultSet.getString(2);
BigDecimal share = resultSet.getBigDecimal(3);
byte[] proxyPublicKey = resultSet.getBytes(4);
BigDecimal sharePercent = resultSet.getBigDecimal(3);
byte[] rewardSharePublicKey = resultSet.getBytes(4);
return new ProxyForgerData(forgerPublicKey, recipient, proxyPublicKey, share);
return new RewardShareData(minterPublicKey, recipient, rewardSharePublicKey, sharePercent);
} catch (SQLException e) {
throw new DataException("Unable to fetch account info from repository", e);
throw new DataException("Unable to fetch reward-share info from repository", e);
}
}
@Override
public void save(ProxyForgerData proxyForgerData) throws DataException {
HSQLDBSaver saveHelper = new HSQLDBSaver("ProxyForgers");
public void save(RewardShareData rewardShareData) throws DataException {
HSQLDBSaver saveHelper = new HSQLDBSaver("RewardShares");
saveHelper.bind("forger", proxyForgerData.getForgerPublicKey()).bind("recipient", proxyForgerData.getRecipient())
.bind("proxy_public_key", proxyForgerData.getProxyPublicKey()).bind("share", proxyForgerData.getShare());
saveHelper.bind("minter_public_key", rewardShareData.getMinterPublicKey()).bind("recipient", rewardShareData.getRecipient())
.bind("reward_share_public_key", rewardShareData.getRewardSharePublicKey()).bind("share_percent", rewardShareData.getSharePercent());
try {
saveHelper.execute(this.repository);
} catch (SQLException e) {
throw new DataException("Unable to save proxy forge info into repository", e);
throw new DataException("Unable to save reward-share info into repository", e);
}
}
@Override
public void delete(byte[] forgerPublickey, String recipient) throws DataException {
public void delete(byte[] minterPublickey, String recipient) throws DataException {
try {
this.repository.delete("ProxyForgers", "forger = ? and recipient = ?", forgerPublickey, recipient);
this.repository.delete("RewardShares", "minter_public_key = ? and recipient = ?", minterPublickey, recipient);
} catch (SQLException e) {
throw new DataException("Unable to delete proxy forge info from repository", e);
throw new DataException("Unable to delete reward-share info from repository", e);
}
}
// Forging accounts used by BlockGenerator
// Minting accounts used by BlockMinter
public List<ForgingAccountData> getForgingAccounts() throws DataException {
List<ForgingAccountData> forgingAccounts = new ArrayList<>();
public List<MintingAccountData> getMintingAccounts() throws DataException {
List<MintingAccountData> mintingAccounts = new ArrayList<>();
try (ResultSet resultSet = this.repository.checkedExecute("SELECT forger_seed FROM ForgingAccounts")) {
try (ResultSet resultSet = this.repository.checkedExecute("SELECT minter_private_key FROM MintingAccounts")) {
if (resultSet == null)
return forgingAccounts;
return mintingAccounts;
do {
byte[] forgerSeed = resultSet.getBytes(1);
byte[] minterPrivateKey = resultSet.getBytes(1);
forgingAccounts.add(new ForgingAccountData(forgerSeed));
mintingAccounts.add(new MintingAccountData(minterPrivateKey));
} while (resultSet.next());
return forgingAccounts;
return mintingAccounts;
} catch (SQLException e) {
throw new DataException("Unable to find forging accounts in repository", e);
throw new DataException("Unable to fetch minting accounts from repository", e);
}
}
public void save(ForgingAccountData forgingAccountData) throws DataException {
HSQLDBSaver saveHelper = new HSQLDBSaver("ForgingAccounts");
public void save(MintingAccountData mintingAccountData) throws DataException {
HSQLDBSaver saveHelper = new HSQLDBSaver("MintingAccounts");
saveHelper.bind("forger_seed", forgingAccountData.getSeed());
saveHelper.bind("minter_private_key", mintingAccountData.getPrivateKey());
try {
saveHelper.execute(this.repository);
} catch (SQLException e) {
throw new DataException("Unable to save forging account into repository", e);
throw new DataException("Unable to save minting account into repository", e);
}
}
public int delete(byte[] forgingAccountSeed) throws DataException {
public int delete(byte[] minterPrivateKey) throws DataException {
try {
return this.repository.delete("ForgingAccounts", "forger_seed = ?", forgingAccountSeed);
return this.repository.delete("MintingAccounts", "minter_private_key = ?", minterPrivateKey);
} catch (SQLException e) {
throw new DataException("Unable to delete forging account from repository", e);
throw new DataException("Unable to delete minting account from repository", e);
}
}

View File

@ -118,9 +118,9 @@ public class HSQLDBAssetRepository implements AssetRepository {
long quantity = resultSet.getLong(5);
boolean isDivisible = resultSet.getBoolean(6);
String data = resultSet.getString(7);
boolean isUnspendable = resultSet.getBoolean(7);
int creationGroupId = resultSet.getInt(8);
byte[] reference = resultSet.getBytes(9);
boolean isUnspendable = resultSet.getBoolean(8);
int creationGroupId = resultSet.getInt(9);
byte[] reference = resultSet.getBytes(10);
assets.add(new AssetData(assetId, owner, assetName, description, quantity, isDivisible, data,
isUnspendable,creationGroupId, reference));

View File

@ -7,7 +7,7 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.qora.api.model.BlockForgerSummary;
import org.qora.api.model.BlockMinterSummary;
import org.qora.data.block.BlockData;
import org.qora.data.block.BlockSummaryData;
import org.qora.data.block.BlockTransactionData;
@ -22,7 +22,7 @@ import static org.qora.repository.hsqldb.HSQLDBRepository.getZonedTimestampMilli
public class HSQLDBBlockRepository implements BlockRepository {
private static final String BLOCK_DB_COLUMNS = "version, reference, transaction_count, total_fees, "
+ "transactions_signature, height, generation, generator, generator_signature, "
+ "transactions_signature, height, minted, minter, minter_signature, "
+ "AT_count, AT_fees, online_accounts, online_accounts_count, online_accounts_timestamp, online_accounts_signatures";
protected HSQLDBRepository repository;
@ -43,8 +43,8 @@ public class HSQLDBBlockRepository implements BlockRepository {
byte[] transactionsSignature = resultSet.getBytes(5);
int height = resultSet.getInt(6);
long timestamp = getZonedTimestampMilli(resultSet, 7);
byte[] generatorPublicKey = resultSet.getBytes(8);
byte[] generatorSignature = resultSet.getBytes(9);
byte[] minterPublicKey = resultSet.getBytes(8);
byte[] minterSignature = resultSet.getBytes(9);
int atCount = resultSet.getInt(10);
BigDecimal atFees = resultSet.getBigDecimal(11);
byte[] encodedOnlineAccounts = resultSet.getBytes(12);
@ -53,7 +53,7 @@ public class HSQLDBBlockRepository implements BlockRepository {
byte[] onlineAccountsSignatures = resultSet.getBytes(15);
return new BlockData(version, reference, transactionCount, totalFees, transactionsSignature, height, timestamp,
generatorPublicKey, generatorSignature, atCount, atFees,
minterPublicKey, minterSignature, atCount, atFees,
encodedOnlineAccounts, onlineAccountsCount, onlineAccountsTimestamp, onlineAccountsSignatures);
} catch (SQLException e) {
throw new DataException("Error extracting data from result set", e);
@ -110,8 +110,8 @@ public class HSQLDBBlockRepository implements BlockRepository {
@Override
public int getHeightFromTimestamp(long timestamp) throws DataException {
// Uses (generation, height) index
try (ResultSet resultSet = this.repository.checkedExecute("SELECT height FROM Blocks WHERE generation <= ? ORDER BY generation DESC LIMIT 1",
// Uses (minted, height) index
try (ResultSet resultSet = this.repository.checkedExecute("SELECT height FROM Blocks WHERE minted <= ? ORDER BY minted DESC LIMIT 1",
toOffsetDateTime(timestamp))) {
if (resultSet == null)
return 0;
@ -174,40 +174,40 @@ public class HSQLDBBlockRepository implements BlockRepository {
}
@Override
public int countForgedBlocks(byte[] publicKey) throws DataException {
String directSql = "SELECT COUNT(*) FROM Blocks WHERE generator = ?";
public int countMintedBlocks(byte[] minterPublicKey) throws DataException {
String directSql = "SELECT COUNT(*) FROM Blocks WHERE minter = ?";
String proxySql = "SELECT COUNT(*) FROM ProxyForgers JOIN Blocks ON generator = proxy_public_key WHERE forger = ?";
String rewardShareSql = "SELECT COUNT(*) FROM RewardShares JOIN Blocks ON minter = reward_share_public_key WHERE minter_public_key = ?";
int totalCount = 0;
try (ResultSet resultSet = this.repository.checkedExecute(directSql, publicKey)) {
try (ResultSet resultSet = this.repository.checkedExecute(directSql, minterPublicKey)) {
totalCount += resultSet.getInt(1);
} catch (SQLException e) {
throw new DataException("Unable to fetch forged blocks count from repository", e);
throw new DataException("Unable to count minted blocks in repository", e);
}
try (ResultSet resultSet = this.repository.checkedExecute(proxySql, publicKey)) {
try (ResultSet resultSet = this.repository.checkedExecute(rewardShareSql, minterPublicKey)) {
totalCount += resultSet.getInt(1);
} catch (SQLException e) {
throw new DataException("Unable to fetch forged blocks count from repository", e);
throw new DataException("Unable to count reward-share minted blocks in repository", e);
}
return totalCount;
}
@Override
public List<BlockForgerSummary> getBlockForgers(List<String> addresses, Integer limit, Integer offset, Boolean reverse) throws DataException {
String subquerySql = "SELECT generator, COUNT(signature) FROM Blocks GROUP BY generator";
public List<BlockMinterSummary> getBlockMinters(List<String> addresses, Integer limit, Integer offset, Boolean reverse) throws DataException {
String subquerySql = "SELECT minter, COUNT(signature) FROM Blocks GROUP BY minter";
StringBuilder sql = new StringBuilder(1024);
sql.append("SELECT DISTINCT generator, n_blocks, forger, recipient FROM (");
sql.append("SELECT DISTINCT block_minter, n_blocks, minter_public_key, recipient FROM (");
sql.append(subquerySql);
sql.append(") AS Forgers (generator, n_blocks) LEFT OUTER JOIN ProxyForgers ON proxy_public_key = generator ");
sql.append(") AS Minters (block_minter, n_blocks) LEFT OUTER JOIN RewardShares ON reward_share_public_key = block_minter ");
if (addresses != null && !addresses.isEmpty()) {
sql.append(" LEFT OUTER JOIN Accounts AS GeneratorAccounts ON GeneratorAccounts.public_key = generator ");
sql.append(" LEFT OUTER JOIN Accounts AS ForgerAccounts ON ForgerAccounts.public_key = forger ");
sql.append(" LEFT OUTER JOIN Accounts AS BlockMinterAccounts ON BlockMinterAccounts.public_key = block_minter ");
sql.append(" LEFT OUTER JOIN Accounts AS RewardShareMinterAccounts ON RewardShareMinterAccounts.public_key = minter_public_key ");
sql.append(" JOIN (VALUES ");
final int addressesSize = addresses.size();
@ -219,7 +219,7 @@ public class HSQLDBBlockRepository implements BlockRepository {
}
sql.append(") AS FilterAccounts (account) ");
sql.append(" ON FilterAccounts.account IN (recipient, GeneratorAccounts.account, ForgerAccounts.account) ");
sql.append(" ON FilterAccounts.account IN (recipient, BlockMinterAccounts.account, RewardShareMinterAccounts.account) ");
} else {
addresses = Collections.emptyList();
}
@ -230,31 +230,37 @@ public class HSQLDBBlockRepository implements BlockRepository {
HSQLDBRepository.limitOffsetSql(sql, limit, offset);
List<BlockForgerSummary> summaries = new ArrayList<>();
List<BlockMinterSummary> summaries = new ArrayList<>();
try (ResultSet resultSet = this.repository.checkedExecute(sql.toString(), addresses.toArray())) {
if (resultSet == null)
return summaries;
do {
byte[] generator = resultSet.getBytes(1);
byte[] blockMinterPublicKey = resultSet.getBytes(1);
int nBlocks = resultSet.getInt(2);
byte[] forger = resultSet.getBytes(3);
String recipient = resultSet.getString(4);
byte[] mintingAccountPublicKey = resultSet.getBytes(3);
String recipientAccount = resultSet.getString(4);
summaries.add(new BlockForgerSummary(generator, nBlocks, forger, recipient));
BlockMinterSummary blockMinterSummary;
if (recipientAccount == null)
blockMinterSummary = new BlockMinterSummary(blockMinterPublicKey, nBlocks);
else
blockMinterSummary = new BlockMinterSummary(blockMinterPublicKey, nBlocks, mintingAccountPublicKey, recipientAccount);
summaries.add(blockMinterSummary);
} while (resultSet.next());
return summaries;
} catch (SQLException e) {
throw new DataException("Unable to fetch generator's blocks from repository", e);
throw new DataException("Unable to fetch block minters from repository", e);
}
}
@Override
public List<BlockData> getBlocksWithGenerator(byte[] generatorPublicKey, Integer limit, Integer offset, Boolean reverse) throws DataException {
public List<BlockData> getBlocksByMinter(byte[] minterPublicKey, Integer limit, Integer offset, Boolean reverse) throws DataException {
StringBuilder sql = new StringBuilder(512);
sql.append("SELECT " + BLOCK_DB_COLUMNS + " FROM Blocks WHERE generator = ? ORDER BY height ");
sql.append("SELECT " + BLOCK_DB_COLUMNS + " FROM Blocks WHERE minter = ? ORDER BY height ");
if (reverse != null && reverse)
sql.append(" DESC");
@ -262,7 +268,7 @@ public class HSQLDBBlockRepository implements BlockRepository {
List<BlockData> blockData = new ArrayList<>();
try (ResultSet resultSet = this.repository.checkedExecute(sql.toString(), generatorPublicKey)) {
try (ResultSet resultSet = this.repository.checkedExecute(sql.toString(), minterPublicKey)) {
if (resultSet == null)
return blockData;
@ -272,7 +278,7 @@ public class HSQLDBBlockRepository implements BlockRepository {
return blockData;
} catch (SQLException e) {
throw new DataException("Unable to fetch generator's blocks from repository", e);
throw new DataException("Unable to fetch minter's blocks from repository", e);
}
}
@ -298,7 +304,7 @@ public class HSQLDBBlockRepository implements BlockRepository {
@Override
public List<BlockSummaryData> getBlockSummaries(int firstBlockHeight, int lastBlockHeight) throws DataException {
String sql = "SELECT signature, height, generator, online_accounts_count FROM Blocks WHERE height BETWEEN ? AND ?";
String sql = "SELECT signature, height, minter, online_accounts_count FROM Blocks WHERE height BETWEEN ? AND ?";
List<BlockSummaryData> blockSummaries = new ArrayList<>();
@ -309,10 +315,10 @@ public class HSQLDBBlockRepository implements BlockRepository {
do {
byte[] signature = resultSet.getBytes(1);
int height = resultSet.getInt(2);
byte[] generatorPublicKey = resultSet.getBytes(3);
byte[] minterPublicKey = resultSet.getBytes(3);
int onlineAccountsCount = resultSet.getInt(4);
BlockSummaryData blockSummary = new BlockSummaryData(height, signature, generatorPublicKey, onlineAccountsCount);
BlockSummaryData blockSummary = new BlockSummaryData(height, signature, minterPublicKey, onlineAccountsCount);
blockSummaries.add(blockSummary);
} while (resultSet.next());
@ -324,7 +330,7 @@ public class HSQLDBBlockRepository implements BlockRepository {
@Override
public int trimOldOnlineAccountsSignatures(long timestamp) throws DataException {
String sql = "UPDATE Blocks set online_accounts_signatures = NULL WHERE generation < ? AND online_accounts_signatures IS NOT NULL";
String sql = "UPDATE Blocks set online_accounts_signatures = NULL WHERE minted < ? AND online_accounts_signatures IS NOT NULL";
try {
return this.repository.checkedExecuteUpdateCount(sql, toOffsetDateTime(timestamp));
@ -340,8 +346,8 @@ public class HSQLDBBlockRepository implements BlockRepository {
saveHelper.bind("signature", blockData.getSignature()).bind("version", blockData.getVersion()).bind("reference", blockData.getReference())
.bind("transaction_count", blockData.getTransactionCount()).bind("total_fees", blockData.getTotalFees())
.bind("transactions_signature", blockData.getTransactionsSignature()).bind("height", blockData.getHeight())
.bind("generation", toOffsetDateTime(blockData.getTimestamp()))
.bind("generator", blockData.getGeneratorPublicKey()).bind("generator_signature", blockData.getGeneratorSignature())
.bind("minted", toOffsetDateTime(blockData.getTimestamp()))
.bind("minter", blockData.getMinterPublicKey()).bind("minter_signature", blockData.getMinterSignature())
.bind("AT_count", blockData.getATCount()).bind("AT_fees", blockData.getATFees())
.bind("online_accounts", blockData.getEncodedOnlineAccounts()).bind("online_accounts_count", blockData.getOnlineAccountsCount())
.bind("online_accounts_timestamp", toOffsetDateTime(blockData.getOnlineAccountsTimestamp()))

View File

@ -810,6 +810,37 @@ public class HSQLDBDatabaseUpdates {
stmt.execute("DROP TABLE EnableForgingTransactions");
break;
case 58:
// Refactoring to unify/clarify block forging/generation/proxy-forging to simply "minting"
// Account-related
stmt.execute("ALTER TABLE Accounts ALTER COLUMN blocks_generated RENAME TO blocks_minted");
// "proxy-forging" is now "reward-share"
stmt.execute("ALTER TABLE ProxyForgers ALTER COLUMN proxy_public_key RENAME TO reward_share_public_key");
stmt.execute("ALTER TABLE ProxyForgers ALTER COLUMN forger RENAME TO minter_public_key");
stmt.execute("ALTER TABLE ProxyForgers ALTER COLUMN share RENAME TO share_percent");
stmt.execute("ALTER TABLE ProxyForgers RENAME TO RewardShares");
stmt.execute("CREATE INDEX RewardSharePublicKeyIndex ON RewardShares (reward_share_public_key)");
stmt.execute("DROP INDEX ProxyForgersProxyPublicKeyIndex");
// Reward-share transactions
stmt.execute("ALTER TABLE ProxyForgingTransactions ALTER COLUMN forger RENAME TO minter_public_key");
stmt.execute("ALTER TABLE ProxyForgingTransactions ALTER COLUMN proxy_public_key RENAME TO reward_share_public_key");
stmt.execute("ALTER TABLE ProxyForgingTransactions ALTER COLUMN share RENAME TO share_percent");
stmt.execute("ALTER TABLE ProxyForgingTransactions ALTER COLUMN previous_share RENAME TO previous_share_percent");
stmt.execute("ALTER TABLE ProxyForgingTransactions RENAME TO RewardShareTransactions");
// Accounts used by BlockMinter
stmt.execute("ALTER TABLE ForgingAccounts ALTER COLUMN forger_seed RENAME TO minter_private_key");
stmt.execute("ALTER TABLE ForgingAccounts RENAME TO MintingAccounts");
// Blocks
stmt.execute("ALTER TABLE Blocks ALTER COLUMN generation RENAME TO minted");
stmt.execute("ALTER TABLE Blocks ALTER COLUMN generator RENAME TO minter");
stmt.execute("ALTER TABLE Blocks ALTER COLUMN generator_signature RENAME TO minter_signature");
// Block-indexes
stmt.execute("CREATE INDEX BlockMinterIndex ON Blocks (minter)");
stmt.execute("DROP INDEX BlockGeneratorIndex");
stmt.execute("CREATE INDEX BlockMintedHeightIndex ON Blocks (minted, height)");
stmt.execute("DROP INDEX BlockGenerationHeightIndex");
break;
default:
// nothing to do
return false;

View File

@ -1,50 +0,0 @@
package org.qora.repository.hsqldb.transaction;
import java.sql.ResultSet;
import java.sql.SQLException;
import org.qora.data.transaction.EnableForgingTransactionData;
import org.qora.data.transaction.BaseTransactionData;
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(BaseTransactionData baseTransactionData) throws DataException {
String sql = "SELECT target FROM EnableForgingTransactions WHERE signature = ?";
try (ResultSet resultSet = this.repository.checkedExecute(sql, baseTransactionData.getSignature())) {
if (resultSet == null)
return null;
String target = resultSet.getString(1);
return new EnableForgingTransactionData(baseTransactionData, target);
} 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);
}
}
}

View File

@ -1,55 +0,0 @@
package org.qora.repository.hsqldb.transaction;
import java.math.BigDecimal;
import java.sql.ResultSet;
import java.sql.SQLException;
import org.qora.data.transaction.BaseTransactionData;
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(BaseTransactionData baseTransactionData) throws DataException {
String sql = "SELECT recipient, proxy_public_key, share, previous_share FROM ProxyForgingTransactions WHERE signature = ?";
try (ResultSet resultSet = this.repository.checkedExecute(sql, baseTransactionData.getSignature())) {
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(baseTransactionData, recipient, proxyPublicKey, share, previousShare);
} 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);
}
}
}

View File

@ -0,0 +1,55 @@
package org.qora.repository.hsqldb.transaction;
import java.math.BigDecimal;
import java.sql.ResultSet;
import java.sql.SQLException;
import org.qora.data.transaction.BaseTransactionData;
import org.qora.data.transaction.RewardShareTransactionData;
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 HSQLDBRewardShareTransactionRepository extends HSQLDBTransactionRepository {
public HSQLDBRewardShareTransactionRepository(HSQLDBRepository repository) {
this.repository = repository;
}
TransactionData fromBase(BaseTransactionData baseTransactionData) throws DataException {
String sql = "SELECT recipient, reward_share_public_key, share_percent, previous_share_percent FROM RewardShareTransactions WHERE signature = ?";
try (ResultSet resultSet = this.repository.checkedExecute(sql, baseTransactionData.getSignature())) {
if (resultSet == null)
return null;
String recipient = resultSet.getString(1);
byte[] rewardSharePublicKey = resultSet.getBytes(2);
BigDecimal sharePercent = resultSet.getBigDecimal(3);
BigDecimal previousSharePercent = resultSet.getBigDecimal(4);
return new RewardShareTransactionData(baseTransactionData, recipient, rewardSharePublicKey, sharePercent, previousSharePercent);
} catch (SQLException e) {
throw new DataException("Unable to fetch reward-share transaction from repository", e);
}
}
@Override
public void save(TransactionData transactionData) throws DataException {
RewardShareTransactionData rewardShareTransactionData = (RewardShareTransactionData) transactionData;
HSQLDBSaver saveHelper = new HSQLDBSaver("RewardShareTransactions");
saveHelper.bind("signature", rewardShareTransactionData.getSignature()).bind("minter_public_key", rewardShareTransactionData.getMinterPublicKey())
.bind("recipient", rewardShareTransactionData.getRecipient()).bind("reward_share_public_key", rewardShareTransactionData.getRewardSharePublicKey())
.bind("share_percent", rewardShareTransactionData.getSharePercent()).bind("previous_share_percent", rewardShareTransactionData.getPreviousSharePercent());
try {
saveHelper.execute(this.repository);
} catch (SQLException e) {
throw new DataException("Unable to save reward-share transaction into repository", e);
}
}
}

View File

@ -75,7 +75,7 @@ public class Settings {
private boolean isTestNet = false;
/** Port number for inbound peer-to-peer connections. */
private Integer listenPort;
/** Minimum number of peers to allow block generation / synchronization. */
/** Minimum number of peers to allow block minting / synchronization. */
private int minBlockchainPeers = 5;
/** Target number of outbound connections to peers we should make. */
private int minOutboundPeers = 20;

View File

@ -104,6 +104,12 @@ public class AccountFlagsTransaction extends Transaction {
target.setFlags(newFlags);
}
@Override
public void processReferencesAndFees() throws DataException {
// Set account's reference
getTarget().setLastReference(this.accountFlagsTransactionData.getSignature());
}
@Override
public void orphan() throws DataException {
// Revert

View File

@ -94,6 +94,12 @@ public class AccountLevelTransaction extends Transaction {
target.setInitialLevel(this.accountLevelTransactionData.getLevel());
}
@Override
public void processReferencesAndFees() throws DataException {
// Set account's reference
getTarget().setLastReference(this.accountLevelTransactionData.getSignature());
}
@Override
public void orphan() throws DataException {
// Revert

View File

@ -1,193 +0,0 @@
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.block.BlockChain;
import org.qora.crypto.Crypto;
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 themselves needs to be allowed to forge
if (!creator.canForge())
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;
// If proxy public key already exists in repository, then it must be for the same forger-recipient combo
ProxyForgerData proxyForgerData = this.repository.getAccountRepository().getProxyForgeData(this.proxyForgingTransactionData.getProxyPublicKey());
if (proxyForgerData != null) {
if (!proxyForgerData.getRecipient().equals(recipient.getAddress()) || !Arrays.equals(proxyForgerData.getForgerPublicKey(), creator.getPublicKey()))
return ValidationResult.INVALID_PUBLIC_KEY;
} else {
// This is a new relationship
// No point starting a new relationship with 0% share (i.e. delete relationship)
if (this.proxyForgingTransactionData.getShare().compareTo(BigDecimal.ZERO) == 0)
return ValidationResult.INVALID_FORGE_SHARE;
// Check that the generator hasn't reach maximum number of relationships
int relationshipCount = this.repository.getAccountRepository().countProxyAccounts(creator.getPublicKey());
if (relationshipCount >= BlockChain.getInstance().getMaxProxyRelationships())
return ValidationResult.MAXIMUM_PROXY_RELATIONSHIPS;
}
// Check fee is positive
if (proxyForgingTransactionData.getFee().compareTo(BigDecimal.ZERO) <= 0)
return ValidationResult.NEGATIVE_FEE;
// Check creator has enough funds
if (creator.getConfirmedBalance(Asset.QORT).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);
// 0% share is actually a request to delete existing relationship
if (proxyForgingTransactionData.getShare().compareTo(BigDecimal.ZERO) == 0) {
this.repository.getAccountRepository().delete(forger.getPublicKey(), proxyForgingTransactionData.getRecipient());
} else {
// Save proxy forging info
proxyForgerData = new ProxyForgerData(forger.getPublicKey(), proxyForgingTransactionData.getRecipient(), proxyForgingTransactionData.getProxyPublicKey(), proxyForgingTransactionData.getShare());
this.repository.getAccountRepository().save(proxyForgerData);
}
}
@Override
public void processReferencesAndFees() throws DataException {
super.processReferencesAndFees();
// If proxy recipient has no last-reference then use this transaction's signature as last-reference so they can spend their block rewards
Account recipient = new Account(this.repository, proxyForgingTransactionData.getRecipient());
if (recipient.getLastReference() == null)
recipient.setLastReference(proxyForgingTransactionData.getSignature());
}
@Override
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());
}
// Save this transaction, with removed previous share info
proxyForgingTransactionData.setPreviousShare(null);
this.repository.getTransactionRepository().save(proxyForgingTransactionData);
}
@Override
public void orphanReferencesAndFees() throws DataException {
super.orphanReferencesAndFees();
// If recipient didn't have a last-reference prior to this transaction then remove it
Account recipient = new Account(this.repository, proxyForgingTransactionData.getRecipient());
if (Arrays.equals(recipient.getLastReference(), proxyForgingTransactionData.getSignature()))
recipient.setLastReference(null);
}
}

View File

@ -0,0 +1,221 @@
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.block.BlockChain;
import org.qora.crypto.Crypto;
import org.qora.data.account.RewardShareData;
import org.qora.data.transaction.RewardShareTransactionData;
import org.qora.data.transaction.TransactionData;
import org.qora.repository.DataException;
import org.qora.repository.Repository;
import org.qora.transform.Transformer;
public class RewardShareTransaction extends Transaction {
// Properties
private RewardShareTransactionData rewardShareTransactionData;
// Constructors
public RewardShareTransaction(Repository repository, TransactionData transactionData) {
super(repository, transactionData);
this.rewardShareTransactionData = (RewardShareTransactionData) 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.getMintingAccount().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.getMintingAccount().getAddress()))
amount = amount.subtract(this.transactionData.getFee());
return amount;
}
// Navigation
public PublicKeyAccount getMintingAccount() {
return new PublicKeyAccount(this.repository, this.rewardShareTransactionData.getMinterPublicKey());
}
public Account getRecipient() {
return new Account(this.repository, this.rewardShareTransactionData.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.rewardShareTransactionData.getSharePercent().compareTo(BigDecimal.ZERO) < 0
|| this.rewardShareTransactionData.getSharePercent().compareTo(MAX_SHARE) > 0)
return ValidationResult.INVALID_REWARD_SHARE_PERCENT;
PublicKeyAccount creator = getCreator();
// Check reward-share public key is correct length
if (this.rewardShareTransactionData.getRewardSharePublicKey().length != Transformer.PUBLIC_KEY_LENGTH)
return ValidationResult.INVALID_PUBLIC_KEY;
Account recipient = getRecipient();
if (!Crypto.isValidAddress(recipient.getAddress()))
return ValidationResult.INVALID_ADDRESS;
// Creator themselves needs to be allowed to mint
if (!creator.canMint())
return ValidationResult.NOT_MINTING_ACCOUNT;
// Qortal: special rules in play depending whether recipient is also minter
final boolean isRecipientAlsoMinter = creator.getAddress().equals(recipient.getAddress());
if (!isRecipientAlsoMinter && !creator.canRewardShare())
return ValidationResult.ACCOUNT_CANNOT_REWARD_SHARE;
// Look up any existing reward-share (using transaction's reward-share public key)
RewardShareData rewardShareData = this.repository.getAccountRepository().getRewardShare(this.rewardShareTransactionData.getRewardSharePublicKey());
if (rewardShareData != null) {
// If reward-share public key already exists in repository, then it must be for the same minter-recipient combo
if (!rewardShareData.getRecipient().equals(recipient.getAddress()) || !Arrays.equals(rewardShareData.getMinterPublicKey(), creator.getPublicKey()))
return ValidationResult.INVALID_PUBLIC_KEY;
} else {
// No luck, try looking up existing reward-share using minting & recipient account info
rewardShareData = this.repository.getAccountRepository().getRewardShare(creator.getPublicKey(), recipient.getAddress());
if (rewardShareData != null)
// If reward-share between minter & recipient already exists in repository, then it must be have the same public key
if (!Arrays.equals(rewardShareData.getRewardSharePublicKey(), this.rewardShareTransactionData.getRewardSharePublicKey()))
return ValidationResult.INVALID_PUBLIC_KEY;
}
final boolean isSharePercentZero = this.rewardShareTransactionData.getSharePercent().compareTo(BigDecimal.ZERO) == 0;
if (rewardShareData == null) {
// This is a new reward-share
// No point starting a new reward-share with 0% share (i.e. delete relationship)
if (isSharePercentZero)
return ValidationResult.INVALID_REWARD_SHARE_PERCENT;
// Check the minting account hasn't reach maximum number of reward-shares
int relationshipCount = this.repository.getAccountRepository().countRewardShares(creator.getPublicKey());
if (relationshipCount >= BlockChain.getInstance().getMaxRewardSharesPerMintingAccount())
return ValidationResult.MAXIMUM_REWARD_SHARES;
} else {
// This transaction intends to modify/terminate an existing reward-share
// Modifying an existing self-share is pointless and forbidden (due to 0 fee). Deleting self-share is OK though.
if (isRecipientAlsoMinter && !isSharePercentZero)
return ValidationResult.INVALID_REWARD_SHARE_PERCENT;
}
// Fee checking needed if not setting up new self-share
if (!(isRecipientAlsoMinter && rewardShareData == null)) {
// Check fee is positive
if (rewardShareTransactionData.getFee().compareTo(BigDecimal.ZERO) <= 0)
return ValidationResult.NEGATIVE_FEE;
// Check creator has enough funds
if (creator.getConfirmedBalance(Asset.QORT).compareTo(rewardShareTransactionData.getFee()) < 0)
return ValidationResult.NO_BALANCE;
}
return ValidationResult.OK;
}
@Override
public void process() throws DataException {
PublicKeyAccount mintingAccount = getMintingAccount();
// Grab any previous share info for orphaning purposes
RewardShareData rewardShareData = this.repository.getAccountRepository().getRewardShare(mintingAccount.getPublicKey(),
rewardShareTransactionData.getRecipient());
if (rewardShareData != null)
rewardShareTransactionData.setPreviousSharePercent(rewardShareData.getSharePercent());
// Save this transaction, with previous share info
this.repository.getTransactionRepository().save(rewardShareTransactionData);
// 0% share is actually a request to delete existing reward-share
if (rewardShareTransactionData.getSharePercent().compareTo(BigDecimal.ZERO) == 0) {
this.repository.getAccountRepository().delete(mintingAccount.getPublicKey(), rewardShareTransactionData.getRecipient());
} else {
// Save reward-share info
rewardShareData = new RewardShareData(mintingAccount.getPublicKey(), rewardShareTransactionData.getRecipient(), rewardShareTransactionData.getRewardSharePublicKey(), rewardShareTransactionData.getSharePercent());
this.repository.getAccountRepository().save(rewardShareData);
}
}
@Override
public void processReferencesAndFees() throws DataException {
super.processReferencesAndFees();
// If reward-share recipient has no last-reference then use this transaction's signature as last-reference so they can spend their block rewards
Account recipient = new Account(this.repository, rewardShareTransactionData.getRecipient());
if (recipient.getLastReference() == null)
recipient.setLastReference(rewardShareTransactionData.getSignature());
}
@Override
public void orphan() throws DataException {
// Revert
PublicKeyAccount mintingAccount = getMintingAccount();
if (rewardShareTransactionData.getPreviousSharePercent() != null) {
// Revert previous sharing arrangement
RewardShareData rewardShareData = new RewardShareData(mintingAccount.getPublicKey(), rewardShareTransactionData.getRecipient(),
rewardShareTransactionData.getRewardSharePublicKey(), rewardShareTransactionData.getPreviousSharePercent());
this.repository.getAccountRepository().save(rewardShareData);
} else {
// No previous arrangement so simply delete
this.repository.getAccountRepository().delete(mintingAccount.getPublicKey(), rewardShareTransactionData.getRecipient());
}
// Save this transaction, with removed previous share info
rewardShareTransactionData.setPreviousSharePercent(null);
this.repository.getTransactionRepository().save(rewardShareTransactionData);
}
@Override
public void orphanReferencesAndFees() throws DataException {
super.orphanReferencesAndFees();
// If recipient didn't have a last-reference prior to this transaction then remove it
Account recipient = new Account(this.repository, rewardShareTransactionData.getRecipient());
if (Arrays.equals(recipient.getLastReference(), rewardShareTransactionData.getSignature()))
recipient.setLastReference(null);
}
}

View File

@ -82,7 +82,7 @@ public abstract class Transaction {
UPDATE_ASSET(35, true),
ACCOUNT_FLAGS(36, false),
ENABLE_FORGING(37, false),
PROXY_FORGING(38, false),
REWARD_SHARE(38, false),
ACCOUNT_LEVEL(39, false);
public final int value;
@ -224,23 +224,21 @@ 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),
NOT_MINTING_ACCOUNT(73),
INVALID_REWARD_SHARE_PERCENT(77),
PUBLIC_KEY_UNKNOWN(78),
INVALID_PUBLIC_KEY(79),
AT_UNKNOWN(80),
AT_ALREADY_EXISTS(81),
GROUP_APPROVAL_NOT_REQUIRED(82),
GROUP_APPROVAL_DECIDED(83),
MAXIMUM_PROXY_RELATIONSHIPS(84),
MAXIMUM_REWARD_SHARES(84),
TRANSACTION_ALREADY_EXISTS(85),
NO_BLOCKCHAIN_LOCK(86),
ORDER_ALREADY_CLOSED(87),
CLOCK_NOT_SYNCED(88),
ASSET_NOT_SPENDABLE(89),
ACCOUNT_CANNOT_REWARD_SHARE(90),
NOT_YET_RELEASED(1000);
public final int value;

View File

@ -34,16 +34,16 @@ public class BlockTransformer extends Transformer {
private static final int VERSION_LENGTH = INT_LENGTH;
private static final int TRANSACTIONS_SIGNATURE_LENGTH = SIGNATURE_LENGTH;
private static final int GENERATOR_SIGNATURE_LENGTH = SIGNATURE_LENGTH;
private static final int BLOCK_REFERENCE_LENGTH = GENERATOR_SIGNATURE_LENGTH + TRANSACTIONS_SIGNATURE_LENGTH;
private static final int MINTER_SIGNATURE_LENGTH = SIGNATURE_LENGTH;
private static final int BLOCK_REFERENCE_LENGTH = MINTER_SIGNATURE_LENGTH + TRANSACTIONS_SIGNATURE_LENGTH;
private static final int TIMESTAMP_LENGTH = LONG_LENGTH;
private static final int GENERATOR_LENGTH = PUBLIC_KEY_LENGTH;
private static final int MINTER_PUBLIC_KEY_LENGTH = PUBLIC_KEY_LENGTH;
private static final int TRANSACTION_COUNT_LENGTH = INT_LENGTH;
private static final int BASE_LENGTH = VERSION_LENGTH + TIMESTAMP_LENGTH + BLOCK_REFERENCE_LENGTH + GENERATOR_LENGTH
+ TRANSACTIONS_SIGNATURE_LENGTH + GENERATOR_SIGNATURE_LENGTH + TRANSACTION_COUNT_LENGTH;
private static final int BASE_LENGTH = VERSION_LENGTH + TIMESTAMP_LENGTH + BLOCK_REFERENCE_LENGTH + MINTER_PUBLIC_KEY_LENGTH
+ TRANSACTIONS_SIGNATURE_LENGTH + MINTER_SIGNATURE_LENGTH + TRANSACTION_COUNT_LENGTH;
public static final int BLOCK_SIGNATURE_LENGTH = GENERATOR_SIGNATURE_LENGTH + TRANSACTIONS_SIGNATURE_LENGTH;
public static final int BLOCK_SIGNATURE_LENGTH = MINTER_SIGNATURE_LENGTH + TRANSACTIONS_SIGNATURE_LENGTH;
protected static final int TRANSACTION_SIZE_LENGTH = INT_LENGTH; // per transaction
protected static final int AT_BYTES_LENGTH = INT_LENGTH;
protected static final int AT_FEES_LENGTH = LONG_LENGTH;
@ -97,13 +97,13 @@ public class BlockTransformer extends Transformer {
byte[] reference = new byte[BLOCK_REFERENCE_LENGTH];
byteBuffer.get(reference);
byte[] generatorPublicKey = Serialization.deserializePublicKey(byteBuffer);
byte[] minterPublicKey = Serialization.deserializePublicKey(byteBuffer);
byte[] transactionsSignature = new byte[TRANSACTIONS_SIGNATURE_LENGTH];
byteBuffer.get(transactionsSignature);
byte[] generatorSignature = new byte[GENERATOR_SIGNATURE_LENGTH];
byteBuffer.get(generatorSignature);
byte[] minterSignature = new byte[MINTER_SIGNATURE_LENGTH];
byteBuffer.get(minterSignature);
BigDecimal totalFees = BigDecimal.ZERO.setScale(8);
@ -242,7 +242,7 @@ public class BlockTransformer extends Transformer {
// We don't have a height!
Integer height = null;
BlockData blockData = new BlockData(version, reference, transactionCount, totalFees, transactionsSignature, height, timestamp,
generatorPublicKey, generatorSignature, atCount, atFees, encodedOnlineAccounts, onlineAccountsCount, onlineAccountsTimestamp, onlineAccountsSignatures);
minterPublicKey, minterSignature, atCount, atFees, encodedOnlineAccounts, onlineAccountsCount, onlineAccountsTimestamp, onlineAccountsSignatures);
return new Triple<>(blockData, transactions, atStates);
}
@ -286,9 +286,9 @@ public class BlockTransformer extends Transformer {
bytes.write(Ints.toByteArray(blockData.getVersion()));
bytes.write(Longs.toByteArray(blockData.getTimestamp()));
bytes.write(blockData.getReference());
bytes.write(blockData.getGeneratorPublicKey());
bytes.write(blockData.getMinterPublicKey());
bytes.write(blockData.getTransactionsSignature());
bytes.write(blockData.getGeneratorSignature());
bytes.write(blockData.getMinterSignature());
if (blockData.getVersion() >= 4) {
int atBytesLength = blockData.getATCount() * V4_AT_ENTRY_LENGTH;
@ -360,26 +360,26 @@ public class BlockTransformer extends Transformer {
}
}
public static byte[] getGeneratorSignatureFromReference(byte[] blockReference) {
return Arrays.copyOf(blockReference, GENERATOR_SIGNATURE_LENGTH);
public static byte[] getMinterSignatureFromReference(byte[] blockReference) {
return Arrays.copyOf(blockReference, MINTER_SIGNATURE_LENGTH);
}
public static byte[] getBytesForGeneratorSignature(BlockData blockData) throws TransformationException {
byte[] generatorSignature = getGeneratorSignatureFromReference(blockData.getReference());
PublicKeyAccount generator = new PublicKeyAccount(null, blockData.getGeneratorPublicKey());
public static byte[] getBytesForMinterSignature(BlockData blockData) throws TransformationException {
byte[] minterSignature = getMinterSignatureFromReference(blockData.getReference());
PublicKeyAccount minter = new PublicKeyAccount(null, blockData.getMinterPublicKey());
return getBytesForGeneratorSignature(generatorSignature, generator, blockData.getEncodedOnlineAccounts());
return getBytesForMinterSignature(minterSignature, minter, blockData.getEncodedOnlineAccounts());
}
public static byte[] getBytesForGeneratorSignature(byte[] generatorSignature, PublicKeyAccount generator, byte[] encodedOnlineAccounts)
public static byte[] getBytesForMinterSignature(byte[] minterSignature, PublicKeyAccount minter, byte[] encodedOnlineAccounts)
throws TransformationException {
try {
ByteArrayOutputStream bytes = new ByteArrayOutputStream(GENERATOR_SIGNATURE_LENGTH + GENERATOR_LENGTH + encodedOnlineAccounts.length);
ByteArrayOutputStream bytes = new ByteArrayOutputStream(MINTER_SIGNATURE_LENGTH + MINTER_PUBLIC_KEY_LENGTH + encodedOnlineAccounts.length);
bytes.write(generatorSignature);
bytes.write(minterSignature);
// We're padding here just in case the generator is the genesis account whose public key is only 8 bytes long.
bytes.write(Bytes.ensureCapacity(generator.getPublicKey(), GENERATOR_LENGTH, 0));
// We're padding here just in case the minter is the genesis account whose public key is only 8 bytes long.
bytes.write(Bytes.ensureCapacity(minter.getPublicKey(), MINTER_PUBLIC_KEY_LENGTH, 0));
bytes.write(encodedOnlineAccounts);
@ -393,9 +393,9 @@ public class BlockTransformer extends Transformer {
try {
List<Transaction> transactions = block.getTransactions();
ByteArrayOutputStream bytes = new ByteArrayOutputStream(GENERATOR_SIGNATURE_LENGTH + transactions.size() * TransactionTransformer.SIGNATURE_LENGTH);
ByteArrayOutputStream bytes = new ByteArrayOutputStream(MINTER_SIGNATURE_LENGTH + transactions.size() * TransactionTransformer.SIGNATURE_LENGTH);
bytes.write(block.getBlockData().getGeneratorSignature());
bytes.write(block.getBlockData().getMinterSignature());
for (Transaction transaction : transactions) {
// We don't include AT-Transactions as AT-state/output is dealt with elsewhere in the block code

View File

@ -7,13 +7,13 @@ import java.nio.ByteBuffer;
import org.qora.block.BlockChain;
import org.qora.data.transaction.BaseTransactionData;
import org.qora.data.transaction.ProxyForgingTransactionData;
import org.qora.data.transaction.RewardShareTransactionData;
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 {
public class RewardShareTransactionTransformer extends TransactionTransformer {
// Property lengths
private static final int TARGET_LENGTH = ADDRESS_LENGTH;
@ -25,14 +25,14 @@ public class ProxyForgingTransactionTransformer extends TransactionTransformer {
static {
layout = new TransactionLayout();
layout.add("txType: " + TransactionType.PROXY_FORGING.valueString, TransformationType.INT);
layout.add("txType: " + TransactionType.REWARD_SHARE.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("minter'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("reward-share public key", TransformationType.PUBLIC_KEY);
layout.add("recipient's percentage share of block rewards", TransformationType.AMOUNT);
layout.add("fee", TransformationType.AMOUNT);
layout.add("signature", TransformationType.SIGNATURE);
}
@ -47,22 +47,22 @@ public class ProxyForgingTransactionTransformer extends TransactionTransformer {
byte[] reference = new byte[REFERENCE_LENGTH];
byteBuffer.get(reference);
byte[] forgerPublicKey = Serialization.deserializePublicKey(byteBuffer);
byte[] minterPublicKey = Serialization.deserializePublicKey(byteBuffer);
String recipient = Serialization.deserializeAddress(byteBuffer);
byte[] proxyPublicKey = Serialization.deserializePublicKey(byteBuffer);
byte[] rewardSharePublicKey = Serialization.deserializePublicKey(byteBuffer);
BigDecimal share = Serialization.deserializeBigDecimal(byteBuffer);
BigDecimal sharePercent = Serialization.deserializeBigDecimal(byteBuffer);
BigDecimal fee = Serialization.deserializeBigDecimal(byteBuffer);
byte[] signature = new byte[SIGNATURE_LENGTH];
byteBuffer.get(signature);
BaseTransactionData baseTransactionData = new BaseTransactionData(timestamp, txGroupId, reference, forgerPublicKey, fee, signature);
BaseTransactionData baseTransactionData = new BaseTransactionData(timestamp, txGroupId, reference, minterPublicKey, fee, signature);
return new ProxyForgingTransactionData(baseTransactionData, recipient, proxyPublicKey, share);
return new RewardShareTransactionData(baseTransactionData, recipient, rewardSharePublicKey, sharePercent);
}
public static int getDataLength(TransactionData transactionData) throws TransformationException {
@ -71,22 +71,22 @@ public class ProxyForgingTransactionTransformer extends TransactionTransformer {
public static byte[] toBytes(TransactionData transactionData) throws TransformationException {
try {
ProxyForgingTransactionData proxyForgingTransactionData = (ProxyForgingTransactionData) transactionData;
RewardShareTransactionData rewardShareTransactionData = (RewardShareTransactionData) transactionData;
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
transformCommonBytes(transactionData, bytes);
Serialization.serializeAddress(bytes, proxyForgingTransactionData.getRecipient());
Serialization.serializeAddress(bytes, rewardShareTransactionData.getRecipient());
bytes.write(proxyForgingTransactionData.getProxyPublicKey());
bytes.write(rewardShareTransactionData.getRewardSharePublicKey());
Serialization.serializeBigDecimal(bytes, proxyForgingTransactionData.getShare());
Serialization.serializeBigDecimal(bytes, rewardShareTransactionData.getSharePercent());
Serialization.serializeBigDecimal(bytes, proxyForgingTransactionData.getFee());
Serialization.serializeBigDecimal(bytes, rewardShareTransactionData.getFee());
if (proxyForgingTransactionData.getSignature() != null)
bytes.write(proxyForgingTransactionData.getSignature());
if (rewardShareTransactionData.getSignature() != null)
bytes.write(rewardShareTransactionData.getSignature());
return bytes.toByteArray();
} catch (IOException | ClassCastException e) {

View File

@ -8,32 +8,10 @@
"requireGroupForApproval": false,
"defaultGroupId": 0,
"oneNamePerAccount": true,
"maxProxyRelationships": 20,
"minAccountLevelToRewardShare": 5,
"maxRewardSharesPerMintingAccount": 20,
"onlineAccountSignaturesMinLifetime": 2592000000,
"onlineAccountSignaturesMaxLifetime": 3196800000,
"genesisInfo": {
"version": 4,
"timestamp": "1569510000000",
"transactions": [
{ "type": "ISSUE_ASSET", "owner": "QUwGVHPPxJNJ2dq95abQNe79EyBN2K26zM", "assetName": "QORT", "description": "QORTAL coin", "quantity": 10000000, "isDivisible": true, "reference": "28u54WRcMfGujtQMZ9dNKFXVqucY7XfPihXAqPFsnx853NPUwfDJy1sMH5boCkahFgjUNYqc5fkduxdBhQTKgUsC", "data": "{}" },
{ "type": "ISSUE_ASSET", "owner": "QUwGVHPPxJNJ2dq95abQNe79EyBN2K26zM", "assetName": "QORA", "description": "Representative legacy QORA", "quantity": 10000000000, "isDivisible": true, "data": "{}", "isUnspendable": true },
{ "type": "GENESIS", "recipient": "QcatTpaU1UneBs3fVHo8QN6mUmuceRVzFY", "amount": "1000000" },
{ "type": "GENESIS", "recipient": "QcatoCyyp7dVfMtJ92sgUUPDoBJevaemRX", "amount": "1000000" },
{ "type": "GENESIS", "recipient": "QTiga19sttbf6CLQLT83mhCSWEaCvjk8th", "amount": "1000000" },
{ "type": "GENESIS", "recipient": "QcrowX39FuycKvMFFBsakyd5HSxe7bxFsn", "amount": "1000000" },
{ "type": "ACCOUNT_FLAGS", "target": "QcatTpaU1UneBs3fVHo8QN6mUmuceRVzFY", "andMask": -1, "orMask": 1, "xorMask": 0 },
{ "type": "ACCOUNT_FLAGS", "target": "QcatoCyyp7dVfMtJ92sgUUPDoBJevaemRX", "andMask": -1, "orMask": 1, "xorMask": 0 },
{ "type": "ACCOUNT_FLAGS", "target": "QTiga19sttbf6CLQLT83mhCSWEaCvjk8th", "andMask": -1, "orMask": 1, "xorMask": 0 },
{ "type": "ACCOUNT_FLAGS", "target": "QcrowX39FuycKvMFFBsakyd5HSxe7bxFsn", "andMask": -1, "orMask": 1, "xorMask": 0 },
{ "type": "ACCOUNT_LEVEL", "target": "QcatTpaU1UneBs3fVHo8QN6mUmuceRVzFY", "level": 8 },
{ "type": "ACCOUNT_LEVEL", "target": "QcatoCyyp7dVfMtJ92sgUUPDoBJevaemRX", "level": 3 },
{ "type": "ACCOUNT_LEVEL", "target": "QTiga19sttbf6CLQLT83mhCSWEaCvjk8th", "level": 10 },
{ "type": "ACCOUNT_LEVEL", "target": "QcrowX39FuycKvMFFBsakyd5HSxe7bxFsn", "level": 10 },
{ "type": "CREATE_GROUP", "creatorPublicKey": "6rNn9b3pYRrG9UKqzMWYZ9qa8F3Zgv2mVWrULGHUusb", "owner": "QcatTpaU1UneBs3fVHo8QN6mUmuceRVzFY", "groupName": "dev-group", "description": "developer group", "isOpen": false, "approvalThreshold": "PCT60", "minimumBlockDelay": 0, "maximumBlockDelay": 1440 },
{ "type": "CREATE_GROUP", "creatorPublicKey": "JBNBQQDzZsm5do1BrwWAp53Ps4KYJVt749EGpCf7ofte", "owner": "QTiga19sttbf6CLQLT83mhCSWEaCvjk8th", "groupName": "Tiga", "description": "Tiga's group", "isOpen": true, "approvalThreshold": "PCT20", "minimumBlockDelay": 120, "maximumBlockDelay": 2880 },
{ "type": "PROXY_FORGING", "forgerPublicKey": "6rNn9b3pYRrG9UKqzMWYZ9qa8F3Zgv2mVWrULGHUusb", "recipient": "QcatTpaU1UneBs3fVHo8QN6mUmuceRVzFY", "proxyPublicKey": "8X3w1521UNnnonieugAxhfbfvqoRpwPXJrwGQZb5JjQ3", "share": 100 }
]
},
"rewardsByHeight": [
{ "height": 2, "reward": 5.0000 },
{ "height": 259204, "reward": 4.7500 },
@ -71,5 +49,117 @@
"v2Timestamp": 0,
"newAssetPricingTimestamp": 0,
"groupApprovalTimestamp": 0
},
"genesisInfo": {
"version": 4,
"timestamp": "1572000000000",
"transactions": [
{ "type": "ISSUE_ASSET", "owner": "QUwGVHPPxJNJ2dq95abQNe79EyBN2K26zM", "assetName": "QORT", "description": "QORTAL coin", "quantity": 0, "isDivisible": true, "reference": "28u54WRcMfGujtQMZ9dNKFXVqucY7XfPihXAqPFsnx853NPUwfDJy1sMH5boCkahFgjUNYqc5fkduxdBhQTKgUsC", "data": "{}" },
{ "type": "ISSUE_ASSET", "owner": "QUwGVHPPxJNJ2dq95abQNe79EyBN2K26zM", "assetName": "QORA", "description": "Representative legacy QORA", "quantity": 0, "isDivisible": true, "data": "{}", "isUnspendable": true },
{ "type": "GENESIS", "recipient": "QcatTpaU1UneBs3fVHo8QN6mUmuceRVzFY", "amount": "1000" },
{ "type": "ACCOUNT_FLAGS", "target": "QcatTpaU1UneBs3fVHo8QN6mUmuceRVzFY", "andMask": -1, "orMask": 1, "xorMask": 0 },
{ "type": "ACCOUNT_LEVEL", "target": "QcatTpaU1UneBs3fVHo8QN6mUmuceRVzFY", "level": 8 },
{ "type": "REWARD_SHARE", "minterPublicKey": "6rNn9b3pYRrG9UKqzMWYZ9qa8F3Zgv2mVWrULGHUusb", "recipient": "QcatTpaU1UneBs3fVHo8QN6mUmuceRVzFY", "rewardSharePublicKey": "8X3w1521UNnnonieugAxhfbfvqoRpwPXJrwGQZb5JjQ3", "sharePercent": 100 },
{ "type": "GENESIS", "recipient": "QcatoCyyp7dVfMtJ92sgUUPDoBJevaemRX", "amount": "1000" },
{ "type": "ACCOUNT_FLAGS", "target": "QcatoCyyp7dVfMtJ92sgUUPDoBJevaemRX", "andMask": -1, "orMask": 1, "xorMask": 0 },
{ "type": "ACCOUNT_LEVEL", "target": "QcatoCyyp7dVfMtJ92sgUUPDoBJevaemRX", "level": 3 },
{ "type": "GENESIS", "recipient": "QTiga19sttbf6CLQLT83mhCSWEaCvjk8th", "amount": "1000" },
{ "type": "ACCOUNT_FLAGS", "target": "QTiga19sttbf6CLQLT83mhCSWEaCvjk8th", "andMask": -1, "orMask": 1, "xorMask": 0 },
{ "type": "ACCOUNT_LEVEL", "target": "QTiga19sttbf6CLQLT83mhCSWEaCvjk8th", "level": 10 },
{ "type": "GENESIS", "recipient": "QcrowX39FuycKvMFFBsakyd5HSxe7bxFsn", "amount": "1000" },
{ "type": "ACCOUNT_FLAGS", "target": "QcrowX39FuycKvMFFBsakyd5HSxe7bxFsn", "andMask": -1, "orMask": 1, "xorMask": 0 },
{ "type": "ACCOUNT_LEVEL", "target": "QcrowX39FuycKvMFFBsakyd5HSxe7bxFsn", "level": 10 },
{ "type": "CREATE_GROUP", "creatorPublicKey": "6rNn9b3pYRrG9UKqzMWYZ9qa8F3Zgv2mVWrULGHUusb", "owner": "QcatTpaU1UneBs3fVHo8QN6mUmuceRVzFY", "groupName": "dev-group", "description": "developer group", "isOpen": false, "approvalThreshold": "PCT60", "minimumBlockDelay": 0, "maximumBlockDelay": 1440 },
{ "type": "ACCOUNT_LEVEL", "target": "QQKeokRiFCgAhBSdu1DUf5e1LCkgApvrxZ", "level": 5 },
{ "type": "ACCOUNT_LEVEL", "target": "QiaaoNZ54wKoaUMXxW72UsPt1MiPpeUTWm", "level": 5 },
{ "type": "ACCOUNT_LEVEL", "target": "QN5XF1YQUyVt3S1LNZtStXQCbtxyhkj2FR", "level": 5 },
{ "type": "ACCOUNT_LEVEL", "target": "QYoRWAxw6CVMeYeWHKJh3csmTVkVzjpdBo", "level": 5 },
{ "type": "ACCOUNT_LEVEL", "target": "QMtx2UmUuRZckCmRJRyxdzSAazHP8hU5rA", "level": 4 },
{ "type": "ACCOUNT_LEVEL", "target": "QM4LF9EzQnXJ9VyFwnJbJzskJLrybuSzsw", "level": 3 },
{ "type": "ACCOUNT_LEVEL", "target": "QMkgf9Y6Ac2TUrynDvyhX69ekpC3P3GQmN", "level": 3 },
{ "type": "ACCOUNT_LEVEL", "target": "QiRey96Ka6uWrHTvSn3zY7cveqj3Et3YQd", "level": 2 },
{ "type": "ACCOUNT_LEVEL", "target": "QjchrhQ6HPhWn1sAr68PfYdeMS3C6PjH2J", "level": 2 },
{ "type": "ACCOUNT_LEVEL", "target": "QMWSbqWDC1eHU7fCGJ3UwABHiTxBgBJGRC", "level": 2 },
{ "type": "ACCOUNT_LEVEL", "target": "QWbLcR7ijdJV3eU23wVKk3gKh7G6wKEj3d", "level": 1 },
{ "type": "ACCOUNT_LEVEL", "target": "QWviNvp1KPMdyG83ZB2HH29Xxca8fCAv9D", "level": 1 },
{ "type": "ACCOUNT_LEVEL", "target": "QRbxbqavJNQYAhqygxYnbrbFx3LmfuTehe", "level": 1 },
{ "type": "ACCOUNT_LEVEL", "target": "QLpqg45nszcs1ewfGrqcXzQX8VuZCaxbN6", "level": 1 },
{ "type": "ACCOUNT_LEVEL", "target": "QSqeotdyTHxrpx4uquPfq4LLBmezBDuCBo", "level": 1 },
{ "type": "ACCOUNT_LEVEL", "target": "Qa1DSUZPXQSnC1DaTdpZ1fBaCuS62F8GLM", "level": 1 },
{ "type": "ACCOUNT_LEVEL", "target": "QS2k8PMpmvUHzFDp2JfbVxRo8SiGiV72xx", "level": 1 },
{ "type": "ACCOUNT_LEVEL", "target": "QaYAUoT2SzYA6kqBEW6W11brkYpSf7g4Wy", "level": 1 },
{ "type": "ACCOUNT_LEVEL", "target": "QLrErVJvpRdYAt4dGwk8i8rG3PMUSgpp3i", "level": 1 },
{ "type": "ACCOUNT_LEVEL", "target": "QQLnurs5NkGB59Ww1jrnRM6deJbAahVLt4", "level": 1 },
{ "type": "ACCOUNT_LEVEL", "target": "QczBn7uYPb3Evsei2AVjMqSpmV5f2e94Hf", "level": 1 },
{ "type": "ACCOUNT_LEVEL", "target": "QfnYhVqbi8Co87dpzqB8NUXxoRhrcQ55am", "level": 1 },
{ "type": "ACCOUNT_LEVEL", "target": "QPR9YBXhyAaCZKhvYK6Z9K9ddBM8F7kT21", "level": 1 },
{ "type": "ACCOUNT_LEVEL", "target": "QPa2Jy6KK1Yf9XnETHSW5Rp9Pwjum2VWBP", "level": 1 },
{ "type": "ACCOUNT_LEVEL", "target": "QQaEZ52y8govJUUTDdQtERU9saG9kg491N", "level": 1 },
{ "type": "ACCOUNT_LEVEL", "target": "QQShonrAdJXg4CzhegtynzaBP7S631vKGq", "level": 1 },
{ "type": "ACCOUNT_LEVEL", "target": "QdxuYD638XE84UGdKnb8QPoss1shkMRKYw", "level": 1 },
{ "type": "ACCOUNT_LEVEL", "target": "QWGwGBzZG8UeNXJYZq9tjzN6ceriqTgMRd", "level": 1 },
{ "type": "ACCOUNT_LEVEL", "target": "QaWReiLe5tAsTNoq7hMiSGNasJEXTX5bmn", "level": 1 },
{ "type": "ACCOUNT_LEVEL", "target": "QjJjjBUJSZAMuYiwTyfJTFthH6SrofjG6d", "level": 1 },
{ "type": "ACCOUNT_LEVEL", "target": "QQSiZAgdMjadXZRVTHa252AAY2yjZfQKR1", "level": 1 },
{ "type": "ACCOUNT_LEVEL", "target": "QbdHzyw6q2hMAhg12JFbXSEcbrj43xNnYG", "level": 1 },
{ "type": "ACCOUNT_LEVEL", "target": "QRD3WHcsek8TB9ReSQqGAAYopBwZm1Ue8C", "level": 1 },
{ "type": "ACCOUNT_LEVEL", "target": "QQMswzmv65MZSdafqzCACm3tbLj23ixkVR", "level": 1 },
{ "type": "ACCOUNT_LEVEL", "target": "QM5Fg3kBzj9BqsGAJKHhsEtgRgf4x2Yn5G", "level": 1 },
{ "type": "ACCOUNT_LEVEL", "target": "QNwedkYs6hToBRPZvBb6naKvDEgB7BeaKi", "level": 1 },
{ "type": "ACCOUNT_LEVEL", "target": "QPV6E7UXYjcxxFECoiRfwK8bU9AxaMH36U", "level": 1 },
{ "type": "ACCOUNT_LEVEL", "target": "QPK8TJ5t32YWef7UCJErELtqKXy2aKPmqy", "level": 1 },
{ "type": "ACCOUNT_LEVEL", "target": "QQ2ijixLLGg6Bic5ho1Q1uM5nDdN7PEujQ", "level": 1 },
{ "type": "ACCOUNT_LEVEL", "target": "QLgBTDkt7t7WwbTd1RM8iGxu9FexPZcn7C", "level": 1 },
{ "type": "ACCOUNT_LEVEL", "target": "Qa15fMdEiY7WqK2xwvdHrdhU2TE6yk3V3L", "level": 1 },
{ "type": "ACCOUNT_LEVEL", "target": "QVxvbaNRk2LUheWJaAzS3dpZ3iSFwrd8Jo", "level": 1 },
{ "type": "ACCOUNT_LEVEL", "target": "QVz1rKM9QQrGX7gkxR9EV8a2T4AuD1QT87", "level": 1 },
{ "type": "ACCOUNT_LEVEL", "target": "QiECCjPphHZNBbpbqgzRuDnBqbYTH6sBCN", "level": 1 },
{ "type": "ACCOUNT_LEVEL", "target": "QU8SzA12SVKPgqpTzRna5itKktGhvEdmgP", "level": 1 },
{ "type": "ACCOUNT_LEVEL", "target": "QMz7UGfYpjBVfgAhqdKEMAkFyyGi4CrDc2", "level": 1 },
{ "type": "ACCOUNT_LEVEL", "target": "QRh9zNPmz5McxeWx3iCy2xnBRrBoYDZfMJ", "level": 1 },
{ "type": "ACCOUNT_LEVEL", "target": "QNyEBd1MNtY8itADG6WQHXSEJWV5dvTLf5", "level": 1 },
{ "type": "ACCOUNT_LEVEL", "target": "QLcvvSBPkLkfz9rSKATf7DmS6M2ryw7okL", "level": 1 },
{ "type": "ACCOUNT_LEVEL", "target": "QhUWSWWFt6vDy2qNFn68JPTPLjyDrzrh4D", "level": 1 },
{ "type": "ACCOUNT_LEVEL", "target": "QUNZQwRXhu1h5fXh39EgbQqCCN1gNo8MV6", "level": 1 },
{ "type": "ACCOUNT_LEVEL", "target": "QPow3AFucRRHyNP8csLspMJL9N3hKYiKGK", "level": 1 },
{ "type": "ACCOUNT_LEVEL", "target": "QVddqTw2VyqtP91F3U21EiMwov7tfuNsQd", "level": 1 },
{ "type": "ACCOUNT_LEVEL", "target": "QPUyMFyXB6g3uUkUruy2AVhrmBniZvHkQ1", "level": 1 },
{ "type": "ACCOUNT_LEVEL", "target": "QP86QhNUhLKUignCEo2MFmj7XPFEq1KMZ5", "level": 1 },
{ "type": "ACCOUNT_LEVEL", "target": "QNsdyTKN7MVsTYqSyZNPatzRTgNyWDqPZW", "level": 1 },
{ "type": "ACCOUNT_LEVEL", "target": "Qa95hURaNK4kPhDhbdmDFm2wMkkoWFZ4Zz", "level": 1 },
{ "type": "ACCOUNT_LEVEL", "target": "QZNYeoAtuSGajD3kihKUBFNv9TvJ6cFqru", "level": 1 },
{ "type": "ACCOUNT_LEVEL", "target": "QUnfm6t9SrhgFB8YqCV1vQhmqFzhncxioK", "level": 1 },
{ "type": "ACCOUNT_LEVEL", "target": "QRy5dV1xgfxMFXuKHHke1SiFQcdAGkYpnT", "level": 1 },
{ "type": "ACCOUNT_LEVEL", "target": "QeaPN9sxEXR5EJ71wuXmWj63XN9MDToD4w", "level": 1 },
{ "type": "ACCOUNT_LEVEL", "target": "QTekAqCoXPy5R7NE78GXxjSaXBuekZge88", "level": 1 },
{ "type": "ACCOUNT_LEVEL", "target": "QMFJGxGLCbegHL3NZ7HRo3UYaCXhweJVKS", "level": 1 },
{ "type": "ACCOUNT_LEVEL", "target": "QSZtc5EQBuVcovMBxcdSQNnEqwdUZvzjSe", "level": 1 },
{ "type": "ACCOUNT_LEVEL", "target": "QNFWWhmsun3HVUChjck8XqhsQW2tckNxxM", "level": 1 },
{ "type": "ACCOUNT_LEVEL", "target": "QYsLsfwMRBPnunmuWmFkM4hvGsfooY8ssU", "level": 1 },
{ "type": "ACCOUNT_LEVEL", "target": "QW9Gf4VofYEyqgxBGzbPZuqcozY2uaMNBc", "level": 1 },
{ "type": "ACCOUNT_LEVEL", "target": "QNzsigxC8EikCFWCun9cieggFtbJDz6f3Q", "level": 1 },
{ "type": "ACCOUNT_LEVEL", "target": "QR3fQyu3g3ox6mGoyC9ytaVKZwugmbmSFh", "level": 1 },
{ "type": "ACCOUNT_LEVEL", "target": "QMvymCURS2abuV1Gn8tFiMkePpvXTKacco", "level": 1 },
{ "type": "ACCOUNT_LEVEL", "target": "QVD1zDHyNy3PWgcsuYMz1NqzKGMiVrXD2T", "level": 1 },
{ "type": "ACCOUNT_LEVEL", "target": "Qiw4VrpLh5yx9RH1VbL9jrKhz7S7sHztDw", "level": 1 },
{ "type": "ACCOUNT_LEVEL", "target": "QUkq2151QPXfvt8VPc7u5LjFhiHgAtEvC9", "level": 1 },
{ "type": "ACCOUNT_LEVEL", "target": "QRHDHASWAXarqTvB2X4SNtJCWbxGf68M2o", "level": 1 },
{ "type": "ACCOUNT_LEVEL", "target": "QR9e8PfrLWU96QCL4UQNqioEUcAGoHfHBH", "level": 1 },
{ "type": "ACCOUNT_LEVEL", "target": "QPZTxWtCmH6Y6zwwntjnPDfKG6zNKRivqJ", "level": 1 },
{ "type": "ACCOUNT_LEVEL", "target": "QMZwrMVDW8L5CTb896ubMeRWUBjiG4cQAi", "level": 1 },
{ "type": "ACCOUNT_LEVEL", "target": "QMYzatepF38RJwe3UygUXEh4sA3K7HoQfG", "level": 1 },
{ "type": "ACCOUNT_LEVEL", "target": "QMNdPz11XubtvxXLGeiG3PHKaQW67LkZMp", "level": 1 },
{ "type": "ACCOUNT_LEVEL", "target": "QM7KP9J7ty7gVAg13DXJyEkASiWmdT5GQC", "level": 1 },
{ "type": "ACCOUNT_LEVEL", "target": "QjhFQysNr2R4fuybiS6Nm6tPvPhT8xcmqF", "level": 1 },
{ "type": "ACCOUNT_LEVEL", "target": "QixvmAGdJWNkSmentXe8CAnisU9DmifB1C", "level": 1 },
{ "type": "ACCOUNT_LEVEL", "target": "QgrEf4nHhijFQCVgJKro8cRgYWAy1G5rDg", "level": 1 }
]
}
}

View File

@ -11,9 +11,9 @@ CONNECTIONS = connections
EXIT = Exit
GENERATING_DISABLED = NOT minting
MINTING_DISABLED = NOT minting
GENERATING_ENABLED = \u2714 Minting
MINTING_ENABLED = \u2714 Minting
# Nagging about lack of NTP time sync
NTP_NAG_CAPTION = Computer's clock is inaccurate!

View File

@ -11,9 +11,9 @@ CONNECTIONS = \u4E2A\u8FDE\u63A5
EXIT = \u9000\u51FA\u8F6F\u4EF6
GENERATING_DISABLED = \u6CA1\u6709\u94F8\u5E01
MINTING_DISABLED = \u6CA1\u6709\u94F8\u5E01
GENERATING_ENABLED = \u2714 \u94F8\u5E01
MINTING_ENABLED = \u2714 \u94F8\u5E01
# Nagging about lack of NTP time sync
NTP_NAG_CAPTION = \u7535\u8111\u7684\u65F6\u949F\u4E0D\u51C6\u786E\uFF01

View File

@ -31,8 +31,6 @@ import org.bitcoinj.script.ScriptChunk;
import org.bitcoinj.script.ScriptOpCodes;
import org.bitcoinj.wallet.WalletTransaction.Pool;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.junit.BeforeClass;
import org.junit.Test;
import com.google.common.hash.HashCode;
import com.google.common.primitives.Bytes;
@ -76,13 +74,9 @@ public class BTCACCTTests {
private static final boolean doRefundNotRedeem = false;
@BeforeClass
public static void beforeClass() {
public void main(String[] args) throws NoSuchAlgorithmException, InsufficientMoneyException, InterruptedException, ExecutionException, UnknownHostException {
Security.insertProviderAt(new BouncyCastleProvider(), 0);
}
@Test
public void buildBTCACCTTest() throws NoSuchAlgorithmException, InsufficientMoneyException, InterruptedException, ExecutionException, UnknownHostException {
byte[] secret = new byte[32];
new SecureRandom().nextBytes(secret);

View File

@ -7,7 +7,7 @@ import org.junit.Before;
import org.junit.Test;
import org.qora.account.PrivateKeyAccount;
import org.qora.block.Block;
import org.qora.block.BlockGenerator;
import org.qora.block.BlockMinter;
import org.qora.block.GenesisBlock;
import org.qora.data.at.ATStateData;
import org.qora.data.block.BlockData;
@ -69,7 +69,7 @@ public class BlockTests extends Common {
assertEquals(Transaction.TransactionType.GENESIS, transactionData.getType());
assertTrue(transactionData.getFee().compareTo(BigDecimal.ZERO) == 0);
assertNull(transactionData.getReference());
// assertNull(transactionData.getReference());
Transaction transaction = Transaction.fromData(repository, transactionData);
assertNotNull(transaction);
@ -106,7 +106,7 @@ public class BlockTests extends Common {
} catch (InterruptedException e) {
}
BlockGenerator.generateTestingBlock(repository, signingAccount);
BlockMinter.mintTestingBlock(repository, signingAccount);
BlockData blockData = repository.getBlockRepository().getLastBlock();
Block block = new Block(repository, blockData);

View File

@ -44,7 +44,7 @@ public class ChainWeightTests {
}
private static BigInteger calcBlockWeight(int parentHeight, byte[] parentGeneratorKey, BlockSummaryData blockSummaryData) {
BigInteger keyDistance = calcKeyDistance(parentHeight, parentGeneratorKey, blockSummaryData.getGeneratorPublicKey());
BigInteger keyDistance = calcKeyDistance(parentHeight, parentGeneratorKey, blockSummaryData.getMinterPublicKey());
BigInteger weight = BigInteger.valueOf(blockSummaryData.getOnlineAccountsCount()).shiftLeft(ACCOUNTS_COUNT_SHIFT).add(keyDistance);
return weight;
}
@ -57,7 +57,7 @@ public class ChainWeightTests {
for (BlockSummaryData blockSummaryData : blockSummaries) {
cumulativeWeight = cumulativeWeight.shiftLeft(CHAIN_WEIGHT_SHIFT).add(calcBlockWeight(parentHeight, parentGeneratorKey, blockSummaryData));
parentHeight = blockSummaryData.getHeight();
parentGeneratorKey = blockSummaryData.getGeneratorPublicKey();
parentGeneratorKey = blockSummaryData.getMinterPublicKey();
}
return cumulativeWeight;
@ -120,7 +120,7 @@ public class ChainWeightTests {
public void testLongerChain() {
final int commonBlockHeight = 1;
BlockSummaryData commonBlockSummary = genBlockSummary(commonBlockHeight);
byte[] commonBlockGeneratorKey = commonBlockSummary.getGeneratorPublicKey();
byte[] commonBlockGeneratorKey = commonBlockSummary.getMinterPublicKey();
List<BlockSummaryData> shorterChain = genBlockSummaries(3, commonBlockSummary);
List<BlockSummaryData> longerChain = genBlockSummaries(shorterChain.size() + 1, commonBlockSummary);

View File

@ -0,0 +1,40 @@
package org.qora.test;
import org.junit.Test;
import org.qora.crypto.CiyamMemoryPoW;
import static org.junit.Assert.*;
import java.util.Random;
public class CiyamMemoryPoWTests {
@Test
public void testCompute() {
Random random = new Random();
byte[] data = new byte[256];
random.nextBytes(data);
int start = 0;
int range = 1000000;
int difficulty = 1;
long startTime = System.currentTimeMillis();
Integer nonce = CiyamMemoryPoW.compute(data, start, range, difficulty);
long finishTime = System.currentTimeMillis();
System.out.println(String.format("Memory-hard PoW took %dms", finishTime - startTime));
assertNotNull(nonce);
System.out.println(String.format("nonce: %d", nonce));
}
@Test
public void testMultipleComputes() {
for (int i = 0; i < 10; ++i)
testCompute();
}
}

View File

@ -2,17 +2,28 @@ package org.qora.test;
import org.junit.Test;
import org.qora.data.transaction.TransactionData;
import org.qora.repository.DataException;
import org.qora.test.common.Common;
import org.qora.transaction.CreateAssetOrderTransaction;
import org.qora.transaction.CreatePollTransaction;
import org.qora.transaction.IssueAssetTransaction;
import org.qora.transform.TransformationException;
import org.qora.transform.transaction.TransactionTransformer;
import org.qora.utils.NTP;
import static org.junit.Assert.*;
import org.junit.Before;
import com.google.common.hash.HashCode;
public class CompatibilityTests {
public class CompatibilityTests extends Common {
@Before
public void beforeTest() throws DataException {
Common.useSettings("test-settings-v1.json");
NTP.testMode();
}
@Test
public void testCreateOrderTransactionSignature() throws TransformationException {

View File

@ -250,7 +250,7 @@ public class CryptoTests extends Common {
final String expectedProxyPrivateKey = "6KszntmNuXmpUkzLfuttgMPeownctxrnyZUG9rErKJJx";
PrivateKeyAccount mintingAccount = new PrivateKeyAccount(null, ourPrivateKey);
byte[] proxyPrivateKey = mintingAccount.getProxyPrivateKey(theirPublicKey);
byte[] proxyPrivateKey = mintingAccount.getRewardSharePrivateKey(theirPublicKey);
assertEquals(expectedProxyPrivateKey, Base58.encode(proxyPrivateKey));
}

View File

@ -1,5 +1,7 @@
package org.qora.test;
import java.awt.TrayIcon.MessageType;
import org.junit.Test;
import org.qora.gui.SplashFrame;
import org.qora.gui.SysTray;
@ -19,7 +21,11 @@ public class GuiTests {
public void testSysTray() throws InterruptedException {
SysTray.getInstance();
SysTray.getInstance().showMessage("Testing...", "Tray icon should disappear in 10 seconds", MessageType.INFO);
Thread.sleep(10_000L);
SysTray.getInstance().dispose();
}
}

View File

@ -1,316 +0,0 @@
package org.qora.test;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import org.junit.Before;
import org.junit.Test;
import org.qora.account.PrivateKeyAccount;
import org.qora.account.PublicKeyAccount;
import org.qora.data.network.OnlineAccountData;
import org.qora.network.message.GetOnlineAccountsMessage;
import org.qora.network.message.Message;
import org.qora.network.message.OnlineAccountsMessage;
import org.qora.repository.DataException;
import org.qora.test.common.Common;
import org.qora.test.common.FakePeer;
import org.qora.utils.ByteArray;
import com.google.common.primitives.Longs;
public class OnlineTests extends Common {
@Before
public void beforeTest() throws DataException {
Common.useDefaultSettings();
}
private static final int MAX_PEERS = 100;
private static final int MAX_RUNNING_PEERS = 20;
private static final boolean LOG_CONNECTION_CHANGES = false;
private static final boolean LOG_ACCOUNT_CHANGES = true;
private static final boolean GET_ONLINE_UNICAST_NOT_BROADCAST = false;
private static final long ONLINE_TIMESTAMP_MODULUS = 5 * 60 * 1000;
private static List<PrivateKeyAccount> allKnownAccounts;
private static final Random random = new Random();
static class OnlinePeer extends FakePeer {
private static final long LAST_SEEN_EXPIRY_PERIOD = 6 * 60 * 1000;
private static final long ONLINE_REFRESH_INTERVAL = 4 * 60 * 1000;
private static final int MAX_CONNECTED_PEERS = 5;
private final PrivateKeyAccount account;
private List<OnlineAccountData> onlineAccounts;
private long nextOnlineRefresh = 0;
public OnlinePeer(int id, PrivateKeyAccount account) {
super(id);
this.account = account;
this.onlineAccounts = Collections.synchronizedList(new ArrayList<>());
}
@Override
protected void processMessage(FakePeer peer, Message message) throws InterruptedException {
switch (message.getType()) {
case GET_ONLINE_ACCOUNTS: {
GetOnlineAccountsMessage getOnlineAccountsMessage = (GetOnlineAccountsMessage) message;
List<OnlineAccountData> excludeAccounts = getOnlineAccountsMessage.getOnlineAccounts();
// Send online accounts info, excluding entries with matching timestamp & public key from excludeAccounts
List<OnlineAccountData> accountsToSend;
synchronized (this.onlineAccounts) {
accountsToSend = new ArrayList<>(this.onlineAccounts);
}
Iterator<OnlineAccountData> iterator = accountsToSend.iterator();
SEND_ITERATOR:
while (iterator.hasNext()) {
OnlineAccountData onlineAccount = iterator.next();
for (int i = 0; i < excludeAccounts.size(); ++i) {
OnlineAccountData excludeAccount = excludeAccounts.get(i);
if (onlineAccount.getTimestamp() == excludeAccount.getTimestamp() && Arrays.equals(onlineAccount.getPublicKey(), excludeAccount.getPublicKey())) {
iterator.remove();
continue SEND_ITERATOR;
}
}
}
Message onlineAccountsMessage = new OnlineAccountsMessage(accountsToSend);
this.send(peer, onlineAccountsMessage);
if (LOG_ACCOUNT_CHANGES)
System.out.println(String.format("[%d] sent %d of our %d online accounts to %d", this.getId(), accountsToSend.size(), onlineAccounts.size(), peer.getId()));
break;
}
case ONLINE_ACCOUNTS: {
OnlineAccountsMessage onlineAccountsMessage = (OnlineAccountsMessage) message;
List<OnlineAccountData> onlineAccounts = onlineAccountsMessage.getOnlineAccounts();
if (LOG_ACCOUNT_CHANGES)
System.out.println(String.format("[%d] received %d online accounts from %d", this.getId(), onlineAccounts.size(), peer.getId()));
for (OnlineAccountData onlineAccount : onlineAccounts)
verifyAndAddAccount(onlineAccount);
break;
}
default:
break;
}
}
private void verifyAndAddAccount(OnlineAccountData onlineAccount) {
// we would check timestamp is 'recent' here
// Verify
byte[] data = Longs.toByteArray(onlineAccount.getTimestamp());
PublicKeyAccount otherAccount = new PublicKeyAccount(null, onlineAccount.getPublicKey());
if (!otherAccount.verify(onlineAccount.getSignature(), data)) {
System.out.println(String.format("[%d] rejecting invalid online account %s", this.getId(), otherAccount.getAddress()));
return;
}
ByteArray publicKeyBA = new ByteArray(onlineAccount.getPublicKey());
synchronized (this.onlineAccounts) {
OnlineAccountData existingAccount = this.onlineAccounts.stream().filter(account -> new ByteArray(account.getPublicKey()).equals(publicKeyBA)).findFirst().orElse(null);
if (existingAccount != null) {
if (existingAccount.getTimestamp() < onlineAccount.getTimestamp()) {
this.onlineAccounts.remove(existingAccount);
if (LOG_ACCOUNT_CHANGES)
System.out.println(String.format("[%d] updated online account %s with timestamp %d (was %d)", this.getId(), otherAccount.getAddress(), onlineAccount.getTimestamp(), existingAccount.getTimestamp()));
} else {
if (LOG_ACCOUNT_CHANGES)
System.out.println(String.format("[%d] ignoring existing online account %s", this.getId(), otherAccount.getAddress()));
return;
}
} else {
if (LOG_ACCOUNT_CHANGES)
System.out.println(String.format("[%d] added online account %s with timestamp %d", this.getId(), otherAccount.getAddress(), onlineAccount.getTimestamp()));
}
this.onlineAccounts.add(onlineAccount);
}
}
@Override
protected void performIdleTasks() {
final long now = System.currentTimeMillis();
// Expire old entries
final long cutoffThreshold = now - LAST_SEEN_EXPIRY_PERIOD;
synchronized (this.onlineAccounts) {
Iterator<OnlineAccountData> iterator = this.onlineAccounts.iterator();
while (iterator.hasNext()) {
OnlineAccountData onlineAccount = iterator.next();
if (onlineAccount.getTimestamp() < cutoffThreshold) {
iterator.remove();
if (LOG_ACCOUNT_CHANGES) {
PublicKeyAccount otherAccount = new PublicKeyAccount(null, onlineAccount.getPublicKey());
System.out.println(String.format("[%d] removed expired online account %s with timestamp %d", this.getId(), otherAccount.getAddress(), onlineAccount.getTimestamp()));
}
}
}
}
// Request data from another peer
Message message;
synchronized (this.onlineAccounts) {
message = new GetOnlineAccountsMessage(this.onlineAccounts);
}
if (GET_ONLINE_UNICAST_NOT_BROADCAST) {
FakePeer peer = this.pickRandomPeer();
if (peer != null)
this.send(peer, message);
} else {
this.broadcast(message);
}
// Refresh our onlineness?
if (now >= this.nextOnlineRefresh) {
this.nextOnlineRefresh = now + ONLINE_REFRESH_INTERVAL;
refreshOnlineness();
}
// Log our online list
synchronized (this.onlineAccounts) {
System.out.println(String.format("[%d] Connections: %d, online accounts: %d", this.getId(), this.peers.size(), this.onlineAccounts.size()));
}
}
private void refreshOnlineness() {
// Broadcast signed timestamp
final long timestamp = (System.currentTimeMillis() / ONLINE_TIMESTAMP_MODULUS) * ONLINE_TIMESTAMP_MODULUS;
byte[] data = Longs.toByteArray(timestamp);
byte[] signature = this.account.sign(data);
byte[] publicKey = this.account.getPublicKey();
// Our account is online
OnlineAccountData onlineAccount = new OnlineAccountData(timestamp, signature, publicKey);
synchronized (this.onlineAccounts) {
this.onlineAccounts.removeIf(account -> account.getPublicKey() == this.account.getPublicKey());
this.onlineAccounts.add(onlineAccount);
}
Message message = new OnlineAccountsMessage(Arrays.asList(onlineAccount));
this.broadcast(message);
if (LOG_ACCOUNT_CHANGES)
System.out.println(String.format("[%d] broadcasted online account %s with timestamp %d", this.getId(), this.account.getAddress(), timestamp));
}
@Override
public void connect(FakePeer otherPeer) {
int totalPeers;
synchronized (this.peers) {
totalPeers = this.peers.size();
}
if (totalPeers >= MAX_CONNECTED_PEERS)
return;
super.connect(otherPeer);
if (LOG_CONNECTION_CHANGES)
System.out.println(String.format("[%d] Connected to peer %d, total peers: %d", this.getId(), otherPeer.getId(), totalPeers + 1));
}
public void randomDisconnect() {
FakePeer peer;
int totalPeers;
synchronized (this.peers) {
peer = this.pickRandomPeer();
if (peer == null)
return;
totalPeers = this.peers.size();
}
this.disconnect(peer);
if (LOG_CONNECTION_CHANGES)
System.out.println(String.format("[%d] Disconnected peer %d, total peers: %d", this.getId(), peer.getId(), totalPeers - 1));
}
}
@Test
public void testOnlineness() throws InterruptedException {
allKnownAccounts = new ArrayList<>();
List<OnlinePeer> allPeers = new ArrayList<>();
for (int i = 0; i < MAX_PEERS; ++i) {
byte[] seed = new byte[32];
random.nextBytes(seed);
PrivateKeyAccount account = new PrivateKeyAccount(null, seed);
allKnownAccounts.add(account);
OnlinePeer peer = new OnlinePeer(i, account);
allPeers.add(peer);
}
// Start up some peers
List<OnlinePeer> runningPeers = new ArrayList<>();
ExecutorService peerExecutor = Executors.newCachedThreadPool();
for (int c = 0; c < MAX_RUNNING_PEERS; ++c) {
OnlinePeer newPeer;
do {
int i = random.nextInt(allPeers.size());
newPeer = allPeers.get(i);
} while (runningPeers.contains(newPeer));
runningPeers.add(newPeer);
peerExecutor.execute(newPeer);
}
// Randomly connect/disconnect peers
while (true) {
int i = random.nextInt(runningPeers.size());
OnlinePeer peer = runningPeers.get(i);
if ((random.nextInt() & 0xf) != 0) {
// Connect
OnlinePeer otherPeer;
do {
int j = random.nextInt(runningPeers.size());
otherPeer = runningPeers.get(j);
} while (otherPeer == peer);
peer.connect(otherPeer);
} else {
peer.randomDisconnect();
}
Thread.sleep(100);
}
}
}

View File

@ -24,13 +24,13 @@ public class AddressesApiTests extends ApiCommon {
}
@Test
public void testGetProxying() {
assertNotNull(this.addressesResource.getProxying(Collections.singletonList(aliceAddress), null, null, null, null, null));
assertNotNull(this.addressesResource.getProxying(null, Collections.singletonList(aliceAddress), null, null, null, null));
assertNotNull(this.addressesResource.getProxying(Collections.singletonList(aliceAddress), Collections.singletonList(aliceAddress), null, null, null, null));
assertNotNull(this.addressesResource.getProxying(null, null, Collections.singletonList(aliceAddress), null, null, null));
assertNotNull(this.addressesResource.getProxying(Collections.singletonList(aliceAddress), Collections.singletonList(aliceAddress), Collections.singletonList(aliceAddress), null, null, null));
assertNotNull(this.addressesResource.getProxying(Collections.singletonList(aliceAddress), Collections.singletonList(aliceAddress), Collections.singletonList(aliceAddress), 1, 1, true));
public void testGetRewardShares() {
assertNotNull(this.addressesResource.getRewardShares(Collections.singletonList(aliceAddress), null, null, null, null, null));
assertNotNull(this.addressesResource.getRewardShares(null, Collections.singletonList(aliceAddress), null, null, null, null));
assertNotNull(this.addressesResource.getRewardShares(Collections.singletonList(aliceAddress), Collections.singletonList(aliceAddress), null, null, null, null));
assertNotNull(this.addressesResource.getRewardShares(null, null, Collections.singletonList(aliceAddress), null, null, null));
assertNotNull(this.addressesResource.getRewardShares(Collections.singletonList(aliceAddress), Collections.singletonList(aliceAddress), Collections.singletonList(aliceAddress), null, null, null));
assertNotNull(this.addressesResource.getRewardShares(Collections.singletonList(aliceAddress), Collections.singletonList(aliceAddress), Collections.singletonList(aliceAddress), 1, 1, true));
}
}

View File

@ -29,16 +29,16 @@ public class BlockApiTests extends ApiCommon {
public void testGetBlockForgers() {
List<String> addresses = Arrays.asList(aliceAddress, aliceAddress);
assertNotNull(this.blocksResource.getBlockForgers(Collections.emptyList(), null, null, null));
assertNotNull(this.blocksResource.getBlockForgers(addresses, null, null, null));
assertNotNull(this.blocksResource.getBlockForgers(Collections.emptyList(), 1, 1, true));
assertNotNull(this.blocksResource.getBlockForgers(addresses, 1, 1, true));
assertNotNull(this.blocksResource.getBlockMinters(Collections.emptyList(), null, null, null));
assertNotNull(this.blocksResource.getBlockMinters(addresses, null, null, null));
assertNotNull(this.blocksResource.getBlockMinters(Collections.emptyList(), 1, 1, true));
assertNotNull(this.blocksResource.getBlockMinters(addresses, 1, 1, true));
}
@Test
public void testGetBlocksByForger() {
assertNotNull(this.blocksResource.getBlocksByForger(aliceAddress, null, null, null));
assertNotNull(this.blocksResource.getBlocksByForger(aliceAddress, 1, 1, true));
assertNotNull(this.blocksResource.getBlocksByMinter(aliceAddress, null, null, null));
assertNotNull(this.blocksResource.getBlocksByMinter(aliceAddress, 1, 1, true));
}
}

View File

@ -10,6 +10,7 @@ import org.qora.repository.RepositoryManager;
import org.qora.test.common.AccountUtils;
import org.qora.test.common.AssetUtils;
import org.qora.test.common.Common;
import org.qora.utils.NTP;
import java.math.BigDecimal;
import java.util.Map;
@ -19,6 +20,7 @@ public class OldTradingTests extends Common {
@Before
public void beforeTest() throws DataException {
Common.useSettings("test-settings-old-asset.json");
NTP.testMode();
}
@After

View File

@ -5,11 +5,9 @@ import java.util.HashMap;
import java.util.Map;
import org.qora.account.PrivateKeyAccount;
import org.qora.crypto.Crypto;
import org.qora.data.transaction.BaseTransactionData;
import org.qora.data.transaction.EnableForgingTransactionData;
import org.qora.data.transaction.PaymentTransactionData;
import org.qora.data.transaction.ProxyForgingTransactionData;
import org.qora.data.transaction.RewardShareTransactionData;
import org.qora.data.transaction.TransactionData;
import org.qora.group.Group;
import org.qora.repository.DataException;
@ -30,60 +28,35 @@ public class AccountUtils {
BaseTransactionData baseTransactionData = new BaseTransactionData(timestamp, txGroupId, reference, sendingAccount.getPublicKey(), fee, null);
TransactionData transactionData = new PaymentTransactionData(baseTransactionData, recipientAccount.getAddress(), amount);
TransactionUtils.signAndForge(repository, transactionData, sendingAccount);
TransactionUtils.signAndMint(repository, transactionData, sendingAccount);
}
public static TransactionData createProxyForging(Repository repository, String forger, String recipient, BigDecimal share) throws DataException {
PrivateKeyAccount forgingAccount = Common.getTestAccount(repository, forger);
public static TransactionData createRewardShare(Repository repository, String minter, String recipient, BigDecimal sharePercent) throws DataException {
PrivateKeyAccount mintingAccount = Common.getTestAccount(repository, minter);
PrivateKeyAccount recipientAccount = Common.getTestAccount(repository, recipient);
byte[] reference = forgingAccount.getLastReference();
byte[] reference = mintingAccount.getLastReference();
long timestamp = repository.getTransactionRepository().fromSignature(reference).getTimestamp() + 1;
byte[] proxyPrivateKey = forgingAccount.getSharedSecret(recipientAccount.getPublicKey());
PrivateKeyAccount proxyAccount = new PrivateKeyAccount(null, proxyPrivateKey);
byte[] rewardSharePrivateKey = mintingAccount.getSharedSecret(recipientAccount.getPublicKey());
PrivateKeyAccount rewardShareAccount = new PrivateKeyAccount(null, rewardSharePrivateKey);
BaseTransactionData baseTransactionData = new BaseTransactionData(timestamp, txGroupId, reference, forgingAccount.getPublicKey(), fee, null);
TransactionData transactionData = new ProxyForgingTransactionData(baseTransactionData, recipientAccount.getAddress(), proxyAccount.getPublicKey(), share);
BaseTransactionData baseTransactionData = new BaseTransactionData(timestamp, txGroupId, reference, mintingAccount.getPublicKey(), fee, null);
TransactionData transactionData = new RewardShareTransactionData(baseTransactionData, recipientAccount.getAddress(), rewardShareAccount.getPublicKey(), sharePercent);
return transactionData;
}
public static byte[] proxyForging(Repository repository, String forger, String recipient, BigDecimal share) throws DataException {
TransactionData transactionData = createProxyForging(repository, forger, recipient, share);
public static byte[] rewardShare(Repository repository, String minter, String recipient, BigDecimal sharePercent) throws DataException {
TransactionData transactionData = createRewardShare(repository, minter, recipient, sharePercent);
PrivateKeyAccount forgingAccount = Common.getTestAccount(repository, forger);
TransactionUtils.signAndForge(repository, transactionData, forgingAccount);
PrivateKeyAccount rewardShareAccount = Common.getTestAccount(repository, minter);
TransactionUtils.signAndMint(repository, transactionData, rewardShareAccount);
PrivateKeyAccount recipientAccount = Common.getTestAccount(repository, recipient);
byte[] proxyPrivateKey = forgingAccount.getSharedSecret(recipientAccount.getPublicKey());
byte[] rewardSharePrivateKey = rewardShareAccount.getSharedSecret(recipientAccount.getPublicKey());
return proxyPrivateKey;
}
public static TransactionData createEnableForging(Repository repository, String forger, byte[] recipientPublicKey) throws DataException {
PrivateKeyAccount forgingAccount = Common.getTestAccount(repository, forger);
byte[] reference = forgingAccount.getLastReference();
long timestamp = repository.getTransactionRepository().fromSignature(reference).getTimestamp() + 1;
BaseTransactionData baseTransactionData = new BaseTransactionData(timestamp, txGroupId, reference, forgingAccount.getPublicKey(), fee, null);
return new EnableForgingTransactionData(baseTransactionData, Crypto.toAddress(recipientPublicKey));
}
public static TransactionData createEnableForging(Repository repository, String forger, String recipient) throws DataException {
PrivateKeyAccount recipientAccount = Common.getTestAccount(repository, recipient);
return createEnableForging(repository, forger, recipientAccount.getPublicKey());
}
public static TransactionData enableForging(Repository repository, String forger, String recipient) throws DataException {
TransactionData transactionData = createEnableForging(repository, forger, recipient);
PrivateKeyAccount forgingAccount = Common.getTestAccount(repository, forger);
TransactionUtils.signAndForge(repository, transactionData, forgingAccount);
return transactionData;
return rewardSharePrivateKey;
}
public static Map<String, Map<Long, BigDecimal>> getBalances(Repository repository, long... assetIds) throws DataException {

View File

@ -41,7 +41,7 @@ public class AssetUtils {
BaseTransactionData baseTransactionData = new BaseTransactionData(timestamp, AssetUtils.txGroupId, reference, account.getPublicKey(), AssetUtils.fee, null);
TransactionData transactionData = new IssueAssetTransactionData(baseTransactionData, account.getAddress(), assetName, "desc", quantity, isDivisible, "{}", false);
TransactionUtils.signAndForge(repository, transactionData, account);
TransactionUtils.signAndMint(repository, transactionData, account);
return repository.getAssetRepository().fromAssetName(assetName).getAssetId();
}
@ -56,7 +56,7 @@ public class AssetUtils {
BaseTransactionData baseTransactionData = new BaseTransactionData(timestamp, AssetUtils.txGroupId, reference, fromAccount.getPublicKey(), AssetUtils.fee, null);
TransactionData transactionData = new TransferAssetTransactionData(baseTransactionData, toAccount.getAddress(), amount, assetId);
TransactionUtils.signAndForge(repository, transactionData, fromAccount);
TransactionUtils.signAndMint(repository, transactionData, fromAccount);
}
public static byte[] createOrder(Repository repository, String accountName, long haveAssetId, long wantAssetId, BigDecimal amount, BigDecimal price) throws DataException {
@ -68,7 +68,7 @@ public class AssetUtils {
BaseTransactionData baseTransactionData = new BaseTransactionData(timestamp, AssetUtils.txGroupId, reference, account.getPublicKey(), AssetUtils.fee, null);
TransactionData transactionData = new CreateAssetOrderTransactionData(baseTransactionData, haveAssetId, wantAssetId, amount, price);
TransactionUtils.signAndForge(repository, transactionData, account);
TransactionUtils.signAndMint(repository, transactionData, account);
return repository.getAssetRepository().getAccountsOrders(account.getPublicKey(), null, null, null, null, true).get(0).getOrderId();
}
@ -89,7 +89,7 @@ public class AssetUtils {
PrivateKeyAccount account = Common.getTestAccount(repository, accountName);
Transaction transaction = buildCancelOrder(repository, accountName, orderId);
TransactionUtils.signAndForge(repository, transaction.getTransactionData(), account);
TransactionUtils.signAndMint(repository, transaction.getTransactionData(), account);
}
public static void genericTradeTest(long haveAssetId, long wantAssetId,

View File

@ -72,18 +72,21 @@ public class Common {
private static Map<String, TestAccount> testAccountsByName = new HashMap<>();
static {
testAccountsByName.put("alice", new TestAccount(null, "alice", "A9MNsATgQgruBUjxy2rjWY36Yf19uRioKZbiLFT2P7c6"));
testAccountsByName.put("bob", new TestAccount(null, "bob", "AdTd9SUEYSdTW8mgK3Gu72K97bCHGdUwi2VvLNjUohot"));
testAccountsByName.put("chloe", new TestAccount(null, "chloe", "HqVngdE1AmEyDpfwTZqUdFHB13o4bCmpoTNAKEqki66K"));
testAccountsByName.put("dilbert", new TestAccount(null, "dilbert", "Gakhh6Ln4vtBFM88nE9JmDaLBDtUBg51aVFpWfSkyVw5"));
testAccountsByName.put("alice", new TestAccount(null, "alice", "A9MNsATgQgruBUjxy2rjWY36Yf19uRioKZbiLFT2P7c6", false));
testAccountsByName.put("bob", new TestAccount(null, "bob", "AdTd9SUEYSdTW8mgK3Gu72K97bCHGdUwi2VvLNjUohot", false));
testAccountsByName.put("chloe", new TestAccount(null, "chloe", "HqVngdE1AmEyDpfwTZqUdFHB13o4bCmpoTNAKEqki66K", false));
testAccountsByName.put("dilbert", new TestAccount(null, "dilbert", "Gakhh6Ln4vtBFM88nE9JmDaLBDtUBg51aVFpWfSkyVw5", false));
// Alice reward-share with herself. Private key is reward-share private key, derived from Alice's private and public keys.
testAccountsByName.put("alice-reward-share", new TestAccount(null, "alice-reward-share", "1CeDCg9TSdBwJNGVTGG7pCKsvsyyoEcaVXYvDT1Xb9f", true));
}
public static TestAccount getTestAccount(Repository repository, String name) {
return new TestAccount(repository, name, testAccountsByName.get(name).getPrivateKey());
return new TestAccount(repository, testAccountsByName.get(name));
}
public static List<TestAccount> getTestAccounts(Repository repository) {
return testAccountsByName.values().stream().map(account -> new TestAccount(repository, account.accountName, account.getPrivateKey())).collect(Collectors.toList());
return testAccountsByName.values().stream().map(account -> new TestAccount(repository, account)).collect(Collectors.toList());
}
public static void useSettings(String settingsFilename) throws DataException {
@ -116,7 +119,8 @@ public class Common {
// Check that each test account can fetch their last reference
for (TestAccount testAccount : getTestAccounts(repository))
assertNotNull(String.format("Test account %s / %s should have last reference", testAccount.accountName, testAccount.getAddress()), testAccount.getLastReference());
if (!testAccount.isRewardShare)
assertNotNull(String.format("Test account %s / %s should have last reference", testAccount.accountName, testAccount.getAddress()), testAccount.getLastReference());
}
}

View File

@ -30,7 +30,7 @@ public class GroupUtils {
BaseTransactionData baseTransactionData = new BaseTransactionData(timestamp, Group.NO_GROUP, reference, account.getPublicKey(), GroupUtils.fee, null);
TransactionData transactionData = new CreateGroupTransactionData(baseTransactionData, account.getAddress(), groupName, groupDescription, isOpen, approvalThreshold, minimumBlockDelay, maximumBlockDelay);
TransactionUtils.signAndForge(repository, transactionData, account);
TransactionUtils.signAndMint(repository, transactionData, account);
return repository.getGroupRepository().fromGroupName(groupName).getGroupId();
}
@ -44,7 +44,7 @@ public class GroupUtils {
BaseTransactionData baseTransactionData = new BaseTransactionData(timestamp, Group.NO_GROUP, reference, account.getPublicKey(), GroupUtils.fee, null);
TransactionData transactionData = new JoinGroupTransactionData(baseTransactionData, groupId);
TransactionUtils.signAndForge(repository, transactionData, account);
TransactionUtils.signAndMint(repository, transactionData, account);
}
public static void approveTransaction(Repository repository, String accountName, byte[] pendingSignature, boolean decision) throws DataException {
@ -56,7 +56,7 @@ public class GroupUtils {
BaseTransactionData baseTransactionData = new BaseTransactionData(timestamp, Group.NO_GROUP, reference, account.getPublicKey(), GroupUtils.fee, null);
TransactionData transactionData = new GroupApprovalTransactionData(baseTransactionData, pendingSignature, decision);
TransactionUtils.signAndForge(repository, transactionData, account);
TransactionUtils.signAndMint(repository, transactionData, account);
}
public static ApprovalStatus getApprovalStatus(Repository repository, byte[] signature) throws DataException {

View File

@ -7,15 +7,17 @@ import org.qora.utils.Base58;
public class TestAccount extends PrivateKeyAccount {
public final String accountName;
public final boolean isRewardShare;
public TestAccount(Repository repository, String accountName, byte[] privateKey) {
super(repository, privateKey);
public TestAccount(Repository repository, String accountName, String privateKey, boolean isRewardShare) {
super(repository, Base58.decode(privateKey));
this.accountName = accountName;
this.isRewardShare = isRewardShare;
}
public TestAccount(Repository repository, String accountName, String privateKey) {
this(repository, accountName, Base58.decode(privateKey));
public TestAccount(Repository repository, TestAccount testAccount) {
this(repository, testAccount.accountName, Base58.encode(testAccount.getPrivateKey()), testAccount.isRewardShare);
}
}

View File

@ -7,7 +7,7 @@ import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import org.qora.account.PrivateKeyAccount;
import org.qora.block.BlockGenerator;
import org.qora.block.BlockMinter;
import org.qora.data.transaction.TransactionData;
import org.qora.repository.DataException;
import org.qora.repository.Repository;
@ -37,12 +37,12 @@ public class TransactionUtils {
}
/** Signs transaction using given account and forges a new block, using "alice" account. */
public static void signAndForge(Repository repository, TransactionData transactionData, PrivateKeyAccount signingAccount) throws DataException {
public static void signAndMint(Repository repository, TransactionData transactionData, PrivateKeyAccount signingAccount) throws DataException {
signAsUnconfirmed(repository, transactionData, signingAccount);
// Generate block
PrivateKeyAccount generatorAccount = Common.getTestAccount(repository, "alice");
BlockGenerator.generateTestingBlock(repository, generatorAccount);
PrivateKeyAccount minterAccount = Common.getTestAccount(repository, "alice-reward-share");
BlockMinter.mintTestingBlock(repository, minterAccount);
}
public static TransactionData randomTransaction(Repository repository, PrivateKeyAccount account, TransactionType txType, boolean wantValid) throws DataException {

View File

@ -1,17 +0,0 @@
package org.qora.test.common.transaction;
import org.qora.account.PrivateKeyAccount;
import org.qora.data.transaction.EnableForgingTransactionData;
import org.qora.data.transaction.TransactionData;
import org.qora.repository.DataException;
import org.qora.repository.Repository;
public class EnableForgingTestTransaction extends TestTransaction {
public static TransactionData randomTransaction(Repository repository, PrivateKeyAccount account, boolean wantValid) throws DataException {
String target = account.getAddress();
return new EnableForgingTransactionData(generateBase(account), target);
}
}

View File

@ -3,19 +3,19 @@ package org.qora.test.common.transaction;
import java.math.BigDecimal;
import org.qora.account.PrivateKeyAccount;
import org.qora.data.transaction.ProxyForgingTransactionData;
import org.qora.data.transaction.RewardShareTransactionData;
import org.qora.data.transaction.TransactionData;
import org.qora.repository.DataException;
import org.qora.repository.Repository;
public class ProxyForgingTestTransaction extends TestTransaction {
public class RewardShareTestTransaction extends TestTransaction {
public static TransactionData randomTransaction(Repository repository, PrivateKeyAccount account, boolean wantValid) throws DataException {
String recipient = account.getAddress();
byte[] proxyPublicKey = account.getProxyPrivateKey(account.getPublicKey());
BigDecimal share = BigDecimal.valueOf(50);
byte[] rewardSharePublicKey = account.getRewardSharePrivateKey(account.getPublicKey());
BigDecimal sharePercent = BigDecimal.valueOf(50);
return new ProxyForgingTransactionData(generateBase(account), recipient, proxyPublicKey, share);
return new RewardShareTransactionData(generateBase(account), recipient, rewardSharePublicKey, sharePercent);
}
}

View File

@ -1,127 +0,0 @@
package org.qora.test.forging;
import static org.junit.Assert.*;
import java.math.BigDecimal;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.qora.account.PrivateKeyAccount;
import org.qora.data.account.ProxyForgerData;
import org.qora.data.transaction.TransactionData;
import org.qora.repository.DataException;
import org.qora.repository.Repository;
import org.qora.repository.RepositoryManager;
import org.qora.test.common.AccountUtils;
import org.qora.test.common.BlockUtils;
import org.qora.test.common.Common;
import org.qora.transaction.Transaction;
import org.qora.transaction.Transaction.ValidationResult;
import org.qora.utils.Base58;
public class ProxyForgingTests extends Common {
@Before
public void beforeTest() throws DataException {
Common.useDefaultSettings();
}
@After
public void afterTest() throws DataException {
Common.orphanCheck();
}
@Test
public void testCreateRelationship() throws DataException {
final BigDecimal share = new BigDecimal("12.8");
try (final Repository repository = RepositoryManager.getRepository()) {
PrivateKeyAccount aliceAccount = Common.getTestAccount(repository, "alice");
PrivateKeyAccount bobAccount = Common.getTestAccount(repository, "bob");
// Create relationship
byte[] proxyPrivateKey = AccountUtils.proxyForging(repository, "alice", "bob", share);
PrivateKeyAccount proxyAccount = new PrivateKeyAccount(repository, proxyPrivateKey);
// Confirm relationship info set correctly
// Fetch using proxy public key
ProxyForgerData proxyForgerData = repository.getAccountRepository().getProxyForgeData(proxyAccount.getPublicKey());
assertEquals("Incorrect generator public key", Base58.encode(aliceAccount.getPublicKey()), Base58.encode(proxyForgerData.getForgerPublicKey()));
assertEquals("Incorrect recipient", bobAccount.getAddress(), proxyForgerData.getRecipient());
assertEqualBigDecimals("Incorrect reward share", share, proxyForgerData.getShare());
// Fetch using generator public key and recipient address combination
proxyForgerData = repository.getAccountRepository().getProxyForgeData(aliceAccount.getPublicKey(), bobAccount.getAddress());
assertEquals("Incorrect generator public key", Base58.encode(aliceAccount.getPublicKey()), Base58.encode(proxyForgerData.getForgerPublicKey()));
assertEquals("Incorrect recipient", bobAccount.getAddress(), proxyForgerData.getRecipient());
assertEqualBigDecimals("Incorrect reward share", share, proxyForgerData.getShare());
// Delete relationship
byte[] newProxyPrivateKey = AccountUtils.proxyForging(repository, "alice", "bob", BigDecimal.ZERO);
PrivateKeyAccount newProxyAccount = new PrivateKeyAccount(repository, newProxyPrivateKey);
// Confirm proxy keys match
assertEquals("Proxy private keys differ", Base58.encode(proxyPrivateKey), Base58.encode(newProxyPrivateKey));
assertEquals("Proxy public keys differ", Base58.encode(proxyAccount.getPublicKey()), Base58.encode(newProxyAccount.getPublicKey()));
// Confirm relationship no longer exists in repository
// Fetch using proxy public key
ProxyForgerData newProxyForgerData = repository.getAccountRepository().getProxyForgeData(proxyAccount.getPublicKey());
assertNull("Proxy relationship data shouldn't exist", newProxyForgerData);
// Fetch using generator public key and recipient address combination
newProxyForgerData = repository.getAccountRepository().getProxyForgeData(aliceAccount.getPublicKey(), bobAccount.getAddress());
assertNull("Proxy relationship data shouldn't exist", newProxyForgerData);
// Orphan last block to restore prior proxy relationship
BlockUtils.orphanLastBlock(repository);
// Confirm proxy relationship restored correctly
// Fetch using proxy public key
newProxyForgerData = repository.getAccountRepository().getProxyForgeData(proxyAccount.getPublicKey());
assertNotNull("Proxy relationship data should have been restored", newProxyForgerData);
assertEquals("Incorrect generator public key", Base58.encode(aliceAccount.getPublicKey()), Base58.encode(newProxyForgerData.getForgerPublicKey()));
assertEquals("Incorrect recipient", bobAccount.getAddress(), newProxyForgerData.getRecipient());
assertEqualBigDecimals("Incorrect reward share", share, newProxyForgerData.getShare());
// Fetch using generator public key and recipient address combination
newProxyForgerData = repository.getAccountRepository().getProxyForgeData(aliceAccount.getPublicKey(), bobAccount.getAddress());
assertNotNull("Proxy relationship data should have been restored", newProxyForgerData);
assertEquals("Incorrect generator public key", Base58.encode(aliceAccount.getPublicKey()), Base58.encode(newProxyForgerData.getForgerPublicKey()));
assertEquals("Incorrect recipient", bobAccount.getAddress(), newProxyForgerData.getRecipient());
assertEqualBigDecimals("Incorrect reward share", share, newProxyForgerData.getShare());
// Orphan another block to remove initial proxy relationship
BlockUtils.orphanLastBlock(repository);
// Confirm proxy relationship no longer exists
// Fetch using proxy public key
newProxyForgerData = repository.getAccountRepository().getProxyForgeData(proxyAccount.getPublicKey());
assertNull("Proxy relationship data shouldn't exist", newProxyForgerData);
// Fetch using generator public key and recipient address combination
newProxyForgerData = repository.getAccountRepository().getProxyForgeData(aliceAccount.getPublicKey(), bobAccount.getAddress());
assertNull("Proxy relationship data shouldn't exist", newProxyForgerData);
}
}
@Test
public void testZeroInitialShareInvalid() throws DataException {
try (final Repository repository = RepositoryManager.getRepository()) {
// Create invalid PROXY_FORGING transaction with initial 0% reward share
TransactionData transactionData = AccountUtils.createProxyForging(repository, "alice", "bob", BigDecimal.ZERO);
// Confirm transaction is invalid
Transaction transaction = Transaction.fromData(repository, transactionData);
ValidationResult validationResult = transaction.isValidUnconfirmed();
assertEquals("Initial 0% share should be invalid", ValidationResult.INVALID_FORGE_SHARE, validationResult);
}
}
}

View File

@ -0,0 +1,127 @@
package org.qora.test.forging;
import static org.junit.Assert.*;
import java.math.BigDecimal;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.qora.account.PrivateKeyAccount;
import org.qora.data.account.RewardShareData;
import org.qora.data.transaction.TransactionData;
import org.qora.repository.DataException;
import org.qora.repository.Repository;
import org.qora.repository.RepositoryManager;
import org.qora.test.common.AccountUtils;
import org.qora.test.common.BlockUtils;
import org.qora.test.common.Common;
import org.qora.transaction.Transaction;
import org.qora.transaction.Transaction.ValidationResult;
import org.qora.utils.Base58;
public class RewardShareTests extends Common {
@Before
public void beforeTest() throws DataException {
Common.useDefaultSettings();
}
@After
public void afterTest() throws DataException {
Common.orphanCheck();
}
@Test
public void testCreateRewardShare() throws DataException {
final BigDecimal sharePercent = new BigDecimal("12.8");
try (final Repository repository = RepositoryManager.getRepository()) {
PrivateKeyAccount aliceAccount = Common.getTestAccount(repository, "alice");
PrivateKeyAccount bobAccount = Common.getTestAccount(repository, "bob");
// Create reward-share
byte[] rewardSharePrivateKey = AccountUtils.rewardShare(repository, "alice", "bob", sharePercent);
PrivateKeyAccount rewardShareAccount = new PrivateKeyAccount(repository, rewardSharePrivateKey);
// Confirm reward-share info set correctly
// Fetch using reward-share public key
RewardShareData rewardShareData = repository.getAccountRepository().getRewardShare(rewardShareAccount.getPublicKey());
assertEquals("Incorrect minter public key", Base58.encode(aliceAccount.getPublicKey()), Base58.encode(rewardShareData.getMinterPublicKey()));
assertEquals("Incorrect recipient", bobAccount.getAddress(), rewardShareData.getRecipient());
assertEqualBigDecimals("Incorrect share percentage", sharePercent, rewardShareData.getSharePercent());
// Fetch using minter public key and recipient address combination
rewardShareData = repository.getAccountRepository().getRewardShare(aliceAccount.getPublicKey(), bobAccount.getAddress());
assertEquals("Incorrect minter public key", Base58.encode(aliceAccount.getPublicKey()), Base58.encode(rewardShareData.getMinterPublicKey()));
assertEquals("Incorrect recipient", bobAccount.getAddress(), rewardShareData.getRecipient());
assertEqualBigDecimals("Incorrect share percentage", sharePercent, rewardShareData.getSharePercent());
// Delete reward-share
byte[] newRewardSharePrivateKey = AccountUtils.rewardShare(repository, "alice", "bob", BigDecimal.ZERO);
PrivateKeyAccount newRewardShareAccount = new PrivateKeyAccount(repository, newRewardSharePrivateKey);
// Confirm reward-share keys match
assertEquals("Reward-share private keys differ", Base58.encode(rewardSharePrivateKey), Base58.encode(newRewardSharePrivateKey));
assertEquals("Reward-share public keys differ", Base58.encode(rewardShareAccount.getPublicKey()), Base58.encode(newRewardShareAccount.getPublicKey()));
// Confirm reward-share no longer exists in repository
// Fetch using reward-share public key
RewardShareData newRewardShareData = repository.getAccountRepository().getRewardShare(rewardShareAccount.getPublicKey());
assertNull("Reward-share shouldn't exist", newRewardShareData);
// Fetch using minter public key and recipient address combination
newRewardShareData = repository.getAccountRepository().getRewardShare(aliceAccount.getPublicKey(), bobAccount.getAddress());
assertNull("Reward-share shouldn't exist", newRewardShareData);
// Orphan last block to restore prior reward-share
BlockUtils.orphanLastBlock(repository);
// Confirm reward-share restored correctly
// Fetch using reward-share public key
newRewardShareData = repository.getAccountRepository().getRewardShare(rewardShareAccount.getPublicKey());
assertNotNull("Reward-share should have been restored", newRewardShareData);
assertEquals("Incorrect minter public key", Base58.encode(aliceAccount.getPublicKey()), Base58.encode(newRewardShareData.getMinterPublicKey()));
assertEquals("Incorrect recipient", bobAccount.getAddress(), newRewardShareData.getRecipient());
assertEqualBigDecimals("Incorrect share percentage", sharePercent, newRewardShareData.getSharePercent());
// Fetch using minter public key and recipient address combination
newRewardShareData = repository.getAccountRepository().getRewardShare(aliceAccount.getPublicKey(), bobAccount.getAddress());
assertNotNull("Reward-share should have been restored", newRewardShareData);
assertEquals("Incorrect minter public key", Base58.encode(aliceAccount.getPublicKey()), Base58.encode(newRewardShareData.getMinterPublicKey()));
assertEquals("Incorrect recipient", bobAccount.getAddress(), newRewardShareData.getRecipient());
assertEqualBigDecimals("Incorrect share percentage", sharePercent, newRewardShareData.getSharePercent());
// Orphan another block to remove initial reward-share
BlockUtils.orphanLastBlock(repository);
// Confirm reward-share no longer exists
// Fetch using reward-share public key
newRewardShareData = repository.getAccountRepository().getRewardShare(rewardShareAccount.getPublicKey());
assertNull("Reward-share shouldn't exist", newRewardShareData);
// Fetch using minter public key and recipient address combination
newRewardShareData = repository.getAccountRepository().getRewardShare(aliceAccount.getPublicKey(), bobAccount.getAddress());
assertNull("Reward-share shouldn't exist", newRewardShareData);
}
}
@Test
public void testZeroInitialShareInvalid() throws DataException {
try (final Repository repository = RepositoryManager.getRepository()) {
// Create invalid REWARD_SHARE transaction with initial 0% reward share
TransactionData transactionData = AccountUtils.createRewardShare(repository, "alice", "bob", BigDecimal.ZERO);
// Confirm transaction is invalid
Transaction transaction = Transaction.fromData(repository, transactionData);
ValidationResult validationResult = transaction.isValidUnconfirmed();
assertEquals("Initial 0% share should be invalid", ValidationResult.INVALID_REWARD_SHARE_PERCENT, validationResult);
}
}
}

View File

@ -12,7 +12,7 @@ import org.qora.account.PrivateKeyAccount;
import org.qora.asset.Asset;
import org.qora.block.BlockChain;
import org.qora.block.BlockChain.RewardByHeight;
import org.qora.block.BlockGenerator;
import org.qora.block.BlockMinter;
import org.qora.repository.DataException;
import org.qora.repository.Repository;
import org.qora.repository.RepositoryManager;
@ -41,7 +41,7 @@ public class RewardTests extends Common {
BigDecimal blockReward = BlockUtils.getNextBlockReward(repository);
BlockGenerator.generateTestingBlock(repository, forgingAccount);
BlockMinter.mintTestingBlock(repository, forgingAccount);
BigDecimal expectedBalance = initialBalances.get("alice").get(Asset.QORT).add(blockReward);
AccountUtils.assertBalance(repository, "alice", Asset.QORT, expectedBalance);
@ -68,7 +68,7 @@ public class RewardTests extends Common {
rewardInfo = rewards.get(rewardIndex);
}
BlockGenerator.generateTestingBlock(repository, forgingAccount);
BlockMinter.mintTestingBlock(repository, forgingAccount);
expectedBalance = expectedBalance.add(rewardInfo.reward);
}
@ -81,12 +81,12 @@ public class RewardTests extends Common {
final BigDecimal share = new BigDecimal("12.8");
try (final Repository repository = RepositoryManager.getRepository()) {
byte[] proxyPrivateKey = AccountUtils.proxyForging(repository, "alice", "bob", share);
byte[] proxyPrivateKey = AccountUtils.rewardShare(repository, "alice", "bob", share);
PrivateKeyAccount proxyAccount = new PrivateKeyAccount(repository, proxyPrivateKey);
Map<String, Map<Long, BigDecimal>> initialBalances = AccountUtils.getBalances(repository, Asset.QORT);
BigDecimal blockReward = BlockUtils.getNextBlockReward(repository);
BlockGenerator.generateTestingBlock(repository, proxyAccount);
BlockMinter.mintTestingBlock(repository, proxyAccount);
// We're expecting reward * 12.8% to Bob, the rest to Alice

View File

@ -5,7 +5,7 @@ import org.junit.Before;
import org.junit.Test;
import org.qora.account.PrivateKeyAccount;
import org.qora.asset.Asset;
import org.qora.block.BlockGenerator;
import org.qora.block.BlockMinter;
import org.qora.data.transaction.BaseTransactionData;
import org.qora.data.transaction.IssueAssetTransactionData;
import org.qora.data.transaction.PaymentTransactionData;
@ -69,7 +69,7 @@ public class GroupApprovalTests extends Common {
int groupId = GroupUtils.createGroup(repository, "alice", "test", true, ApprovalThreshold.ONE, minBlockDelay, maxBlockDelay);
Transaction transaction = buildIssueAssetTransaction(repository, "alice", groupId);
TransactionUtils.signAndForge(repository, transaction.getTransactionData(), aliceAccount);
TransactionUtils.signAndMint(repository, transaction.getTransactionData(), aliceAccount);
// Confirm transaction doesn't need approval
ApprovalStatus approvalStatus = GroupUtils.getApprovalStatus(repository, transaction.getTransactionData().getSignature());
@ -95,7 +95,7 @@ public class GroupApprovalTests extends Common {
BigDecimal blockReward = BlockUtils.getNextBlockReward(repository);
Transaction bobAssetTransaction = buildIssueAssetTransaction(repository, "bob", groupId);
TransactionUtils.signAndForge(repository, bobAssetTransaction.getTransactionData(), bobAccount);
TransactionUtils.signAndMint(repository, bobAssetTransaction.getTransactionData(), bobAccount);
// Confirm transaction needs approval, and hasn't been approved
ApprovalStatus approvalStatus = GroupUtils.getApprovalStatus(repository, bobAssetTransaction.getTransactionData().getSignature());
@ -115,7 +115,7 @@ public class GroupApprovalTests extends Common {
// Have Bob do a non-approval transaction to change his last-reference
Transaction bobPaymentTransaction = buildPaymentTransaction(repository, "bob", "chloe", amount, Group.NO_GROUP);
TransactionUtils.signAndForge(repository, bobPaymentTransaction.getTransactionData(), bobAccount);
TransactionUtils.signAndMint(repository, bobPaymentTransaction.getTransactionData(), bobAccount);
byte[] bobPostPaymentReference = bobAccount.getLastReference();
assertFalse("reference should have changed", Arrays.equals(bobPostAssetReference, bobPostPaymentReference));
@ -125,7 +125,7 @@ public class GroupApprovalTests extends Common {
// Now forge a few blocks so transaction is approved
for (int blockCount = 0; blockCount < minBlockDelay; ++blockCount)
BlockGenerator.generateTestingBlock(repository, aliceAccount);
BlockMinter.mintTestingBlock(repository, aliceAccount);
// Confirm transaction now approved
approvalStatus = GroupUtils.getApprovalStatus(repository, bobAssetTransaction.getTransactionData().getSignature());
@ -184,7 +184,7 @@ public class GroupApprovalTests extends Common {
// Bob's issue-asset transaction needs group-approval
Transaction bobAssetTransaction = buildIssueAssetTransaction(repository, "bob", groupId);
TransactionUtils.signAndForge(repository, bobAssetTransaction.getTransactionData(), bobAccount);
TransactionUtils.signAndMint(repository, bobAssetTransaction.getTransactionData(), bobAccount);
// Confirm transaction needs approval, and hasn't been approved
ApprovalStatus approvalStatus = GroupUtils.getApprovalStatus(repository, bobAssetTransaction.getTransactionData().getSignature());
@ -199,7 +199,7 @@ public class GroupApprovalTests extends Common {
// Now forge a few blocks so transaction is approved
for (int blockCount = 0; blockCount < minBlockDelay; ++blockCount)
BlockGenerator.generateTestingBlock(repository, aliceAccount);
BlockMinter.mintTestingBlock(repository, aliceAccount);
// Confirm transaction now approved
approvalStatus = GroupUtils.getApprovalStatus(repository, bobAssetTransaction.getTransactionData().getSignature());
@ -246,7 +246,7 @@ public class GroupApprovalTests extends Common {
// Bob's issue-asset transaction needs group-approval
Transaction bobAssetTransaction = buildIssueAssetTransaction(repository, "bob", groupId);
TransactionUtils.signAndForge(repository, bobAssetTransaction.getTransactionData(), bobAccount);
TransactionUtils.signAndMint(repository, bobAssetTransaction.getTransactionData(), bobAccount);
// Confirm transaction needs approval, and hasn't been approved
ApprovalStatus approvalStatus = GroupUtils.getApprovalStatus(repository, bobAssetTransaction.getTransactionData().getSignature());
@ -261,7 +261,7 @@ public class GroupApprovalTests extends Common {
// Now forge a few blocks so transaction is approved
for (int blockCount = 0; blockCount < minBlockDelay; ++blockCount)
BlockGenerator.generateTestingBlock(repository, aliceAccount);
BlockMinter.mintTestingBlock(repository, aliceAccount);
// Confirm transaction now rejected
approvalStatus = GroupUtils.getApprovalStatus(repository, bobAssetTransaction.getTransactionData().getSignature());
@ -308,7 +308,7 @@ public class GroupApprovalTests extends Common {
// Bob's issue-asset transaction needs group-approval
Transaction bobAssetTransaction = buildIssueAssetTransaction(repository, "bob", groupId);
TransactionUtils.signAndForge(repository, bobAssetTransaction.getTransactionData(), bobAccount);
TransactionUtils.signAndMint(repository, bobAssetTransaction.getTransactionData(), bobAccount);
// Confirm transaction needs approval, and hasn't been approved
ApprovalStatus approvalStatus = GroupUtils.getApprovalStatus(repository, bobAssetTransaction.getTransactionData().getSignature());
@ -320,7 +320,7 @@ public class GroupApprovalTests extends Common {
// Now forge a few blocks so group-approval for transaction expires
for (int blockCount = 0; blockCount <= maxBlockDelay; ++blockCount)
BlockGenerator.generateTestingBlock(repository, aliceAccount);
BlockMinter.mintTestingBlock(repository, aliceAccount);
// Confirm transaction now expired
approvalStatus = GroupUtils.getApprovalStatus(repository, bobAssetTransaction.getTransactionData().getSignature());
@ -356,7 +356,7 @@ public class GroupApprovalTests extends Common {
// Bob's issue-asset transaction needs group-approval
Transaction bobAssetTransaction = buildIssueAssetTransaction(repository, "bob", groupId);
TransactionUtils.signAndForge(repository, bobAssetTransaction.getTransactionData(), bobAccount);
TransactionUtils.signAndMint(repository, bobAssetTransaction.getTransactionData(), bobAccount);
// Confirm transaction needs approval, and hasn't been approved
ApprovalStatus approvalStatus = GroupUtils.getApprovalStatus(repository, bobAssetTransaction.getTransactionData().getSignature());
@ -372,7 +372,7 @@ public class GroupApprovalTests extends Common {
// But wait! Alice issues an asset with the same name before Bob's asset is issued!
// This transaction will be auto-approved as Alice is the group owner (and admin)
Transaction aliceAssetTransaction = buildIssueAssetTransaction(repository, "alice", groupId);
TransactionUtils.signAndForge(repository, aliceAssetTransaction.getTransactionData(), aliceAccount);
TransactionUtils.signAndMint(repository, aliceAssetTransaction.getTransactionData(), aliceAccount);
// Confirm Alice's transaction auto-approved
approvalStatus = GroupUtils.getApprovalStatus(repository, aliceAssetTransaction.getTransactionData().getSignature());
@ -380,7 +380,7 @@ public class GroupApprovalTests extends Common {
// Now forge a few blocks so transaction is approved
for (int blockCount = 0; blockCount < minBlockDelay; ++blockCount)
BlockGenerator.generateTestingBlock(repository, aliceAccount);
BlockMinter.mintTestingBlock(repository, aliceAccount);
// Confirm Bob's transaction now invalid
approvalStatus = GroupUtils.getApprovalStatus(repository, bobAssetTransaction.getTransactionData().getSignature());

View File

@ -9,7 +9,7 @@ import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.qora.account.PrivateKeyAccount;
import org.qora.block.BlockGenerator;
import org.qora.block.BlockMinter;
import org.qora.data.naming.NameData;
import org.qora.data.transaction.BuyNameTransactionData;
import org.qora.data.transaction.RegisterNameTransactionData;
@ -60,7 +60,7 @@ public class OrphaningTests extends Common {
public void testRegisterName() throws DataException {
// Register-name
RegisterNameTransactionData transactionData = new RegisterNameTransactionData(TestTransaction.generateBase(alice), alice.getAddress(), name, "{}");
TransactionUtils.signAndForge(repository, transactionData, alice);
TransactionUtils.signAndMint(repository, transactionData, alice);
String name = transactionData.getName();
@ -74,7 +74,7 @@ public class OrphaningTests extends Common {
assertFalse(repository.getNameRepository().nameExists(name));
// Re-process register-name
BlockGenerator.generateTestingBlock(repository, alice);
BlockMinter.mintTestingBlock(repository, alice);
// Check name does exist
assertTrue(repository.getNameRepository().nameExists(name));
@ -87,7 +87,7 @@ public class OrphaningTests extends Common {
// Sell-name
SellNameTransactionData transactionData = new SellNameTransactionData(TestTransaction.generateBase(alice), name, price);
TransactionUtils.signAndForge(repository, transactionData, alice);
TransactionUtils.signAndMint(repository, transactionData, alice);
NameData nameData;
@ -105,7 +105,7 @@ public class OrphaningTests extends Common {
// Not concerned about price
// Re-process sell-name
BlockGenerator.generateTestingBlock(repository, alice);
BlockMinter.mintTestingBlock(repository, alice);
// Check name is for sale
nameData = repository.getNameRepository().fromName(name);
@ -121,10 +121,10 @@ public class OrphaningTests extends Common {
assertNull(nameData);
// Re-process register-name and sell-name
BlockGenerator.generateTestingBlock(repository, alice);
BlockMinter.mintTestingBlock(repository, alice);
// Unconfirmed sell-name transaction not included in previous block
// as it isn't valid until name exists thanks to register-name transaction.
BlockGenerator.generateTestingBlock(repository, alice);
BlockMinter.mintTestingBlock(repository, alice);
// Check name does exist
assertTrue(repository.getNameRepository().nameExists(name));
@ -144,7 +144,7 @@ public class OrphaningTests extends Common {
// Buy-name
BuyNameTransactionData transactionData = new BuyNameTransactionData(TestTransaction.generateBase(bob), name, price, seller);
TransactionUtils.signAndForge(repository, transactionData, bob);
TransactionUtils.signAndMint(repository, transactionData, bob);
NameData nameData;
@ -162,7 +162,7 @@ public class OrphaningTests extends Common {
assertEqualBigDecimals("price incorrect", price, nameData.getSalePrice());
// Re-process buy-name
BlockGenerator.generateTestingBlock(repository, alice);
BlockMinter.mintTestingBlock(repository, alice);
// Check name is sold
nameData = repository.getNameRepository().fromName(name);
@ -180,10 +180,10 @@ public class OrphaningTests extends Common {
assertEquals(alice.getAddress(), nameData.getOwner());
// Re-process sell-name and buy-name
BlockGenerator.generateTestingBlock(repository, alice);
BlockMinter.mintTestingBlock(repository, alice);
// Unconfirmed buy-name transaction not included in previous block
// as it isn't valid until name is for sale thanks to sell-name transaction.
BlockGenerator.generateTestingBlock(repository, alice);
BlockMinter.mintTestingBlock(repository, alice);
// Check name is sold
nameData = repository.getNameRepository().fromName(name);
@ -200,7 +200,7 @@ public class OrphaningTests extends Common {
// Sell-name
BigDecimal newPrice = new BigDecimal(random.nextInt(1000)).setScale(8);
SellNameTransactionData transactionData = new SellNameTransactionData(TestTransaction.generateBase(bob), name, newPrice);
TransactionUtils.signAndForge(repository, transactionData, bob);
TransactionUtils.signAndMint(repository, transactionData, bob);
NameData nameData;
@ -218,7 +218,7 @@ public class OrphaningTests extends Common {
// Not concerned about price
// Re-process sell-name
BlockGenerator.generateTestingBlock(repository, alice);
BlockMinter.mintTestingBlock(repository, alice);
// Check name is for sale
nameData = repository.getNameRepository().fromName(name);
@ -236,10 +236,10 @@ public class OrphaningTests extends Common {
assertEquals(alice.getAddress(), nameData.getOwner());
// Re-process buy-name and sell-name
BlockGenerator.generateTestingBlock(repository, bob);
BlockMinter.mintTestingBlock(repository, bob);
// Unconfirmed sell-name transaction not included in previous block
// as it isn't valid until name owned by bob thanks to buy-name transaction.
BlockGenerator.generateTestingBlock(repository, bob);
BlockMinter.mintTestingBlock(repository, bob);
// Check name does exist
assertTrue(repository.getNameRepository().nameExists(name));

View File

@ -1,28 +1,32 @@
{
"isTestChain": true,
"maxBalance": "10000000000",
"blockDifficultyInterval": 10,
"minBlockTime": 30,
"maxBlockTime": 60,
"blockTimestampMargin": 500,
"transactionExpiryPeriod": 86400000,
"maxBlockSize": 2097152,
"maxBytesPerUnitFee": 1024,
"unitFee": "0.1",
"requireGroupForApproval": false,
"genesisInfo": {
"version": 4,
"timestamp": 0,
"generatingBalance": "10000000",
"transactions": [
{ "type": "ISSUE_ASSET", "owner": "QcFmNxSArv5tWEzCtTKb2Lqc5QkKuQ7RNs", "assetName": "QORA", "description": "QORA native coin", "data": "", "quantity": 10000000000, "isDivisible": true, "fee": 0, "reference": "3Verk6ZKBJc3WTTVfxFC9icSjKdM8b92eeJEpJP8qNizG4ZszNFq8wdDYdSjJXq2iogDFR1njyhsBdVpbvDfjzU7" },
{ "type": "GENESIS", "recipient": "QgV4s3xnzLhVBEJxcYui4u4q11yhUHsd9v", "amount": "1000000000", "fee": 0 },
{ "type": "GENESIS", "recipient": "QixPbJUwsaHsVEofJdozU9zgVqkK6aYhrK", "amount": "1000000", "fee": 0 },
{ "type": "GENESIS", "recipient": "QaUpHNhT3Ygx6avRiKobuLdusppR5biXjL", "amount": "1000000", "fee": 0 },
{ "type": "GENESIS", "recipient": "Qci5m9k4rcwe4ruKrZZQKka4FzUUMut3er", "amount": "1000000", "fee": 0 },
{ "type": "ISSUE_ASSET", "owner": "QgV4s3xnzLhVBEJxcYui4u4q11yhUHsd9v", "assetName": "TEST", "description": "test asset", "data": "", "quantity": 1000000, "isDivisible": true, "fee": 0 },
{ "type": "ISSUE_ASSET", "owner": "QixPbJUwsaHsVEofJdozU9zgVqkK6aYhrK", "assetName": "OTHER", "description": "other test asset", "data": "", "quantity": 1000000, "isDivisible": true, "fee": 0 },
{ "type": "ISSUE_ASSET", "owner": "QgV4s3xnzLhVBEJxcYui4u4q11yhUHsd9v", "assetName": "GOLD", "description": "gold test asset", "data": "", "quantity": 1000000, "isDivisible": true, "fee": 0 }
]
},
"minAccountLevelToRewardShare": 5,
"maxRewardSharesPerMintingAccount": 20,
"onlineAccountSignaturesMinLifetime": 3600000,
"onlineAccountSignaturesMaxLifetime": 86400000,
"rewardsByHeight": [
{ "height": 1, "reward": 100 },
{ "height": 11, "reward": 10 },
{ "height": 21, "reward": 1 }
],
"sharesByLevel": [
{ "levels": [ 1, 2 ], "share": 0.05 },
{ "levels": [ 3, 4 ], "share": 0.10 },
{ "levels": [ 5, 6 ], "share": 0.15 },
{ "levels": [ 7, 8 ], "share": 0.20 },
{ "levels": [ 9, 10 ], "share": 0.25 }
],
"qoraHoldersShare": 0.20,
"blocksNeededByLevel": [ 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 ],
"blockTimingsByHeight": [
{ "height": 1, "target": 60000, "deviation": 30000, "power": 0.2 }
],
"featureTriggers": {
"messageHeight": 0,
"atHeight": 0,
@ -31,6 +35,23 @@
"arbitraryTimestamp": 0,
"powfixTimestamp": 0,
"v2Timestamp": 0,
"newAssetPricingTimestamp": 1600000000000
"newAssetPricingTimestamp": 1600000000000,
"groupApprovalTimestamp": 0
},
"genesisInfo": {
"version": 4,
"timestamp": 0,
"transactions": [
{ "type": "ISSUE_ASSET", "owner": "QcFmNxSArv5tWEzCtTKb2Lqc5QkKuQ7RNs", "assetName": "QORA", "description": "QORA native coin", "data": "", "quantity": 10000000000, "isDivisible": true, "fee": 0, "reference": "3Verk6ZKBJc3WTTVfxFC9icSjKdM8b92eeJEpJP8qNizG4ZszNFq8wdDYdSjJXq2iogDFR1njyhsBdVpbvDfjzU7" },
{ "type": "GENESIS", "recipient": "QgV4s3xnzLhVBEJxcYui4u4q11yhUHsd9v", "amount": "1000000000", "fee": 0 },
{ "type": "GENESIS", "recipient": "QixPbJUwsaHsVEofJdozU9zgVqkK6aYhrK", "amount": "1000000", "fee": 0 },
{ "type": "GENESIS", "recipient": "QaUpHNhT3Ygx6avRiKobuLdusppR5biXjL", "amount": "1000000", "fee": 0 },
{ "type": "GENESIS", "recipient": "Qci5m9k4rcwe4ruKrZZQKka4FzUUMut3er", "amount": "1000000", "fee": 0 },
{ "type": "ISSUE_ASSET", "owner": "QgV4s3xnzLhVBEJxcYui4u4q11yhUHsd9v", "assetName": "TEST", "description": "test asset", "data": "", "quantity": 1000000, "isDivisible": true, "fee": 0 },
{ "type": "ISSUE_ASSET", "owner": "QixPbJUwsaHsVEofJdozU9zgVqkK6aYhrK", "assetName": "OTHER", "description": "other test asset", "data": "", "quantity": 1000000, "isDivisible": true, "fee": 0 },
{ "type": "ISSUE_ASSET", "owner": "QgV4s3xnzLhVBEJxcYui4u4q11yhUHsd9v", "assetName": "GOLD", "description": "gold test asset", "data": "", "quantity": 1000000, "isDivisible": true, "fee": 0 },
{ "type": "ACCOUNT_FLAGS", "target": "QgV4s3xnzLhVBEJxcYui4u4q11yhUHsd9v", "andMask": -1, "orMask": 1, "xorMask": 0 },
{ "type": "REWARD_SHARE", "minterPublicKey": "2tiMr5LTpaWCgbRvkPK8TFd7k63DyHJMMFFsz9uBf1ZP", "recipient": "QgV4s3xnzLhVBEJxcYui4u4q11yhUHsd9v", "rewardSharePublicKey": "7PpfnvLSG7y4HPh8hE7KoqAjLCkv7Ui6xw4mKAkbZtox", "sharePercent": 100 }
]
}
}

View File

@ -0,0 +1,52 @@
{
"isTestChain": true,
"blockTimestampMargin": 500,
"transactionExpiryPeriod": 86400000,
"maxBlockSize": 2097152,
"maxBytesPerUnitFee": 1024,
"unitFee": "0.1",
"requireGroupForApproval": false,
"minAccountLevelToRewardShare": 5,
"maxRewardSharesPerMintingAccount": 20,
"onlineAccountSignaturesMinLifetime": 3600000,
"onlineAccountSignaturesMaxLifetime": 86400000,
"rewardsByHeight": [
{ "height": 1, "reward": 100 },
{ "height": 11, "reward": 10 },
{ "height": 21, "reward": 1 }
],
"sharesByLevel": [
{ "levels": [ 1, 2 ], "share": 0.05 },
{ "levels": [ 3, 4 ], "share": 0.10 },
{ "levels": [ 5, 6 ], "share": 0.15 },
{ "levels": [ 7, 8 ], "share": 0.20 },
{ "levels": [ 9, 10 ], "share": 0.25 }
],
"qoraHoldersShare": 0.20,
"blocksNeededByLevel": [ 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 ],
"blockTimingsByHeight": [
{ "height": 1, "target": 60000, "deviation": 30000, "power": 0.2 }
],
"featureTriggers": {
"messageHeight": 0,
"atHeight": 0,
"assetsTimestamp": 0,
"votingTimestamp": 0,
"arbitraryTimestamp": 0,
"powfixTimestamp": 0,
"v2Timestamp": 1600000000000,
"newAssetPricingTimestamp": 1600000000000,
"groupApprovalTimestamp": 0
},
"genesisInfo": {
"version": 1,
"timestamp": 1400247274336,
"transactions": [
{ "type": "ISSUE_ASSET", "owner": "QcFmNxSArv5tWEzCtTKb2Lqc5QkKuQ7RNs", "assetName": "QORA", "description": "QORA native coin", "data": "", "quantity": 10000000000, "isDivisible": true, "fee": 0, "reference": "3Verk6ZKBJc3WTTVfxFC9icSjKdM8b92eeJEpJP8qNizG4ZszNFq8wdDYdSjJXq2iogDFR1njyhsBdVpbvDfjzU7" },
{ "type": "GENESIS", "recipient": "QgV4s3xnzLhVBEJxcYui4u4q11yhUHsd9v", "amount": "1000000000", "fee": 0 },
{ "type": "GENESIS", "recipient": "QixPbJUwsaHsVEofJdozU9zgVqkK6aYhrK", "amount": "1000000", "fee": 0 },
{ "type": "GENESIS", "recipient": "QaUpHNhT3Ygx6avRiKobuLdusppR5biXjL", "amount": "1000000", "fee": 0 },
{ "type": "GENESIS", "recipient": "Qci5m9k4rcwe4ruKrZZQKka4FzUUMut3er", "amount": "1000000", "fee": 0 }
]
}
}

View File

@ -1,40 +1,31 @@
{
"isTestChain": true,
"maxBalance": "10000000000",
"blockDifficultyInterval": 10,
"minBlockTime": 30,
"maxBlockTime": 60,
"blockTimestampMargin": 500,
"transactionExpiryPeriod": 86400000,
"maxBlockSize": 2097152,
"maxBytesPerUnitFee": 1024,
"unitFee": "0.1",
"requireGroupForApproval": false,
"maxProxyRelationships": 8,
"genesisInfo": {
"version": 4,
"timestamp": 0,
"generatingBalance": "10000000",
"transactions": [
{ "type": "ISSUE_ASSET", "owner": "QcFmNxSArv5tWEzCtTKb2Lqc5QkKuQ7RNs", "assetName": "QORA", "description": "QORA native coin", "data": "", "quantity": 10000000000, "isDivisible": true, "fee": 0, "reference": "3Verk6ZKBJc3WTTVfxFC9icSjKdM8b92eeJEpJP8qNizG4ZszNFq8wdDYdSjJXq2iogDFR1njyhsBdVpbvDfjzU7" },
{ "type": "GENESIS", "recipient": "QgV4s3xnzLhVBEJxcYui4u4q11yhUHsd9v", "amount": "1000000000", "fee": 0 },
{ "type": "GENESIS", "recipient": "QixPbJUwsaHsVEofJdozU9zgVqkK6aYhrK", "amount": "1000000", "fee": 0 },
{ "type": "GENESIS", "recipient": "QaUpHNhT3Ygx6avRiKobuLdusppR5biXjL", "amount": "1000000", "fee": 0 },
{ "type": "GENESIS", "recipient": "Qci5m9k4rcwe4ruKrZZQKka4FzUUMut3er", "amount": "1000000", "fee": 0 },
{ "type": "CREATE_GROUP", "creatorPublicKey": "2tiMr5LTpaWCgbRvkPK8TFd7k63DyHJMMFFsz9uBf1ZP", "owner": "QgV4s3xnzLhVBEJxcYui4u4q11yhUHsd9v", "groupName": "dev-group", "description": "developer group", "isOpen": false, "approvalThreshold": "PCT100", "minimumBlockDelay": 0, "maximumBlockDelay": 1440 },
{ "type": "ISSUE_ASSET", "owner": "QgV4s3xnzLhVBEJxcYui4u4q11yhUHsd9v", "assetName": "TEST", "description": "test asset", "data": "", "quantity": 1000000, "isDivisible": true, "fee": 0 },
{ "type": "ISSUE_ASSET", "owner": "QixPbJUwsaHsVEofJdozU9zgVqkK6aYhrK", "assetName": "OTHER", "description": "other test asset", "data": "", "quantity": 1000000, "isDivisible": true, "fee": 0 },
{ "type": "ISSUE_ASSET", "owner": "QgV4s3xnzLhVBEJxcYui4u4q11yhUHsd9v", "assetName": "GOLD", "description": "gold test asset", "data": "", "quantity": 1000000, "isDivisible": true, "fee": 0 },
{ "type": "ACCOUNT_FLAGS", "target": "QgV4s3xnzLhVBEJxcYui4u4q11yhUHsd9v", "andMask": -1, "orMask": 1, "xorMask": 0 }
]
},
"minAccountLevelToRewardShare": 5,
"maxRewardSharesPerMintingAccount": 20,
"onlineAccountSignaturesMinLifetime": 3600000,
"onlineAccountSignaturesMaxLifetime": 86400000,
"rewardsByHeight": [
{ "height": 1, "reward": 100 },
{ "height": 11, "reward": 10 },
{ "height": 21, "reward": 1 }
],
"forgingTiers": [
{ "minBlocks": 5, "maxSubAccounts": 5 },
{ "minBlocks": 4, "maxSubAccounts": 3 },
{ "minBlocks": 0, "maxSubAccounts": 0 }
"sharesByLevel": [
{ "levels": [ 1, 2 ], "share": 0.05 },
{ "levels": [ 3, 4 ], "share": 0.10 },
{ "levels": [ 5, 6 ], "share": 0.15 },
{ "levels": [ 7, 8 ], "share": 0.20 },
{ "levels": [ 9, 10 ], "share": 0.25 }
],
"qoraHoldersShare": 0.20,
"blocksNeededByLevel": [ 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 ],
"blockTimingsByHeight": [
{ "height": 1, "target": 60000, "deviation": 30000, "power": 0.2 }
],
"featureTriggers": {
"messageHeight": 0,
@ -46,5 +37,22 @@
"v2Timestamp": 0,
"newAssetPricingTimestamp": 0,
"groupApprovalTimestamp": 0
},
"genesisInfo": {
"version": 4,
"timestamp": 0,
"transactions": [
{ "type": "ISSUE_ASSET", "owner": "QcFmNxSArv5tWEzCtTKb2Lqc5QkKuQ7RNs", "assetName": "QORA", "description": "QORA native coin", "data": "", "quantity": 10000000000, "isDivisible": true, "fee": 0, "reference": "3Verk6ZKBJc3WTTVfxFC9icSjKdM8b92eeJEpJP8qNizG4ZszNFq8wdDYdSjJXq2iogDFR1njyhsBdVpbvDfjzU7" },
{ "type": "GENESIS", "recipient": "QgV4s3xnzLhVBEJxcYui4u4q11yhUHsd9v", "amount": "1000000000", "fee": 0 },
{ "type": "GENESIS", "recipient": "QixPbJUwsaHsVEofJdozU9zgVqkK6aYhrK", "amount": "1000000", "fee": 0 },
{ "type": "GENESIS", "recipient": "QaUpHNhT3Ygx6avRiKobuLdusppR5biXjL", "amount": "1000000", "fee": 0 },
{ "type": "GENESIS", "recipient": "Qci5m9k4rcwe4ruKrZZQKka4FzUUMut3er", "amount": "1000000", "fee": 0 },
{ "type": "CREATE_GROUP", "creatorPublicKey": "2tiMr5LTpaWCgbRvkPK8TFd7k63DyHJMMFFsz9uBf1ZP", "owner": "QgV4s3xnzLhVBEJxcYui4u4q11yhUHsd9v", "groupName": "dev-group", "description": "developer group", "isOpen": false, "approvalThreshold": "PCT100", "minimumBlockDelay": 0, "maximumBlockDelay": 1440 },
{ "type": "ISSUE_ASSET", "owner": "QgV4s3xnzLhVBEJxcYui4u4q11yhUHsd9v", "assetName": "TEST", "description": "test asset", "data": "", "quantity": 1000000, "isDivisible": true, "fee": 0 },
{ "type": "ISSUE_ASSET", "owner": "QixPbJUwsaHsVEofJdozU9zgVqkK6aYhrK", "assetName": "OTHER", "description": "other test asset", "data": "", "quantity": 1000000, "isDivisible": true, "fee": 0 },
{ "type": "ISSUE_ASSET", "owner": "QgV4s3xnzLhVBEJxcYui4u4q11yhUHsd9v", "assetName": "GOLD", "description": "gold test asset", "data": "", "quantity": 1000000, "isDivisible": true, "fee": 0 },
{ "type": "ACCOUNT_FLAGS", "target": "QgV4s3xnzLhVBEJxcYui4u4q11yhUHsd9v", "andMask": -1, "orMask": 1, "xorMask": 0 },
{ "type": "REWARD_SHARE", "minterPublicKey": "2tiMr5LTpaWCgbRvkPK8TFd7k63DyHJMMFFsz9uBf1ZP", "recipient": "QgV4s3xnzLhVBEJxcYui4u4q11yhUHsd9v", "rewardSharePublicKey": "7PpfnvLSG7y4HPh8hE7KoqAjLCkv7Ui6xw4mKAkbZtox", "sharePercent": 100 }
]
}
}

View File

@ -0,0 +1,6 @@
{
"restrictedApi": false,
"blockchainConfig": "src/test/resources/test-chain-v1.json",
"wipeUnconfirmedOnStart": false,
"minPeers": 0
}