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

@@ -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
}