forked from Qortal/qortal
Compare commits
31 Commits
v1.4.1
...
block-rewa
Author | SHA1 | Date | |
---|---|---|---|
|
16453ed602 | ||
|
fde68dc598 | ||
|
847e81e95c | ||
|
7918622e2e | ||
|
427fa1816d | ||
|
0c7e388463 | ||
|
be3af53011 | ||
|
414399b2a0 | ||
|
c592051a80 | ||
|
33a8f311e5 | ||
|
018c3cdcd4 | ||
|
384dffbf9a | ||
|
0306ecb03d | ||
|
e5ce732557 | ||
|
f19e0498bf | ||
|
32ec02225a | ||
|
3920933fc7 | ||
|
1fdd7f156c | ||
|
91925cf931 | ||
|
30e58f1c19 | ||
|
8d5c6db39f | ||
|
3453f0efaf | ||
|
eb23940996 | ||
|
6cd86d86a6 | ||
|
c3fa34f5b9 | ||
|
0af0aaaa21 | ||
|
02100c502b | ||
|
b55154cd3c | ||
|
dc6eda1355 | ||
|
6224bc3bca | ||
|
9ceac8c991 |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -16,3 +16,6 @@
|
||||
/settings*.json
|
||||
/testchain.json
|
||||
/run-testnet.sh
|
||||
/.idea
|
||||
/qortal.iml
|
||||
*.DS_Store
|
||||
|
@@ -1,5 +1,20 @@
|
||||
# Auto Updates
|
||||
|
||||
## TL;DR: how-to
|
||||
|
||||
* Prepare new release version (see way below for details)
|
||||
* Assuming you are in git 'master' branch, at HEAD
|
||||
* Shutdown local node if running
|
||||
* Build auto-update download: `tools/build-auto-update.sh` - uploads auto-update file into new git branch
|
||||
* Restart local node
|
||||
* Publish auto-update transaction using *private key* for **non-admin** member of "dev" group:
|
||||
`tools/publish-auto-update.sh non-admin-dev-member-private-key-in-base58`
|
||||
* Wait for auto-update `ARBITRARY` transaction to be confirmed into a block
|
||||
* Have "dev" group admins 'approve' auto-update using `tools/approve-auto-update.sh`
|
||||
This tool will prompt for *private key* of **admin** of "dev" group
|
||||
* A minimum number of admins are required for approval, and a minimum number of blocks must pass also.
|
||||
* Nodes will start to download, and apply, the update over the next 20 minutes or so (see CHECK_INTERVAL in AutoUpdate.java)
|
||||
|
||||
## Theory
|
||||
* Using a specific git commit (e.g. abcdef123) we produce a determinstic JAR with consistent hash.
|
||||
* To avoid issues with over-eager anti-virus / firewalls we obfuscate JAR using very simplistic XOR-based method.
|
||||
@@ -25,8 +40,8 @@ The same method is used to obfuscate and de-obfuscate:
|
||||
|
||||
## Typical download locations
|
||||
The git SHA1 commit hash is used to replace `%s` in various download locations, e.g.:
|
||||
* https://github.com/QORT/qortal/raw/%s/qortal.update
|
||||
* https://raw.githubusercontent.com@151.101.16.133/QORT/qortal/%s/qortal.update
|
||||
* https://github.com/Qortal/qortal/raw/%s/qortal.update
|
||||
* https://raw.githubusercontent.com@151.101.16.133/Qortal/qortal/%s/qortal.update
|
||||
|
||||
These locations are part of the org.qortal.settings.Settings class and can be overriden in settings.json like:
|
||||
```
|
||||
@@ -45,4 +60,12 @@ $ java -cp qortal.jar org.qortal.XorUpdate
|
||||
usage: XorUpdate <input-file> <output-file>
|
||||
$ java -cp qortal.jar org.qortal.XorUpdate qortal.jar qortal.update
|
||||
$
|
||||
```
|
||||
```
|
||||
|
||||
## Preparing new release version
|
||||
|
||||
* Shutdown local node
|
||||
* Modify `pom.xml` and increase version inside `<version>` tag
|
||||
* Commit new `pom.xml` and push to github, e.g. `git commit -m 'Bumped to v1.4.2' -- pom.xml; git push`
|
||||
* Tag this new commit with same version: `git tag v1.4.2`
|
||||
* Push tag up to github: `git push origin v1.4.2`
|
||||
|
12
DATABASE.md
12
DATABASE.md
@@ -4,10 +4,10 @@ You can examine your node's database using [HSQLDB's "sqltool"](http://www.hsqld
|
||||
It's a good idea to install "rlwrap" (ReadLine wrapper) too as sqltool doesn't support command history/editing.
|
||||
|
||||
Typical command line for sqltool would be:
|
||||
`rlwrap java -cp ${HSQLDB_JAR}:${SQLTOOL_JAR} org.hsqldb.cmdline.SqlTool --rcFile=${SQLTOOL_RC} qora`
|
||||
`rlwrap java -cp ${HSQLDB_JAR}:${SQLTOOL_JAR} org.hsqldb.cmdline.SqlTool --rcFile=${SQLTOOL_RC} qortal`
|
||||
|
||||
`${HSQLDB_JAR}` should be set with pathname where Maven downloaded hsqldb,
|
||||
typically `${HOME}/.m2/repository/org/hsqldb/hsqldb/2.5.0/hsqldb-2.5.0.jar`
|
||||
typically `${HOME}/.m2/repository/org/hsqldb/hsqldb/2.5.1/hsqldb-2.5.1.jar`
|
||||
|
||||
`${SQLTOOL_JAR}` should be set with pathname where Maven downloaded sqltool,
|
||||
typically `${HOME}/.m2/repository/org/hsqldb/sqltool/2.5.0/sqltool-2.5.0.jar`
|
||||
@@ -25,10 +25,16 @@ Above `url` component `file:db/blockchain` assumes you will call `sqltool` from
|
||||
|
||||
Another idea is to assign a shell alias in your `.bashrc` like:
|
||||
```
|
||||
export HSQLDB_JAR=${HOME}/.m2/repository/org/hsqldb/hsqldb/2.5.0/hsqldb-2.5.0.jar
|
||||
export HSQLDB_JAR=${HOME}/.m2/repository/org/hsqldb/hsqldb/2.5.1/hsqldb-2.5.1.jar
|
||||
export SQLTOOL_JAR=${HOME}/.m2/repository/org/hsqldb/sqltool/2.5.0/sqltool-2.5.0.jar
|
||||
alias sqltool='rlwrap java -cp ${HSQLDB_JAR}:${SQLTOOL_JAR} org.hsqldb.cmdline.SqlTool --rcFile=${SQLTOOL_RC}'
|
||||
```
|
||||
So you can simply type: `sqltool qortal`
|
||||
|
||||
Don't forget to use `SHUTDOWN;` before exiting sqltool so that database files are closed cleanly.
|
||||
|
||||
## Quick and dirty version
|
||||
|
||||
With `sqltool-2.5.0.jar` and `qortal.jar` in current directory, and database in `db/`
|
||||
|
||||
`java -cp qortal.jar:sqltool-2.5.0.jar org.hsqldb.cmdline.SqlTool --inlineRc=url=jdbc:hsqldb:file:db/blockchain`
|
||||
|
@@ -9,4 +9,4 @@
|
||||
- Create basic *settings.json* file: `echo '{}' > settings.json`
|
||||
- Run JAR in same working directory as *settings.json*: `java -jar target/qortal-1.0.jar`
|
||||
- Wrap in shell script, add JVM flags, redirection, backgrounding, etc. as necessary.
|
||||
- Or use supplied example shell script: *run.sh*
|
||||
- Or use supplied example shell script: *start.sh*
|
||||
|
69
TestNets.md
Normal file
69
TestNets.md
Normal file
@@ -0,0 +1,69 @@
|
||||
# How to build a testnet
|
||||
|
||||
## Create testnet blockchain config
|
||||
|
||||
- You can begin by copying the mainnet blockchain config `src/main/resources/blockchain.json`
|
||||
- Insert `"isTestChain": true,` after the opening `{`
|
||||
- Modify testnet genesis block
|
||||
|
||||
### Testnet genesis block
|
||||
|
||||
- Set `timestamp` to a nearby future value, e.g. 15 mins from 'now'
|
||||
This is to give yourself enough time to set up other testnet nodes
|
||||
- Retain the initial `ISSUE_ASSET` transactions!
|
||||
- Add `ACCOUNT_FLAGS` transactions with `"andMask": -1, "orMask": 1, "xorMask": 0` to create founders
|
||||
- Add at least one `REWARD_SHARE` transaction otherwise no-one can mint initial blocks!
|
||||
You will need to calculate `rewardSharePublicKey` (and private key),
|
||||
or make a new account on mainnet and use self-share key values
|
||||
- Add `ACCOUNT_LEVEL` transactions to set initial level of accounts as needed
|
||||
- Add `GENESIS` transactions to add QORT/LEGACY_QORA funds to accounts as needed
|
||||
|
||||
## Testnet `settings.json`
|
||||
|
||||
- Create a new `settings-test.json`
|
||||
- Make sure to add `"isTestNet": true,`
|
||||
- Make sure to reference testnet blockchain config file: `"blockchainConfig": "testchain.json",`
|
||||
- It is a good idea to use a separate database: `"repositoryPath": "db-testnet",`
|
||||
- You might also need to add `"bitcoinNet": "TEST3",` and `"litecoinNet": "TEST3",`
|
||||
|
||||
## Other nodes
|
||||
|
||||
- Copy `testchain.json` and `settings-test.json` to other nodes
|
||||
- Alternatively, you can run multiple nodes on the same machine by:
|
||||
* Copying `settings-test.json` to `settings-test-1.json`
|
||||
* Configure different `repositoryPath`
|
||||
* Configure use of different ports:
|
||||
+ `"apiPort": 22391,`
|
||||
+ `"listenPort": 22392,`
|
||||
|
||||
## Starting-up
|
||||
|
||||
- Start up at least as many nodes as `minBlockchainPeers` (or adjust this value instead)
|
||||
- Probably best to perform API call `DELETE /peers/known`
|
||||
- Add other nodes via API call `POST /peers <peer-hostname-or-IP>`
|
||||
- Add minting private key to node(s) via API call `POST /admin/mintingaccounts <minting-private-key>`
|
||||
This key must have corresponding `REWARD_SHARE` transaction in testnet genesis block
|
||||
- Wait for genesis block timestamp to pass
|
||||
- A node should mint block 2 approximately 60 seconds after genesis block timestamp
|
||||
- Other testnet nodes will sync *as long as there is at least `minBlockchainPeers` peers with an "up-to-date" chain`
|
||||
- You can also use API call `POST /admin/forcesync <connected-peer-IP-and-port>` on stuck nodes
|
||||
|
||||
## Dealing with stuck chain
|
||||
|
||||
Maybe your nodes have been offline and no-one has minted a recent testnet block.
|
||||
Your options are:
|
||||
|
||||
- Start a new testnet from scratch
|
||||
- Fire up your testnet node(s)
|
||||
- Force one of your nodes to mint by:
|
||||
+ Set a debugger breakpoint on Settings.getMinBlockchainPeers()
|
||||
+ When breakpoint is hit, change `this.minBlockchainPeers` to zero, then continue
|
||||
- Once one of your nodes has minted blocks up to 'now', you can use "forcesync" on the other nodes
|
||||
|
||||
## Tools
|
||||
|
||||
- `qort` tool, but use `-t` option for default testnet API port (62391)
|
||||
- `qort` tool, but first set shell variable: `export BASE_URL=some-node-hostname-or-ip:port`
|
||||
- `qort` tool, but prepend with one-time shell variable: `BASE_URL=some-node-hostname-or-ip:port qort ......`
|
||||
- `peer-heights`, but use `-t` option, or `BASE_URL` shell variable as above
|
||||
|
Binary file not shown.
@@ -19,10 +19,10 @@
|
||||
<ROW Property="Manufacturer" Value="Qortal"/>
|
||||
<ROW Property="MsiLogging" MultiBuildValue="DefaultBuild:vp"/>
|
||||
<ROW Property="NTP_GOOD" Value="false"/>
|
||||
<ROW Property="ProductCode" Value="1033:{F45F964E-1F1F-4B24-AAC5-687C656B5534} 1049:{F228D3BD-A49D-4332-A57D-67A5EFA47674} 2052:{17BB4192-98DA-4D79-AA29-7340ADA1EB38} 2057:{F25873D9-9179-4B35-98FB-EA8D19EE89DE} " Type="16"/>
|
||||
<ROW Property="ProductCode" Value="1033:{9EA6B58D-641E-442E-8F16-5D35B92B9F9B} 1049:{B16722F6-C2FA-418D-A9DA-69707FE2034B} 2052:{459AC873-98DC-43AC-8787-B23BAF976FF5} 2057:{CD923B63-65A0-4F2A-93B6-6362AAC8608E} " Type="16"/>
|
||||
<ROW Property="ProductLanguage" Value="2057"/>
|
||||
<ROW Property="ProductName" Value="Qortal"/>
|
||||
<ROW Property="ProductVersion" Value="1.3.7" Type="32"/>
|
||||
<ROW Property="ProductVersion" Value="1.4.2" Type="32"/>
|
||||
<ROW Property="RECONFIG_NTP" Value="true"/>
|
||||
<ROW Property="REMOVE_BLOCKCHAIN" Value="YES" Type="4"/>
|
||||
<ROW Property="REPAIR_BLOCKCHAIN" Value="YES" Type="4"/>
|
||||
@@ -174,7 +174,7 @@
|
||||
<ROW Component="ADDITIONAL_LICENSE_INFO_97" ComponentId="{D5544706-E2A7-424F-AEA5-3963E355AA29}" Directory_="jdk.crypto.mscapi_Dir" Attributes="0" KeyPath="ADDITIONAL_LICENSE_INFO_97" Type="0"/>
|
||||
<ROW Component="ADDITIONAL_LICENSE_INFO_98" ComponentId="{104DBCE8-A458-4B3E-9EFA-2D8613561619}" Directory_="jdk.dynalink_Dir" Attributes="0" KeyPath="ADDITIONAL_LICENSE_INFO_98" Type="0"/>
|
||||
<ROW Component="ADDITIONAL_LICENSE_INFO_99" ComponentId="{D02E3C37-E81A-48FA-9E28-B26B728AECD9}" Directory_="jdk.httpserver_Dir" Attributes="0" KeyPath="ADDITIONAL_LICENSE_INFO_99" Type="0"/>
|
||||
<ROW Component="AI_CustomARPName" ComponentId="{7D827076-2762-468D-BC89-813DBA8A8B89}" Directory_="APPDIR" Attributes="260" KeyPath="DisplayName" Options="1"/>
|
||||
<ROW Component="AI_CustomARPName" ComponentId="{F9375D31-26C0-4E23-948D-3570B43B7FA2}" Directory_="APPDIR" Attributes="260" KeyPath="DisplayName" Options="1"/>
|
||||
<ROW Component="AI_ExePath" ComponentId="{3644948D-AE0B-41BB-9FAF-A79E70490A08}" Directory_="APPDIR" Attributes="260" KeyPath="AI_ExePath"/>
|
||||
<ROW Component="APPDIR" ComponentId="{680DFDDE-3FB4-47A5-8FF5-934F576C6F91}" Directory_="APPDIR" Attributes="0"/>
|
||||
<ROW Component="DATA_PATH" ComponentId="{EE0B6107-E244-4CDB-B195-E9038D2F1E0E}" Directory_="DATA_PATH" Attributes="0"/>
|
||||
|
2
pom.xml
2
pom.xml
@@ -3,7 +3,7 @@
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>org.qortal</groupId>
|
||||
<artifactId>qortal</artifactId>
|
||||
<version>1.4.1</version>
|
||||
<version>1.4.3</version>
|
||||
<packaging>jar</packaging>
|
||||
<properties>
|
||||
<skipTests>true</skipTests>
|
||||
|
BIN
src/.DS_Store
vendored
Normal file
BIN
src/.DS_Store
vendored
Normal file
Binary file not shown.
BIN
src/main/.DS_Store
vendored
Normal file
BIN
src/main/.DS_Store
vendored
Normal file
Binary file not shown.
@@ -628,25 +628,9 @@ public class AdminResource {
|
||||
public String checkpointRepository() {
|
||||
Security.checkApiCallAllowed(request);
|
||||
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
ReentrantLock blockchainLock = Controller.getInstance().getBlockchainLock();
|
||||
RepositoryManager.setRequestedCheckpoint(Boolean.TRUE);
|
||||
|
||||
blockchainLock.lockInterruptibly();
|
||||
|
||||
try {
|
||||
repository.checkpoint(true);
|
||||
repository.saveChanges();
|
||||
|
||||
return "true";
|
||||
} finally {
|
||||
blockchainLock.unlock();
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
// We couldn't lock blockchain to perform checkpoint
|
||||
return "false";
|
||||
} catch (DataException e) {
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e);
|
||||
}
|
||||
return "true";
|
||||
}
|
||||
|
||||
@POST
|
||||
|
@@ -176,19 +176,26 @@ public class Block {
|
||||
*
|
||||
* @return account-level share "bin" from blockchain config, or null if founder / none found
|
||||
*/
|
||||
public AccountLevelShareBin getShareBin() {
|
||||
public AccountLevelShareBin getShareBin(int blockHeight) {
|
||||
if (this.isMinterFounder)
|
||||
return null;
|
||||
|
||||
final int accountLevel = this.mintingAccountData.getLevel();
|
||||
if (accountLevel <= 0)
|
||||
return null;
|
||||
return null; // level 0 isn't included in any share bins
|
||||
|
||||
final AccountLevelShareBin[] shareBinsByLevel = BlockChain.getInstance().getShareBinsByAccountLevel();
|
||||
final BlockChain blockChain = BlockChain.getInstance();
|
||||
final AccountLevelShareBin[] shareBinsByLevel = blockChain.getShareBinsByAccountLevel();
|
||||
if (accountLevel > shareBinsByLevel.length)
|
||||
return null;
|
||||
|
||||
return shareBinsByLevel[accountLevel];
|
||||
if (blockHeight < blockChain.getShareBinFixHeight())
|
||||
// Off-by-one bug still in effect
|
||||
return shareBinsByLevel[accountLevel];
|
||||
|
||||
// level 1 stored at index 0, level 2 stored at index 1, etc.
|
||||
return shareBinsByLevel[accountLevel-1];
|
||||
|
||||
}
|
||||
|
||||
public long distribute(long accountAmount, Map<String, Long> balanceChanges) {
|
||||
@@ -357,7 +364,7 @@ public class Block {
|
||||
System.arraycopy(onlineAccountData.getSignature(), 0, onlineAccountsSignatures, i * Transformer.SIGNATURE_LENGTH, Transformer.SIGNATURE_LENGTH);
|
||||
}
|
||||
|
||||
byte[] minterSignature = minter.sign(BlockTransformer.getBytesForMinterSignature(parentBlockData.getMinterSignature(),
|
||||
byte[] minterSignature = minter.sign(BlockTransformer.getBytesForMinterSignature(parentBlockData,
|
||||
minter.getPublicKey(), encodedOnlineAccounts));
|
||||
|
||||
// Qortal: minter is always a reward-share, so find actual minter and get their effective minting level
|
||||
@@ -424,7 +431,7 @@ public class Block {
|
||||
int version = this.blockData.getVersion();
|
||||
byte[] reference = this.blockData.getReference();
|
||||
|
||||
byte[] minterSignature = minter.sign(BlockTransformer.getBytesForMinterSignature(parentBlockData.getMinterSignature(),
|
||||
byte[] minterSignature = minter.sign(BlockTransformer.getBytesForMinterSignature(parentBlockData,
|
||||
minter.getPublicKey(), this.blockData.getEncodedOnlineAccounts()));
|
||||
|
||||
// Qortal: minter is always a reward-share, so find actual minter and get their effective minting level
|
||||
@@ -738,11 +745,7 @@ public class Block {
|
||||
if (!(this.minter instanceof PrivateKeyAccount))
|
||||
throw new IllegalStateException("Block's minter is not a PrivateKeyAccount - can't sign!");
|
||||
|
||||
try {
|
||||
this.blockData.setMinterSignature(((PrivateKeyAccount) this.minter).sign(BlockTransformer.getBytesForMinterSignature(this.blockData)));
|
||||
} catch (TransformationException e) {
|
||||
throw new RuntimeException("Unable to calculate block's minter signature", e);
|
||||
}
|
||||
this.blockData.setMinterSignature(((PrivateKeyAccount) this.minter).sign(BlockTransformer.getBytesForMinterSignature(this.blockData)));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1787,7 +1790,7 @@ public class Block {
|
||||
// Find all accounts in share bin. getShareBin() returns null for minter accounts that are also founders, so they are effectively filtered out.
|
||||
AccountLevelShareBin accountLevelShareBin = accountLevelShareBins.get(binIndex);
|
||||
// Object reference compare is OK as all references are read-only from blockchain config.
|
||||
List<ExpandedAccount> binnedAccounts = expandedAccounts.stream().filter(accountInfo -> accountInfo.getShareBin() == accountLevelShareBin).collect(Collectors.toList());
|
||||
List<ExpandedAccount> binnedAccounts = expandedAccounts.stream().filter(accountInfo -> accountInfo.getShareBin(this.blockData.getHeight()) == accountLevelShareBin).collect(Collectors.toList());
|
||||
|
||||
// No online accounts in this bin? Skip to next one
|
||||
if (binnedAccounts.isEmpty())
|
||||
|
@@ -70,7 +70,9 @@ public class BlockChain {
|
||||
private GenesisBlock.GenesisInfo genesisInfo;
|
||||
|
||||
public enum FeatureTrigger {
|
||||
atFindNextTransactionFix;
|
||||
atFindNextTransactionFix,
|
||||
newBlockSigHeight,
|
||||
shareBinFix;
|
||||
}
|
||||
|
||||
/** Map of which blockchain features are enabled when (height/timestamp) */
|
||||
@@ -376,6 +378,14 @@ public class BlockChain {
|
||||
return this.featureTriggers.get(FeatureTrigger.atFindNextTransactionFix.name()).intValue();
|
||||
}
|
||||
|
||||
public int getNewBlockSigHeight() {
|
||||
return this.featureTriggers.get(FeatureTrigger.newBlockSigHeight.name()).intValue();
|
||||
}
|
||||
|
||||
public int getShareBinFixHeight() {
|
||||
return this.featureTriggers.get(FeatureTrigger.shareBinFix.name()).intValue();
|
||||
}
|
||||
|
||||
// More complex getters for aspects that change by height or timestamp
|
||||
|
||||
public long getRewardAtHeight(int ourHeight) {
|
||||
|
@@ -143,7 +143,6 @@ public class Controller extends Thread {
|
||||
private ExecutorService callbackExecutor = Executors.newFixedThreadPool(3);
|
||||
private volatile boolean notifyGroupMembershipChange = false;
|
||||
|
||||
private static final int BLOCK_CACHE_SIZE = 10; // To cover typical Synchronizer request + a few spare
|
||||
/** Latest blocks on our chain. Note: tail/last is the latest block. */
|
||||
private final Deque<BlockData> latestBlocks = new LinkedList<>();
|
||||
|
||||
@@ -152,7 +151,7 @@ public class Controller extends Thread {
|
||||
private final LinkedHashMap<ByteArray, BlockMessage> blockMessageCache = new LinkedHashMap<>() {
|
||||
@Override
|
||||
protected boolean removeEldestEntry(Map.Entry<ByteArray, BlockMessage> eldest) {
|
||||
return this.size() > BLOCK_CACHE_SIZE;
|
||||
return this.size() > Settings.getInstance().getBlockCacheSize();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -319,11 +318,12 @@ public class Controller extends Thread {
|
||||
// Set initial chain height/tip
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
BlockData blockData = repository.getBlockRepository().getLastBlock();
|
||||
int blockCacheSize = Settings.getInstance().getBlockCacheSize();
|
||||
|
||||
synchronized (this.latestBlocks) {
|
||||
this.latestBlocks.clear();
|
||||
|
||||
for (int i = 0; i < BLOCK_CACHE_SIZE && blockData != null; ++i) {
|
||||
for (int i = 0; i < blockCacheSize && blockData != null; ++i) {
|
||||
this.latestBlocks.addFirst(blockData);
|
||||
blockData = repository.getBlockRepository().fromHeight(blockData.getHeight() - 1);
|
||||
}
|
||||
@@ -536,12 +536,7 @@ public class Controller extends Thread {
|
||||
if (now >= repositoryCheckpointTimestamp + repositoryCheckpointInterval) {
|
||||
repositoryCheckpointTimestamp = now + repositoryCheckpointInterval;
|
||||
|
||||
if (Settings.getInstance().getShowCheckpointNotification())
|
||||
SysTray.getInstance().showMessage(Translator.INSTANCE.translate("SysTray", "DB_CHECKPOINT"),
|
||||
Translator.INSTANCE.translate("SysTray", "PERFORMING_DB_CHECKPOINT"),
|
||||
MessageType.INFO);
|
||||
|
||||
RepositoryManager.checkpoint(true);
|
||||
RepositoryManager.setRequestedCheckpoint(Boolean.TRUE);
|
||||
}
|
||||
|
||||
// Give repository a chance to backup (if enabled)
|
||||
@@ -811,7 +806,10 @@ public class Controller extends Thread {
|
||||
|
||||
repository.saveChanges();
|
||||
} catch (DataException e) {
|
||||
LOGGER.error("Repository issue while deleting expired unconfirmed transactions", e);
|
||||
if (RepositoryManager.isDeadlockRelated(e))
|
||||
LOGGER.info("Couldn't delete some expired, unconfirmed transactions this round");
|
||||
else
|
||||
LOGGER.error("Repository issue while deleting expired unconfirmed transactions", e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -935,6 +933,7 @@ public class Controller extends Thread {
|
||||
public void onNewBlock(BlockData latestBlockData) {
|
||||
// Protective copy
|
||||
BlockData blockDataCopy = new BlockData(latestBlockData);
|
||||
int blockCacheSize = Settings.getInstance().getBlockCacheSize();
|
||||
|
||||
synchronized (this.latestBlocks) {
|
||||
BlockData cachedChainTip = this.latestBlocks.peekLast();
|
||||
@@ -944,7 +943,7 @@ public class Controller extends Thread {
|
||||
this.latestBlocks.addLast(latestBlockData);
|
||||
|
||||
// Trim if necessary
|
||||
if (this.latestBlocks.size() >= BLOCK_CACHE_SIZE)
|
||||
if (this.latestBlocks.size() >= blockCacheSize)
|
||||
this.latestBlocks.pollFirst();
|
||||
} else {
|
||||
if (cachedChainTip != null)
|
||||
@@ -1153,6 +1152,7 @@ public class Controller extends Thread {
|
||||
ByteArray signatureAsByteArray = new ByteArray(signature);
|
||||
|
||||
BlockMessage cachedBlockMessage = this.blockMessageCache.get(signatureAsByteArray);
|
||||
int blockCacheSize = Settings.getInstance().getBlockCacheSize();
|
||||
|
||||
// Check cached latest block message
|
||||
if (cachedBlockMessage != null) {
|
||||
@@ -1195,7 +1195,7 @@ public class Controller extends Thread {
|
||||
peer.disconnect("failed to send block");
|
||||
|
||||
// If request is for a recent block, cache it
|
||||
if (getChainHeight() - blockData.getHeight() <= BLOCK_CACHE_SIZE) {
|
||||
if (getChainHeight() - blockData.getHeight() <= blockCacheSize) {
|
||||
this.stats.getBlockMessageStats.cacheFills.incrementAndGet();
|
||||
|
||||
this.blockMessageCache.put(new ByteArray(blockData.getSignature()), blockMessage);
|
||||
|
@@ -488,7 +488,9 @@ public class Synchronizer {
|
||||
peerBlockSignatures.remove(0);
|
||||
++ourHeight;
|
||||
|
||||
LOGGER.trace(String.format("Fetching block %d, sig %.8s from %s", ourHeight, Base58.encode(latestPeerSignature), peer));
|
||||
Block newBlock = this.fetchBlock(repository, peer, latestPeerSignature);
|
||||
LOGGER.trace(String.format("Fetched block %d, sig %.8s from %s", ourHeight, Base58.encode(latestPeerSignature), peer));
|
||||
|
||||
if (newBlock == null) {
|
||||
LOGGER.info(String.format("Peer %s failed to respond with block for height %d, sig %.8s", peer,
|
||||
|
@@ -23,7 +23,7 @@ public interface AcctTradeBot {
|
||||
public ResponseResult startResponse(Repository repository, ATData atData, ACCT acct,
|
||||
CrossChainTradeData crossChainTradeData, String foreignKey, String receivingAddress) throws DataException;
|
||||
|
||||
public boolean canDelete(Repository repository, TradeBotData tradeBotData);
|
||||
public boolean canDelete(Repository repository, TradeBotData tradeBotData) throws DataException;
|
||||
|
||||
public void progress(Repository repository, TradeBotData tradeBotData) throws DataException, ForeignBlockchainException;
|
||||
|
||||
|
@@ -345,11 +345,15 @@ public class BitcoinACCTv1TradeBot implements AcctTradeBot {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canDelete(Repository repository, TradeBotData tradeBotData) {
|
||||
public boolean canDelete(Repository repository, TradeBotData tradeBotData) throws DataException {
|
||||
State tradeBotState = State.valueOf(tradeBotData.getStateValue());
|
||||
if (tradeBotState == null)
|
||||
return true;
|
||||
|
||||
// If the AT doesn't exist then we might as well let the user tidy up
|
||||
if (!repository.getATRepository().exists(tradeBotData.getAtAddress()))
|
||||
return true;
|
||||
|
||||
switch (tradeBotState) {
|
||||
case BOB_WAITING_FOR_AT_CONFIRM:
|
||||
case ALICE_DONE:
|
||||
@@ -378,7 +382,16 @@ public class BitcoinACCTv1TradeBot implements AcctTradeBot {
|
||||
// Attempt to fetch AT data
|
||||
atData = repository.getATRepository().fromATAddress(tradeBotData.getAtAddress());
|
||||
if (atData == null) {
|
||||
LOGGER.warn(() -> String.format("Unable to fetch trade AT %s from repository", tradeBotData.getAtAddress()));
|
||||
LOGGER.debug(() -> String.format("Unable to fetch trade AT %s from repository", tradeBotData.getAtAddress()));
|
||||
|
||||
// If it has been over 24 hours since we last updated this trade-bot entry then assume AT is never coming back
|
||||
// and so wipe the trade-bot entry
|
||||
if (tradeBotData.getTimestamp() + MAX_AT_CONFIRMATION_PERIOD > NTP.getTime()) {
|
||||
LOGGER.info(() -> String.format("AT %s has been gone for too long - deleting trade-bot entry", tradeBotData.getAtAddress()));
|
||||
repository.getCrossChainRepository().delete(tradeBotData.getTradePrivateKey());
|
||||
repository.saveChanges();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
@@ -343,11 +343,15 @@ public class LitecoinACCTv1TradeBot implements AcctTradeBot {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canDelete(Repository repository, TradeBotData tradeBotData) {
|
||||
public boolean canDelete(Repository repository, TradeBotData tradeBotData) throws DataException {
|
||||
State tradeBotState = State.valueOf(tradeBotData.getStateValue());
|
||||
if (tradeBotState == null)
|
||||
return true;
|
||||
|
||||
// If the AT doesn't exist then we might as well let the user tidy up
|
||||
if (!repository.getATRepository().exists(tradeBotData.getAtAddress()))
|
||||
return true;
|
||||
|
||||
switch (tradeBotState) {
|
||||
case BOB_WAITING_FOR_AT_CONFIRM:
|
||||
case ALICE_DONE:
|
||||
@@ -376,7 +380,16 @@ public class LitecoinACCTv1TradeBot implements AcctTradeBot {
|
||||
// Attempt to fetch AT data
|
||||
atData = repository.getATRepository().fromATAddress(tradeBotData.getAtAddress());
|
||||
if (atData == null) {
|
||||
LOGGER.warn(() -> String.format("Unable to fetch trade AT %s from repository", tradeBotData.getAtAddress()));
|
||||
LOGGER.debug(() -> String.format("Unable to fetch trade AT %s from repository", tradeBotData.getAtAddress()));
|
||||
|
||||
// If it has been over 24 hours since we last updated this trade-bot entry then assume AT is never coming back
|
||||
// and so wipe the trade-bot entry
|
||||
if (tradeBotData.getTimestamp() + MAX_AT_CONFIRMATION_PERIOD > NTP.getTime()) {
|
||||
LOGGER.info(() -> String.format("AT %s has been gone for too long - deleting trade-bot entry", tradeBotData.getAtAddress()));
|
||||
repository.getCrossChainRepository().delete(tradeBotData.getTradePrivateKey());
|
||||
repository.saveChanges();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
@@ -47,8 +47,6 @@ public interface Repository extends AutoCloseable {
|
||||
|
||||
public void backup(boolean quick) throws DataException;
|
||||
|
||||
public void checkpoint(boolean quick) throws DataException;
|
||||
|
||||
public void performPeriodicMaintenance() throws DataException;
|
||||
|
||||
public void exportNodeLocalData() throws DataException;
|
||||
|
@@ -1,5 +1,7 @@
|
||||
package org.qortal.repository;
|
||||
|
||||
import java.sql.SQLException;
|
||||
|
||||
public interface RepositoryFactory {
|
||||
|
||||
public boolean wasPristineAtOpen();
|
||||
@@ -12,4 +14,7 @@ public interface RepositoryFactory {
|
||||
|
||||
public void close() throws DataException;
|
||||
|
||||
// Not ideal place for this but implementating class will know the answer without having to open a new DB session
|
||||
public boolean isDeadlockException(SQLException e);
|
||||
|
||||
}
|
||||
|
@@ -1,9 +1,14 @@
|
||||
package org.qortal.repository;
|
||||
|
||||
import java.sql.SQLException;
|
||||
|
||||
public abstract class RepositoryManager {
|
||||
|
||||
private static RepositoryFactory repositoryFactory = null;
|
||||
|
||||
/** null if no checkpoint requested, TRUE for quick checkpoint, false for slow/full checkpoint. */
|
||||
private static Boolean quickCheckpointRequested = null;
|
||||
|
||||
public static RepositoryFactory getRepositoryFactory() {
|
||||
return repositoryFactory;
|
||||
}
|
||||
@@ -46,12 +51,12 @@ public abstract class RepositoryManager {
|
||||
}
|
||||
}
|
||||
|
||||
public static void checkpoint(boolean quick) {
|
||||
try (final Repository repository = getRepository()) {
|
||||
repository.checkpoint(quick);
|
||||
} catch (DataException e) {
|
||||
// Checkpoint is best-effort so don't complain
|
||||
}
|
||||
public static void setRequestedCheckpoint(Boolean quick) {
|
||||
quickCheckpointRequested = quick;
|
||||
}
|
||||
|
||||
public static Boolean getRequestedCheckpoint() {
|
||||
return quickCheckpointRequested;
|
||||
}
|
||||
|
||||
public static void rebuild() throws DataException {
|
||||
@@ -66,4 +71,10 @@ public abstract class RepositoryManager {
|
||||
repositoryFactory = oldRepositoryFactory.reopen();
|
||||
}
|
||||
|
||||
public static boolean isDeadlockRelated(Throwable e) {
|
||||
Throwable cause = e.getCause();
|
||||
|
||||
return SQLException.class.isInstance(cause) && repositoryFactory.isDeadlockException((SQLException) cause);
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -1,5 +1,6 @@
|
||||
package org.qortal.repository;
|
||||
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@@ -251,6 +252,14 @@ public interface TransactionRepository {
|
||||
*/
|
||||
public List<TransactionData> getUnconfirmedTransactions(TransactionType txType, byte[] creatorPublicKey) throws DataException;
|
||||
|
||||
/**
|
||||
* Returns list of unconfirmed transactions excluding specified type(s).
|
||||
*
|
||||
* @return list of transactions, or empty if none.
|
||||
* @throws DataException
|
||||
*/
|
||||
public List<TransactionData> getUnconfirmedTransactions(EnumSet<TransactionType> excludedTxTypes) throws DataException;
|
||||
|
||||
/**
|
||||
* Remove transaction from unconfirmed transactions pile.
|
||||
*
|
||||
|
@@ -1,5 +1,6 @@
|
||||
package org.qortal.repository.hsqldb;
|
||||
|
||||
import java.awt.TrayIcon.MessageType;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.math.BigDecimal;
|
||||
@@ -31,6 +32,8 @@ import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.qortal.account.PrivateKeyAccount;
|
||||
import org.qortal.crypto.Crypto;
|
||||
import org.qortal.globalization.Translator;
|
||||
import org.qortal.gui.SysTray;
|
||||
import org.qortal.repository.ATRepository;
|
||||
import org.qortal.repository.AccountRepository;
|
||||
import org.qortal.repository.ArbitraryRepository;
|
||||
@@ -54,6 +57,11 @@ public class HSQLDBRepository implements Repository {
|
||||
|
||||
private static final Logger LOGGER = LogManager.getLogger(HSQLDBRepository.class);
|
||||
|
||||
private static final Object CHECKPOINT_LOCK = new Object();
|
||||
|
||||
// "serialization failure"
|
||||
private static final Integer DEADLOCK_ERROR_CODE = Integer.valueOf(-4861);
|
||||
|
||||
protected Connection connection;
|
||||
protected final Deque<Savepoint> savepoints = new ArrayDeque<>(3);
|
||||
protected boolean debugState = false;
|
||||
@@ -103,7 +111,10 @@ public class HSQLDBRepository implements Repository {
|
||||
throw new DataException("Unable to fetch session ID from repository", e);
|
||||
}
|
||||
|
||||
assertEmptyTransaction("connection creation");
|
||||
// synchronize to block new connections if checkpointing in progress
|
||||
synchronized (CHECKPOINT_LOCK) {
|
||||
assertEmptyTransaction("connection creation");
|
||||
}
|
||||
}
|
||||
|
||||
// Getters / setters
|
||||
@@ -284,6 +295,9 @@ public class HSQLDBRepository implements Repository {
|
||||
this.sqlStatements = null;
|
||||
this.savepoints.clear();
|
||||
|
||||
// If a checkpoint has been requested, we could perform that now
|
||||
this.maybeCheckpoint();
|
||||
|
||||
// Give connection back to the pool
|
||||
this.connection.close();
|
||||
this.connection = null;
|
||||
@@ -292,6 +306,58 @@ public class HSQLDBRepository implements Repository {
|
||||
}
|
||||
}
|
||||
|
||||
private void maybeCheckpoint() throws DataException {
|
||||
// To serialize checkpointing and to block new sessions when checkpointing in progress
|
||||
synchronized (CHECKPOINT_LOCK) {
|
||||
Boolean quickCheckpointRequest = RepositoryManager.getRequestedCheckpoint();
|
||||
if (quickCheckpointRequest == null)
|
||||
return;
|
||||
|
||||
// We can only perform a CHECKPOINT if no other HSQLDB session is mid-transaction,
|
||||
// otherwise the CHECKPOINT blocks for COMMITs and other threads can't open HSQLDB sessions
|
||||
// due to HSQLDB blocking until CHECKPOINT finishes - i.e. deadlock
|
||||
String sql = "SELECT COUNT(*) "
|
||||
+ "FROM Information_schema.system_sessions "
|
||||
+ "WHERE transaction = TRUE";
|
||||
|
||||
try {
|
||||
PreparedStatement pstmt = this.cachePreparedStatement(sql);
|
||||
|
||||
if (!pstmt.execute())
|
||||
throw new DataException("Unable to check repository session status");
|
||||
|
||||
try (ResultSet resultSet = pstmt.getResultSet()) {
|
||||
if (resultSet == null || !resultSet.next())
|
||||
// Failed to even find HSQLDB session info!
|
||||
throw new DataException("No results when checking repository session status");
|
||||
|
||||
int transactionCount = resultSet.getInt(1);
|
||||
|
||||
if (transactionCount > 0)
|
||||
// We can't safely perform CHECKPOINT due to ongoing SQL transactions
|
||||
return;
|
||||
}
|
||||
|
||||
LOGGER.info("Performing repository CHECKPOINT...");
|
||||
|
||||
if (Settings.getInstance().getShowCheckpointNotification())
|
||||
SysTray.getInstance().showMessage(Translator.INSTANCE.translate("SysTray", "DB_CHECKPOINT"),
|
||||
Translator.INSTANCE.translate("SysTray", "PERFORMING_DB_CHECKPOINT"),
|
||||
MessageType.INFO);
|
||||
|
||||
try (Statement stmt = this.connection.createStatement()) {
|
||||
stmt.execute(Boolean.TRUE.equals(quickCheckpointRequest) ? "CHECKPOINT" : "CHECKPOINT DEFRAG");
|
||||
}
|
||||
|
||||
// Completed!
|
||||
LOGGER.info("Repository CHECKPOINT completed!");
|
||||
RepositoryManager.setRequestedCheckpoint(null);
|
||||
} catch (SQLException e) {
|
||||
throw new DataException("Unable to check repository session status", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void rebuild() throws DataException {
|
||||
LOGGER.info("Rebuilding repository from scratch");
|
||||
@@ -379,15 +445,6 @@ public class HSQLDBRepository implements Repository {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void checkpoint(boolean quick) throws DataException {
|
||||
try (Statement stmt = this.connection.createStatement()) {
|
||||
stmt.execute(quick ? "CHECKPOINT" : "CHECKPOINT DEFRAG");
|
||||
} catch (SQLException e) {
|
||||
throw new DataException("Unable to perform repository checkpoint");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void performPeriodicMaintenance() throws DataException {
|
||||
// Defrag DB - takes a while!
|
||||
@@ -654,7 +711,16 @@ public class HSQLDBRepository implements Repository {
|
||||
|
||||
long beforeQuery = this.slowQueryThreshold == null ? 0 : System.currentTimeMillis();
|
||||
|
||||
int[] updateCounts = preparedStatement.executeBatch();
|
||||
int[] updateCounts = null;
|
||||
try {
|
||||
updateCounts = preparedStatement.executeBatch();
|
||||
} catch (SQLException e) {
|
||||
if (isDeadlockException(e))
|
||||
// We want more info on what other DB sessions are doing to cause this
|
||||
examineException(e);
|
||||
|
||||
throw e;
|
||||
}
|
||||
|
||||
if (this.slowQueryThreshold != null) {
|
||||
long queryTime = System.currentTimeMillis() - beforeQuery;
|
||||
@@ -946,4 +1012,8 @@ public class HSQLDBRepository implements Repository {
|
||||
return Crypto.toAddress(publicKey);
|
||||
}
|
||||
|
||||
/*package*/ static boolean isDeadlockException(SQLException e) {
|
||||
return DEADLOCK_ERROR_CODE.equals(e.getErrorCode());
|
||||
}
|
||||
|
||||
}
|
@@ -94,7 +94,11 @@ public class HSQLDBRepositoryFactory implements RepositoryFactory {
|
||||
@Override
|
||||
public Repository tryRepository() throws DataException {
|
||||
try {
|
||||
return new HSQLDBRepository(this.tryConnection());
|
||||
Connection connection = this.tryConnection();
|
||||
if (connection == null)
|
||||
return null;
|
||||
|
||||
return new HSQLDBRepository(connection);
|
||||
} catch (SQLException e) {
|
||||
throw new DataException("Repository instantiation error", e);
|
||||
}
|
||||
@@ -144,4 +148,9 @@ public class HSQLDBRepositoryFactory implements RepositoryFactory {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDeadlockException(SQLException e) {
|
||||
return HSQLDBRepository.isDeadlockException(e);
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -9,6 +9,7 @@ import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.EnumMap;
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@@ -1181,6 +1182,51 @@ public class HSQLDBTransactionRepository implements TransactionRepository {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<TransactionData> getUnconfirmedTransactions(EnumSet<TransactionType> excludedTxTypes) throws DataException {
|
||||
StringBuilder sql = new StringBuilder(1024);
|
||||
sql.append("SELECT signature FROM UnconfirmedTransactions ");
|
||||
sql.append("JOIN Transactions USING (signature) ");
|
||||
sql.append("WHERE type NOT IN (");
|
||||
|
||||
boolean firstTxType = true;
|
||||
for (TransactionType txType : excludedTxTypes) {
|
||||
if (firstTxType)
|
||||
firstTxType = false;
|
||||
else
|
||||
sql.append(", ");
|
||||
|
||||
sql.append(txType.value);
|
||||
}
|
||||
|
||||
sql.append(")");
|
||||
sql.append("ORDER BY created_when, signature");
|
||||
|
||||
List<TransactionData> transactions = new ArrayList<>();
|
||||
|
||||
// Find transactions with no corresponding row in BlockTransactions
|
||||
try (ResultSet resultSet = this.repository.checkedExecute(sql.toString())) {
|
||||
if (resultSet == null)
|
||||
return transactions;
|
||||
|
||||
do {
|
||||
byte[] signature = resultSet.getBytes(1);
|
||||
|
||||
TransactionData transactionData = this.fromSignature(signature);
|
||||
|
||||
if (transactionData == null)
|
||||
// Something inconsistent with the repository
|
||||
throw new DataException(String.format("Unable to fetch unconfirmed transaction %s from repository?", Base58.encode(signature)));
|
||||
|
||||
transactions.add(transactionData);
|
||||
} while (resultSet.next());
|
||||
|
||||
return transactions;
|
||||
} catch (SQLException | DataException e) {
|
||||
throw new DataException("Unable to fetch unconfirmed transactions from repository", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void confirmTransaction(byte[] signature) throws DataException {
|
||||
try {
|
||||
|
@@ -89,6 +89,8 @@ public class Settings {
|
||||
private long repositoryCheckpointInterval = 60 * 60 * 1000L; // 1 hour (ms) default
|
||||
/** Whether to show a notification when we perform repository 'checkpoint'. */
|
||||
private boolean showCheckpointNotification = false;
|
||||
/* How many blocks to cache locally. Defaulted to 10, which covers a typical Synchronizer request + a few spare */
|
||||
private int blockCacheSize = 10;
|
||||
|
||||
/** How long to keep old, full, AT state data (ms). */
|
||||
private long atStatesMaxLifetime = 2 * 7 * 24 * 60 * 60 * 1000L; // milliseconds
|
||||
@@ -361,6 +363,10 @@ public class Settings {
|
||||
return this.maxTransactionTimestampFuture;
|
||||
}
|
||||
|
||||
public int getBlockCacheSize() {
|
||||
return this.blockCacheSize;
|
||||
}
|
||||
|
||||
public boolean isTestNet() {
|
||||
return this.isTestNet;
|
||||
}
|
||||
|
@@ -4,6 +4,7 @@ import java.math.BigInteger;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
import java.util.EnumSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -605,7 +606,8 @@ public abstract class Transaction {
|
||||
public static List<TransactionData> getUnconfirmedTransactions(Repository repository) throws DataException {
|
||||
BlockData latestBlockData = repository.getBlockRepository().getLastBlock();
|
||||
|
||||
List<TransactionData> unconfirmedTransactions = repository.getTransactionRepository().getUnconfirmedTransactions();
|
||||
EnumSet<TransactionType> excludedTxTypes = EnumSet.of(TransactionType.CHAT, TransactionType.PRESENCE);
|
||||
List<TransactionData> unconfirmedTransactions = repository.getTransactionRepository().getUnconfirmedTransactions(excludedTxTypes);
|
||||
|
||||
unconfirmedTransactions.sort(getDataComparator());
|
||||
|
||||
|
@@ -326,24 +326,36 @@ public class BlockTransformer extends Transformer {
|
||||
}
|
||||
}
|
||||
|
||||
public static byte[] getMinterSignatureFromReference(byte[] blockReference) {
|
||||
return Arrays.copyOf(blockReference, MINTER_SIGNATURE_LENGTH);
|
||||
private static byte[] getReferenceBytesForMinterSignature(int blockHeight, byte[] reference) {
|
||||
int newBlockSigTriggerHeight = BlockChain.getInstance().getNewBlockSigHeight();
|
||||
|
||||
return blockHeight >= newBlockSigTriggerHeight
|
||||
// 'new' block sig uses all of previous block's signature
|
||||
? reference
|
||||
// 'old' block sig only uses first 64 bytes of previous block's signature
|
||||
: Arrays.copyOf(reference, MINTER_SIGNATURE_LENGTH);
|
||||
}
|
||||
|
||||
public static byte[] getBytesForMinterSignature(BlockData blockData) throws TransformationException {
|
||||
byte[] minterSignature = getMinterSignatureFromReference(blockData.getReference());
|
||||
public static byte[] getBytesForMinterSignature(BlockData blockData) {
|
||||
byte[] referenceBytes = getReferenceBytesForMinterSignature(blockData.getHeight(), blockData.getReference());
|
||||
|
||||
return getBytesForMinterSignature(minterSignature, blockData.getMinterPublicKey(), blockData.getEncodedOnlineAccounts());
|
||||
return getBytesForMinterSignature(referenceBytes, blockData.getMinterPublicKey(), blockData.getEncodedOnlineAccounts());
|
||||
}
|
||||
|
||||
public static byte[] getBytesForMinterSignature(byte[] minterSignature, byte[] minterPublicKey, byte[] encodedOnlineAccounts) {
|
||||
byte[] bytes = new byte[MINTER_SIGNATURE_LENGTH + MINTER_PUBLIC_KEY_LENGTH + encodedOnlineAccounts.length];
|
||||
public static byte[] getBytesForMinterSignature(BlockData parentBlockData, byte[] minterPublicKey, byte[] encodedOnlineAccounts) {
|
||||
byte[] referenceBytes = getReferenceBytesForMinterSignature(parentBlockData.getHeight() + 1, parentBlockData.getSignature());
|
||||
|
||||
System.arraycopy(minterSignature, 0, bytes, 0, MINTER_SIGNATURE_LENGTH);
|
||||
return getBytesForMinterSignature(referenceBytes, minterPublicKey, encodedOnlineAccounts);
|
||||
}
|
||||
|
||||
System.arraycopy(minterPublicKey, 0, bytes, MINTER_SIGNATURE_LENGTH, MINTER_PUBLIC_KEY_LENGTH);
|
||||
private static byte[] getBytesForMinterSignature(byte[] referenceBytes, byte[] minterPublicKey, byte[] encodedOnlineAccounts) {
|
||||
byte[] bytes = new byte[referenceBytes.length + MINTER_PUBLIC_KEY_LENGTH + encodedOnlineAccounts.length];
|
||||
|
||||
System.arraycopy(encodedOnlineAccounts, 0, bytes, MINTER_SIGNATURE_LENGTH + MINTER_PUBLIC_KEY_LENGTH, encodedOnlineAccounts.length);
|
||||
System.arraycopy(referenceBytes, 0, bytes, 0, referenceBytes.length);
|
||||
|
||||
System.arraycopy(minterPublicKey, 0, bytes, referenceBytes.length, MINTER_PUBLIC_KEY_LENGTH);
|
||||
|
||||
System.arraycopy(encodedOnlineAccounts, 0, bytes, referenceBytes.length + MINTER_PUBLIC_KEY_LENGTH, encodedOnlineAccounts.length);
|
||||
|
||||
return bytes;
|
||||
}
|
||||
|
BIN
src/main/resources/.DS_Store
vendored
Normal file
BIN
src/main/resources/.DS_Store
vendored
Normal file
Binary file not shown.
@@ -48,7 +48,9 @@
|
||||
"minutesPerBlock": 1
|
||||
},
|
||||
"featureTriggers": {
|
||||
"atFindNextTransactionFix": 275000
|
||||
"atFindNextTransactionFix": 275000,
|
||||
"newBlockSigHeight": 320000,
|
||||
"shareBinFix": 999999
|
||||
},
|
||||
"genesisInfo": {
|
||||
"version": 4,
|
||||
|
71
src/main/resources/i18n/ApiError_fi.properties
Normal file
71
src/main/resources/i18n/ApiError_fi.properties
Normal file
@@ -0,0 +1,71 @@
|
||||
#Generated by ResourceBundle Editor (http://essiembre.github.io/eclipse-rbe/)
|
||||
# Keys are from api.ApiError enum
|
||||
#
|
||||
# Kielen muuttaminen suomeksi tapahtuu settings.json-tiedostossa
|
||||
#
|
||||
# "localeLang": "fi",
|
||||
# muista pilkku lopussa jos komento ei ole viimeisellä rivillä
|
||||
|
||||
ADDRESS_UNKNOWN = tilin osoite on tuntematon
|
||||
|
||||
BLOCKCHAIN_NEEDS_SYNC = lohkoketjun tarvitsee ensin synkronisoitua
|
||||
|
||||
# Blocks
|
||||
BLOCK_UNKNOWN = tuntematon lohko
|
||||
|
||||
BTC_BALANCE_ISSUE = riittämätön Bitcoin-saldo
|
||||
|
||||
BTC_NETWORK_ISSUE = Bitcoin/ElectrumX -verkon ongelma
|
||||
|
||||
BTC_TOO_SOON = liian aikaista julkistaa Bitcoin-tapahtumaa (lukitusaika/mediiaanilohkoaika)
|
||||
|
||||
CANNOT_MINT = tili ei voi lyödä rahaa
|
||||
|
||||
GROUP_UNKNOWN = tuntematon ryhmä
|
||||
|
||||
INVALID_ADDRESS = osoite on kelvoton
|
||||
|
||||
# Assets
|
||||
INVALID_ASSET_ID = kelvoton ID resurssille
|
||||
|
||||
INVALID_CRITERIA = kelvoton hakuehto
|
||||
|
||||
INVALID_DATA = kelvoton data
|
||||
|
||||
INVALID_HEIGHT = kelvoton lohkon korkeus
|
||||
|
||||
INVALID_NETWORK_ADDRESS = kelvoton verkko-osoite
|
||||
|
||||
INVALID_ORDER_ID = kelvoton resurssin tilaus-ID
|
||||
|
||||
INVALID_PRIVATE_KEY = kelvoton yksityinen avain
|
||||
|
||||
INVALID_PUBLIC_KEY = kelvoton julkinen avain
|
||||
|
||||
INVALID_REFERENCE = kelvoton viite
|
||||
|
||||
# Validation
|
||||
INVALID_SIGNATURE = kelvoton allekirjoitus
|
||||
|
||||
JSON = JSON-viestin jaottelu epäonnistui
|
||||
|
||||
NAME_UNKNOWN = tuntematon nimi
|
||||
|
||||
NON_PRODUCTION = tämä API-kutsu on kielletty tuotantoversiossa
|
||||
|
||||
NO_TIME_SYNC = kello vielä synkronisoimatta
|
||||
|
||||
ORDER_UNKNOWN = tuntematon resurssin tilaus-ID
|
||||
|
||||
PUBLIC_KEY_NOT_FOUND = julkista avainta ei löytynyt
|
||||
|
||||
REPOSITORY_ISSUE = tietovarantovirhe (repo)
|
||||
|
||||
# This one is special in that caller expected to pass two additional strings, hence the two %s
|
||||
TRANSACTION_INVALID = kelvoton transaktio: %s (%s)
|
||||
|
||||
TRANSACTION_UNKNOWN = tuntematon transaktio
|
||||
|
||||
TRANSFORMATION_ERROR = JSON:in muuntaminen transaktioksi epäonnistui
|
||||
|
||||
UNAUTHORIZED = luvaton API-kutsu
|
72
src/main/resources/i18n/ApiError_it.properties
Normal file
72
src/main/resources/i18n/ApiError_it.properties
Normal file
@@ -0,0 +1,72 @@
|
||||
#Generated by ResourceBundle Editor (http://essiembre.github.io/eclipse-rbe/)
|
||||
# Keys are from api.ApiError enum
|
||||
# Italian translation by Pabs 2021
|
||||
|
||||
# La modifica della lingua dell'UI è fatta nel file Settings.json
|
||||
#
|
||||
# "localeLang": "it",
|
||||
# Si prega ricordare la virgola alla fine, se questo comando non è sull'ultima riga
|
||||
|
||||
ADDRESS_UNKNOWN = indirizzo account sconosciuto
|
||||
|
||||
BLOCKCHAIN_NEEDS_SYNC = blockchain deve prima sincronizzarsi
|
||||
|
||||
# Blocks
|
||||
BLOCK_UNKNOWN = blocco sconosciuto
|
||||
|
||||
BTC_BALANCE_ISSUE = saldo Bitcoin insufficiente
|
||||
|
||||
BTC_NETWORK_ISSUE = Bitcoin/ElectrumX problema di rete
|
||||
|
||||
BTC_TOO_SOON = troppo presto per trasmettere transazione Bitcoin (tempo di blocco / tempo di blocco mediano)
|
||||
|
||||
CANNOT_MINT = l'account non può coniare
|
||||
|
||||
GROUP_UNKNOWN = gruppo sconosciuto
|
||||
|
||||
INVALID_ADDRESS = indirizzo non valido
|
||||
|
||||
# Assets
|
||||
INVALID_ASSET_ID = identificazione risorsa non valida
|
||||
|
||||
INVALID_CRITERIA = criteri di ricerca non validi
|
||||
|
||||
INVALID_DATA = dati non validi
|
||||
|
||||
INVALID_HEIGHT = altezza blocco non valida
|
||||
|
||||
INVALID_NETWORK_ADDRESS = indirizzo di rete non valido
|
||||
|
||||
INVALID_ORDER_ID = identificazione di ordine di risorsa non valida
|
||||
|
||||
INVALID_PRIVATE_KEY = chiave privata non valida
|
||||
|
||||
INVALID_PUBLIC_KEY = chiave pubblica non valida
|
||||
|
||||
INVALID_REFERENCE = riferimento non valido
|
||||
|
||||
# Validation
|
||||
INVALID_SIGNATURE = firma non valida
|
||||
|
||||
JSON = Impossibile analizzare il messaggio JSON
|
||||
|
||||
NAME_UNKNOWN = nome sconosciuto
|
||||
|
||||
NON_PRODUCTION = questa chiamata API non è consentita per i sistemi di produzione
|
||||
|
||||
NO_TIME_SYNC = nessuna sincronizzazione dell'orologio ancora
|
||||
|
||||
ORDER_UNKNOWN = identificazione di ordine di risorsa sconosciuta
|
||||
|
||||
PUBLIC_KEY_NOT_FOUND = chiave pubblica non trovata
|
||||
|
||||
REPOSITORY_ISSUE = errore del repositorio
|
||||
|
||||
# This one is special in that caller expected to pass two additional strings, hence the two %s
|
||||
TRANSACTION_INVALID = transazione non valida: %s (%s)
|
||||
|
||||
TRANSACTION_UNKNOWN = transazione sconosciuta
|
||||
|
||||
TRANSFORMATION_ERROR = non è stato possibile trasformare JSON in transazione
|
||||
|
||||
UNAUTHORIZED = Chiamata API non autorizzata
|
45
src/main/resources/i18n/SysTray_fi.properties
Normal file
45
src/main/resources/i18n/SysTray_fi.properties
Normal file
@@ -0,0 +1,45 @@
|
||||
#Generated by ResourceBundle Editor (http://essiembre.github.io/eclipse-rbe/)
|
||||
# SysTray pop-up menu
|
||||
|
||||
APPLYING_UPDATE_AND_RESTARTING = Automaattinen päivitys käynnissä, uudelleenkäynnistys seuraa...
|
||||
|
||||
AUTO_UPDATE = Automaattinen päivitys
|
||||
|
||||
BLOCK_HEIGHT = korkeus
|
||||
|
||||
CHECK_TIME_ACCURACY = Tarkista ajan tarkkuus
|
||||
|
||||
CONNECTING = Yhdistää
|
||||
|
||||
CONNECTION = yhteys
|
||||
|
||||
CONNECTIONS = yhteyttä
|
||||
|
||||
CREATING_BACKUP_OF_DB_FILES = Luodaan varmuuskopio tietokannan tiedostoista...
|
||||
|
||||
DB_BACKUP = Tietokannan varmuuskopio
|
||||
|
||||
DB_CHECKPOINT = Tietokannan varmistuspiste
|
||||
|
||||
EXIT = Pois
|
||||
|
||||
MINTING_DISABLED = EI lyö rahaa
|
||||
|
||||
MINTING_ENABLED = \u2714 Lyö rahaa
|
||||
|
||||
# Nagging about lack of NTP time sync
|
||||
NTP_NAG_CAPTION = Tietokoneen kello on epätarkka!
|
||||
|
||||
NTP_NAG_TEXT_UNIX = Asennathan NTP-palvelun, jotta saat kellon tarkkuuden oikeaksi.
|
||||
|
||||
NTP_NAG_TEXT_WINDOWS = Valitse "Kellon synkronisointi" valikosta korjataksesi.
|
||||
|
||||
OPEN_UI = Avaa UI
|
||||
|
||||
PERFORMING_DB_CHECKPOINT = Tallentaa kommittoidut tietokantamuutokset...
|
||||
|
||||
SYNCHRONIZE_CLOCK = Synkronisoi kello
|
||||
|
||||
SYNCHRONIZING_BLOCKCHAIN = Synkronisoi
|
||||
|
||||
SYNCHRONIZING_CLOCK = Synkronisoi kelloa
|
46
src/main/resources/i18n/SysTray_it.properties
Normal file
46
src/main/resources/i18n/SysTray_it.properties
Normal file
@@ -0,0 +1,46 @@
|
||||
#Generated by ResourceBundle Editor (http://essiembre.github.io/eclipse-rbe/)
|
||||
# SysTray pop-up menu
|
||||
# Italian translation by Pabs 2021
|
||||
|
||||
APPLYING_UPDATE_AND_RESTARTING = Applicando aggiornamento automatico e riavviando...
|
||||
|
||||
AUTO_UPDATE = Aggiornamento automatico
|
||||
|
||||
BLOCK_HEIGHT = altezza
|
||||
|
||||
CHECK_TIME_ACCURACY = Controlla la precisione dell'ora
|
||||
|
||||
CONNECTING = Collegando
|
||||
|
||||
CONNECTION = connessione
|
||||
|
||||
CONNECTIONS = connessioni
|
||||
|
||||
CREATING_BACKUP_OF_DB_FILES = Creazione di backup dei file di database...
|
||||
|
||||
DB_BACKUP = Backup del database
|
||||
|
||||
DB_CHECKPOINT = Punto di controllo del database
|
||||
|
||||
EXIT = Uscita
|
||||
|
||||
MINTING_DISABLED = NON coniando
|
||||
|
||||
MINTING_ENABLED = \u2714 Coniando
|
||||
|
||||
# Nagging about lack of NTP time sync
|
||||
NTP_NAG_CAPTION = L'orologio del computer è impreciso!
|
||||
|
||||
NTP_NAG_TEXT_UNIX = Installare servizio NTP per ottenere un orologio preciso.
|
||||
|
||||
NTP_NAG_TEXT_WINDOWS = Seleziona "Sincronizza orologio" dal menu per correggere.
|
||||
|
||||
OPEN_UI = Apri UI
|
||||
|
||||
PERFORMING_DB_CHECKPOINT = Salvataggio delle modifiche al database non salvate...
|
||||
|
||||
SYNCHRONIZE_CLOCK = Sincronizza orologio
|
||||
|
||||
SYNCHRONIZING_BLOCKCHAIN = Sincronizzando
|
||||
|
||||
SYNCHRONIZING_CLOCK = Sincronizzando orologio
|
184
src/main/resources/i18n/TransactionValidity_fi.properties
Normal file
184
src/main/resources/i18n/TransactionValidity_fi.properties
Normal file
@@ -0,0 +1,184 @@
|
||||
|
||||
ACCOUNT_ALREADY_EXISTS = tili on jo olemassa
|
||||
|
||||
ACCOUNT_CANNOT_REWARD_SHARE = tili ei voi palkinto-jakaa
|
||||
|
||||
ALREADY_GROUP_ADMIN = on jo ryhmän admin
|
||||
|
||||
ALREADY_GROUP_MEMBER = on jo ryhmän jäsen
|
||||
|
||||
ALREADY_VOTED_FOR_THAT_OPTION = on jo äänestänyt vaihtoehtoa
|
||||
|
||||
ASSET_ALREADY_EXISTS = resurssi on jo olemassa
|
||||
|
||||
ASSET_DOES_NOT_EXIST = resurssia ei ole olemassa
|
||||
|
||||
ASSET_DOES_NOT_MATCH_AT = resurssi ei vastaa AT:n resurssia
|
||||
|
||||
ASSET_NOT_SPENDABLE = resurssi ei ole kulutettavaa laatua
|
||||
|
||||
AT_ALREADY_EXISTS = AT on jo olemassa
|
||||
|
||||
AT_IS_FINISHED = AT on päättynyt
|
||||
|
||||
AT_UNKNOWN = AT on tuntematon
|
||||
|
||||
BANNED_FROM_GROUP = on evätty ryhmän jäsenyydestä
|
||||
|
||||
BAN_EXISTS = eväys on jo olemassa
|
||||
|
||||
BAN_UNKNOWN = tuntematon eväys
|
||||
|
||||
BUYER_ALREADY_OWNER = ostaja on jo omistaja
|
||||
|
||||
CHAT = CHATin transaktiot eivät koskaan ole kelvollisia sisällytettäväksi lohkoihin
|
||||
|
||||
CLOCK_NOT_SYNCED = kello on synkronisoimatta
|
||||
|
||||
DUPLICATE_OPTION = kahdennettu valinta
|
||||
|
||||
GROUP_ALREADY_EXISTS = ryhmä on jo olemassa
|
||||
|
||||
GROUP_APPROVAL_DECIDED = ryhmä-hyväksyminen jo päätetty
|
||||
|
||||
GROUP_APPROVAL_NOT_REQUIRED = ryhmä-hyväksyminen tarpeeton
|
||||
|
||||
GROUP_DOES_NOT_EXIST = ryhmää ei ole
|
||||
|
||||
GROUP_ID_MISMATCH = ryhmän ID:n vastaavuusvirhe
|
||||
|
||||
GROUP_OWNER_CANNOT_LEAVE = ryhmän omistaja ei voi jättää ryhmää
|
||||
|
||||
HAVE_EQUALS_WANT = have-resurssi on sama kuin want-resurssi
|
||||
|
||||
INCORRECT_NONCE = virheellinen PoW nonce
|
||||
|
||||
INSUFFICIENT_FEE = riittämätön kulu
|
||||
|
||||
INVALID_ADDRESS = kelvoton osoite
|
||||
|
||||
INVALID_AMOUNT = kelvoton summa
|
||||
|
||||
INVALID_ASSET_OWNER = kelvoton resurssin omistaja
|
||||
|
||||
INVALID_AT_TRANSACTION = kelvoton AT-transaktio
|
||||
|
||||
INVALID_AT_TYPE_LENGTH = kelvoton AT 'tyypin' pituus
|
||||
|
||||
INVALID_CREATION_BYTES = kelvoton luodun tavumäärä
|
||||
|
||||
INVALID_DATA_LENGTH = kelvoton datan pituus
|
||||
|
||||
INVALID_DESCRIPTION_LENGTH = kelvoton kuvauksen pituus
|
||||
|
||||
INVALID_GROUP_APPROVAL_THRESHOLD = kelvoton ryhmä-hyväksymisen alaraja
|
||||
|
||||
INVALID_GROUP_BLOCK_DELAY = kelvoton ryhmä-hyväksymisen lohkon viive
|
||||
|
||||
INVALID_GROUP_ID = kelvoton ryhmän ID
|
||||
|
||||
INVALID_GROUP_OWNER = kelvoton ryhmän omistaja
|
||||
|
||||
INVALID_LIFETIME = kelvoton elinaika
|
||||
|
||||
INVALID_NAME_LENGTH = kelvoton nimen pituus
|
||||
|
||||
INVALID_NAME_OWNER = kelvoton nimen omistaja
|
||||
|
||||
INVALID_OPTIONS_COUNT = kelvoton valintojen lkm
|
||||
|
||||
INVALID_OPTION_LENGTH = kelvoton valintojen pituus
|
||||
|
||||
INVALID_ORDER_CREATOR = kelvoton tilauksen luoja
|
||||
|
||||
INVALID_PAYMENTS_COUNT = kelvoton maksujen lkm
|
||||
|
||||
INVALID_PUBLIC_KEY = kelvoton julkinen avain
|
||||
|
||||
INVALID_QUANTITY = kelvoton määrä
|
||||
|
||||
INVALID_REFERENCE = kelvoton viite
|
||||
|
||||
INVALID_RETURN = kelvoton palautusarvo
|
||||
|
||||
INVALID_REWARD_SHARE_PERCENT = kelvoton palkkiojaon prosenttiosuus
|
||||
|
||||
INVALID_SELLER = kelvoton myyjä
|
||||
|
||||
INVALID_TAGS_LENGTH = kelvoton 'tagin' pituus
|
||||
|
||||
INVALID_TX_GROUP_ID = kelvoton transaktion ryhmä-ID
|
||||
|
||||
INVALID_VALUE_LENGTH = kelvoton 'arvon' pituus
|
||||
|
||||
INVITE_UNKNOWN = tuntematon ryhmän kutsu
|
||||
|
||||
JOIN_REQUEST_EXISTS = ryhmään liittymispyyntö on jo olemassa
|
||||
|
||||
MAXIMUM_REWARD_SHARES = tämän tilin suurin sallittu palkkiojaon lkm on saavutettu
|
||||
|
||||
MISSING_CREATOR = luoja puuttuu
|
||||
|
||||
MULTIPLE_NAMES_FORBIDDEN = yhdelle tilille sallitaan vain yksi rekisteröity nimi
|
||||
|
||||
NAME_ALREADY_FOR_SALE = nimi on jo myynnissä
|
||||
|
||||
NAME_ALREADY_REGISTERED = nimi on jo rekisteröity
|
||||
|
||||
NAME_DOES_NOT_EXIST = nimeä ei ole
|
||||
|
||||
NAME_NOT_FOR_SALE = nimi ei ole kaupan
|
||||
|
||||
NAME_NOT_NORMALIZED = nimi ei ole Unicode 'normalisoitua' muotoa
|
||||
|
||||
NEGATIVE_AMOUNT = kelvoton/negatiivinen summa
|
||||
|
||||
NEGATIVE_FEE = kelvoton/negatiivinen kulu
|
||||
|
||||
NEGATIVE_PRICE = kelvoton/negatiivinen hinta
|
||||
|
||||
NOT_GROUP_ADMIN = tili ei ole ryhmän admin
|
||||
|
||||
NOT_GROUP_MEMBER = tili ei ole ryhmän jäsen
|
||||
|
||||
NOT_MINTING_ACCOUNT = tili ei voi lyödä rahaa
|
||||
|
||||
NOT_YET_RELEASED = ominaisuutta ei ole vielä julkistettu
|
||||
|
||||
NO_BALANCE = riittämätön saldo
|
||||
|
||||
NO_BLOCKCHAIN_LOCK = solmun lohkoketju on juuri nyt varattuna
|
||||
|
||||
NO_FLAG_PERMISSION = tilillä ei ole lupaa tuohon
|
||||
|
||||
OK = OK
|
||||
|
||||
ORDER_ALREADY_CLOSED = resurssin määräys kauppaan on jo suljettu
|
||||
|
||||
ORDER_DOES_NOT_EXIST = resurssin määräystä kauppaan ei ole
|
||||
|
||||
POLL_ALREADY_EXISTS = kysely on jo olemassa
|
||||
|
||||
POLL_DOES_NOT_EXIST = kyselyä ei ole
|
||||
|
||||
POLL_OPTION_DOES_NOT_EXIST = kyselyn tuota valintaa ei ole olemassa
|
||||
|
||||
PUBLIC_KEY_UNKNOWN = tuntematon julkinen avain
|
||||
|
||||
REWARD_SHARE_UNKNOWN = tuntematon palkkiojako
|
||||
|
||||
SELF_SHARE_EXISTS = itse-jako (palkkiojako) on jo olemassa
|
||||
|
||||
TIMESTAMP_TOO_NEW = aikaleima on liian tuore
|
||||
|
||||
TIMESTAMP_TOO_OLD = aikaleima on liian vanha
|
||||
|
||||
TOO_MANY_UNCONFIRMED = tilillä on liian monta vahvistamatonta transaktiota tekeillä
|
||||
|
||||
TRANSACTION_ALREADY_CONFIRMED = transaktio on jo vahvistettu
|
||||
|
||||
TRANSACTION_ALREADY_EXISTS = transaktio on jo olemassa
|
||||
|
||||
TRANSACTION_UNKNOWN = tuntematon transaktio
|
||||
|
||||
TX_GROUP_ID_MISMATCH = transaktion ryhmä-ID:n vastaavuusvirhe
|
185
src/main/resources/i18n/TransactionValidity_it.properties
Normal file
185
src/main/resources/i18n/TransactionValidity_it.properties
Normal file
@@ -0,0 +1,185 @@
|
||||
# Italian translation by Pabs 2021
|
||||
|
||||
ACCOUNT_ALREADY_EXISTS = l'account gia esiste
|
||||
|
||||
ACCOUNT_CANNOT_REWARD_SHARE = l'account non può fare la condivisione di ricompensa
|
||||
|
||||
ALREADY_GROUP_ADMIN = è già amministratore del gruppo
|
||||
|
||||
ALREADY_GROUP_MEMBER = è già membro del gruppo
|
||||
|
||||
ALREADY_VOTED_FOR_THAT_OPTION = già votato per questa opzione
|
||||
|
||||
ASSET_ALREADY_EXISTS = risorsa già esistente
|
||||
|
||||
ASSET_DOES_NOT_EXIST = risorsa non esistente
|
||||
|
||||
ASSET_DOES_NOT_MATCH_AT = l'asset non corrisponde all'asset di AT
|
||||
|
||||
ASSET_NOT_SPENDABLE = la risorsa non è spendibile
|
||||
|
||||
AT_ALREADY_EXISTS = AT gia esiste
|
||||
|
||||
AT_IS_FINISHED = AT ha finito
|
||||
|
||||
AT_UNKNOWN = AT sconosciuto
|
||||
|
||||
BANNED_FROM_GROUP = divietato dal gruppo
|
||||
|
||||
BAN_EXISTS = il divieto esiste già
|
||||
|
||||
BAN_UNKNOWN = divieto sconosciuto
|
||||
|
||||
BUYER_ALREADY_OWNER = l'acquirente è già proprietario
|
||||
|
||||
CHAT = Le transazioni CHAT non sono mai valide per l'inclusione nei blocchi
|
||||
|
||||
CLOCK_NOT_SYNCED = orologio non sincronizzato
|
||||
|
||||
DUPLICATE_OPTION = opzione duplicata
|
||||
|
||||
GROUP_ALREADY_EXISTS = gruppo già esistente
|
||||
|
||||
GROUP_APPROVAL_DECIDED = approvazione di gruppo già decisa
|
||||
|
||||
GROUP_APPROVAL_NOT_REQUIRED = approvazione di gruppo non richiesto
|
||||
|
||||
GROUP_DOES_NOT_EXIST = gruppo non esiste
|
||||
|
||||
GROUP_ID_MISMATCH = identificazione di gruppo non corrispondente
|
||||
|
||||
GROUP_OWNER_CANNOT_LEAVE = il proprietario del gruppo non può lasciare il gruppo
|
||||
|
||||
HAVE_EQUALS_WANT = la risorsa avere è uguale a la risorsa volere
|
||||
|
||||
INCORRECT_NONCE = PoW nonce sbagliato
|
||||
|
||||
INSUFFICIENT_FEE = tariffa insufficiente
|
||||
|
||||
INVALID_ADDRESS = indirizzo non valido
|
||||
|
||||
INVALID_AMOUNT = importo non valido
|
||||
|
||||
INVALID_ASSET_OWNER = proprietario della risorsa non valido
|
||||
|
||||
INVALID_AT_TRANSACTION = transazione AT non valida
|
||||
|
||||
INVALID_AT_TYPE_LENGTH = lunghezza di "tipo" AT non valida
|
||||
|
||||
INVALID_CREATION_BYTES = byte di creazione non validi
|
||||
|
||||
INVALID_DATA_LENGTH = lunghezza di dati non valida
|
||||
|
||||
INVALID_DESCRIPTION_LENGTH = lunghezza della descrizione non valida
|
||||
|
||||
INVALID_GROUP_APPROVAL_THRESHOLD = soglia di approvazione del gruppo non valida
|
||||
|
||||
INVALID_GROUP_BLOCK_DELAY = ritardo del blocco di approvazione del gruppo non valido
|
||||
|
||||
INVALID_GROUP_ID = identificazione di gruppo non valida
|
||||
|
||||
INVALID_GROUP_OWNER = proprietario di gruppo non valido
|
||||
|
||||
INVALID_LIFETIME = durata della vita non valida
|
||||
|
||||
INVALID_NAME_LENGTH = lunghezza del nome non valida
|
||||
|
||||
INVALID_NAME_OWNER = proprietario del nome non valido
|
||||
|
||||
INVALID_OPTIONS_COUNT = conteggio di opzioni non validi
|
||||
|
||||
INVALID_OPTION_LENGTH = lunghezza di opzioni non valida
|
||||
|
||||
INVALID_ORDER_CREATOR = creatore dell'ordine non valido
|
||||
|
||||
INVALID_PAYMENTS_COUNT = conteggio pagamenti non validi
|
||||
|
||||
INVALID_PUBLIC_KEY = chiave pubblica non valida
|
||||
|
||||
INVALID_QUANTITY = quantità non valida
|
||||
|
||||
INVALID_REFERENCE = riferimento non valido
|
||||
|
||||
INVALID_RETURN = ritorno non valido
|
||||
|
||||
INVALID_REWARD_SHARE_PERCENT = percentuale condivisione di ricompensa non valida
|
||||
|
||||
INVALID_SELLER = venditore non valido
|
||||
|
||||
INVALID_TAGS_LENGTH = lunghezza dei "tag" non valida
|
||||
|
||||
INVALID_TX_GROUP_ID = identificazione di gruppo di transazioni non valida
|
||||
|
||||
INVALID_VALUE_LENGTH = lunghezza "valore" non valida
|
||||
|
||||
INVITE_UNKNOWN = invito di gruppo sconosciuto
|
||||
|
||||
JOIN_REQUEST_EXISTS = la richiesta di iscrizione al gruppo già esiste
|
||||
|
||||
MAXIMUM_REWARD_SHARES = numero massimo di condivisione di ricompensa raggiunto per l'account
|
||||
|
||||
MISSING_CREATOR = creatore mancante
|
||||
|
||||
MULTIPLE_NAMES_FORBIDDEN = è vietata la registrazione di multipli nomi per account
|
||||
|
||||
NAME_ALREADY_FOR_SALE = nome già in vendita
|
||||
|
||||
NAME_ALREADY_REGISTERED = nome già registrato
|
||||
|
||||
NAME_DOES_NOT_EXIST = il nome non esiste
|
||||
|
||||
NAME_NOT_FOR_SALE = il nome non è in vendita
|
||||
|
||||
NAME_NOT_NORMALIZED = il nome non è in forma "normalizzata" Unicode
|
||||
|
||||
NEGATIVE_AMOUNT = importo non valido / negativo
|
||||
|
||||
NEGATIVE_FEE = tariffa non valida / negativa
|
||||
|
||||
NEGATIVE_PRICE = prezzo non valido / negativo
|
||||
|
||||
NOT_GROUP_ADMIN = l'account non è un amministratore di gruppo
|
||||
|
||||
NOT_GROUP_MEMBER = l'account non è un membro del gruppo
|
||||
|
||||
NOT_MINTING_ACCOUNT = l'account non può coniare
|
||||
|
||||
NOT_YET_RELEASED = funzione non ancora rilasciata
|
||||
|
||||
NO_BALANCE = equilibrio insufficiente
|
||||
|
||||
NO_BLOCKCHAIN_LOCK = nodo di blockchain attualmente occupato
|
||||
|
||||
NO_FLAG_PERMISSION = l'account non dispone di questa autorizzazione
|
||||
|
||||
OK = OK
|
||||
|
||||
ORDER_ALREADY_CLOSED = l'ordine di scambio di risorsa è già chiuso
|
||||
|
||||
ORDER_DOES_NOT_EXIST = l'ordine di scambio di risorsa non esiste
|
||||
|
||||
POLL_ALREADY_EXISTS = il sondaggio già esiste
|
||||
|
||||
POLL_DOES_NOT_EXIST = il sondaggio non esiste
|
||||
|
||||
POLL_OPTION_DOES_NOT_EXIST = le opzioni di sondaggio non esistono
|
||||
|
||||
PUBLIC_KEY_UNKNOWN = chiave pubblica sconosciuta
|
||||
|
||||
REWARD_SHARE_UNKNOWN = condivisione di ricompensa sconosciuta
|
||||
|
||||
SELF_SHARE_EXISTS = condivisione di sé (condivisione di ricompensa) già esiste
|
||||
|
||||
TIMESTAMP_TOO_NEW = timestamp troppo nuovo
|
||||
|
||||
TIMESTAMP_TOO_OLD = timestamp troppo vecchio
|
||||
|
||||
TOO_MANY_UNCONFIRMED = l'account ha troppe transazioni non confermate in sospeso
|
||||
|
||||
TRANSACTION_ALREADY_CONFIRMED = la transazione è già confermata
|
||||
|
||||
TRANSACTION_ALREADY_EXISTS = la transazione già esiste
|
||||
|
||||
TRANSACTION_UNKNOWN = transazione sconosciuta
|
||||
|
||||
TX_GROUP_ID_MISMATCH = identificazione di gruppo della transazione non corrisponde
|
@@ -336,4 +336,457 @@ public class RewardTests extends Common {
|
||||
}
|
||||
}
|
||||
|
||||
/** Test rewards for level 1 and 2 accounts both pre and post the shareBinFix, including orphaning back through the feature trigger block */
|
||||
@Test
|
||||
public void testLevel1And2Rewards() throws DataException {
|
||||
Common.useSettings("test-settings-v2-reward-levels.json");
|
||||
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
|
||||
List<PrivateKeyAccount> mintingAndOnlineAccounts = new ArrayList<>();
|
||||
|
||||
// Alice self share online
|
||||
PrivateKeyAccount aliceSelfShare = Common.getTestAccount(repository, "alice-reward-share");
|
||||
mintingAndOnlineAccounts.add(aliceSelfShare);
|
||||
|
||||
// Bob self-share NOT online
|
||||
|
||||
// Chloe self share online
|
||||
byte[] chloeRewardSharePrivateKey = AccountUtils.rewardShare(repository, "chloe", "chloe", 0);
|
||||
PrivateKeyAccount chloeRewardShareAccount = new PrivateKeyAccount(repository, chloeRewardSharePrivateKey);
|
||||
mintingAndOnlineAccounts.add(chloeRewardShareAccount);
|
||||
|
||||
// Dilbert self share online
|
||||
byte[] dilbertRewardSharePrivateKey = AccountUtils.rewardShare(repository, "dilbert", "dilbert", 0);
|
||||
PrivateKeyAccount dilbertRewardShareAccount = new PrivateKeyAccount(repository, dilbertRewardSharePrivateKey);
|
||||
mintingAndOnlineAccounts.add(dilbertRewardShareAccount);
|
||||
|
||||
// Mint a couple of blocks so that we are able to orphan them later
|
||||
for (int i=0; i<2; i++)
|
||||
BlockMinter.mintTestingBlock(repository, mintingAndOnlineAccounts.toArray(new PrivateKeyAccount[0]));
|
||||
|
||||
// Ensure that the levels are as we expect
|
||||
assertEquals(1, (int) Common.getTestAccount(repository, "alice").getLevel());
|
||||
assertEquals(1, (int) Common.getTestAccount(repository, "bob").getLevel());
|
||||
assertEquals(1, (int) Common.getTestAccount(repository, "chloe").getLevel());
|
||||
assertEquals(2, (int) Common.getTestAccount(repository, "dilbert").getLevel());
|
||||
|
||||
// Ensure that only Alice is a founder
|
||||
assertEquals(1, getFlags(repository, "alice"));
|
||||
assertEquals(0, getFlags(repository, "bob"));
|
||||
assertEquals(0, getFlags(repository, "chloe"));
|
||||
assertEquals(0, getFlags(repository, "dilbert"));
|
||||
|
||||
// Now that everyone is at level 1 or 2, we can capture initial balances
|
||||
Map<String, Map<Long, Long>> initialBalances = AccountUtils.getBalances(repository, Asset.QORT, Asset.LEGACY_QORA, Asset.QORT_FROM_QORA);
|
||||
final long aliceInitialBalance = initialBalances.get("alice").get(Asset.QORT);
|
||||
final long bobInitialBalance = initialBalances.get("bob").get(Asset.QORT);
|
||||
final long chloeInitialBalance = initialBalances.get("chloe").get(Asset.QORT);
|
||||
final long dilbertInitialBalance = initialBalances.get("dilbert").get(Asset.QORT);
|
||||
|
||||
// Mint a block
|
||||
final long blockReward = BlockUtils.getNextBlockReward(repository);
|
||||
BlockMinter.mintTestingBlock(repository, mintingAndOnlineAccounts.toArray(new PrivateKeyAccount[0]));
|
||||
|
||||
// Ensure we are at the correct height and block reward value
|
||||
assertEquals(6, (int) repository.getBlockRepository().getLastBlock().getHeight());
|
||||
assertEquals(10000000000L, blockReward);
|
||||
|
||||
/*
|
||||
* Alice, Chloe, and Dilbert are 'online'. Bob is offline.
|
||||
* Chloe is level 1, Dilbert is level 2.
|
||||
* One founder online (Alice, who is also level 1).
|
||||
* No legacy QORA holders.
|
||||
*
|
||||
* Chloe and Dilbert should receive equal shares of the 5% block reward for Level 1 and 2
|
||||
* Alice should receive the remainder (95%)
|
||||
*/
|
||||
|
||||
// We are after the shareBinFix feature trigger, so we expect level 1 and 2 to share the same reward (5%)
|
||||
final int level1And2SharePercent = 5_00; // 5%
|
||||
final long level1And2ShareAmount = (blockReward * level1And2SharePercent) / 100L / 100L;
|
||||
final long expectedReward = level1And2ShareAmount / 2; // The reward is split between Chloe and Dilbert
|
||||
final long expectedFounderReward = blockReward - level1And2ShareAmount; // Alice should receive the remainder
|
||||
|
||||
// Validate the balances to ensure that the correct post-shareBinFix distribution is being applied
|
||||
assertEquals(500000000, level1And2ShareAmount);
|
||||
AccountUtils.assertBalance(repository, "alice", Asset.QORT, aliceInitialBalance+expectedFounderReward);
|
||||
AccountUtils.assertBalance(repository, "bob", Asset.QORT, bobInitialBalance); // Bob not online so his balance remains the same
|
||||
AccountUtils.assertBalance(repository, "chloe", Asset.QORT, chloeInitialBalance+expectedReward);
|
||||
AccountUtils.assertBalance(repository, "dilbert", Asset.QORT, dilbertInitialBalance+expectedReward);
|
||||
|
||||
// Now orphan the latest block. This brings us to the threshold of the shareBinFix feature trigger.
|
||||
BlockUtils.orphanBlocks(repository, 1);
|
||||
assertEquals(5, (int) repository.getBlockRepository().getLastBlock().getHeight());
|
||||
|
||||
// Ensure the latest post-fix block rewards have been subtracted and they have returned to their initial values
|
||||
AccountUtils.assertBalance(repository, "alice", Asset.QORT, aliceInitialBalance);
|
||||
AccountUtils.assertBalance(repository, "bob", Asset.QORT, bobInitialBalance); // Bob not online so his balance remains the same
|
||||
AccountUtils.assertBalance(repository, "chloe", Asset.QORT, chloeInitialBalance);
|
||||
AccountUtils.assertBalance(repository, "dilbert", Asset.QORT, dilbertInitialBalance);
|
||||
|
||||
// Orphan another block. This time, the block that was orphaned was prior to the shareBinFix feature trigger.
|
||||
BlockUtils.orphanBlocks(repository, 1);
|
||||
assertEquals(4, (int) repository.getBlockRepository().getLastBlock().getHeight());
|
||||
|
||||
// Prior to the fix, the levels were incorrectly grouped
|
||||
// Chloe should receive 100% of the level 1 reward, and Dilbert should receive 100% of the level 2+3 reward
|
||||
final int level1SharePercent = 5_00; // 5%
|
||||
final int level2And3SharePercent = 10_00; // 10%
|
||||
final long level1ShareAmountBeforeFix = (blockReward * level1SharePercent) / 100L / 100L;
|
||||
final long level2And3ShareAmountBeforeFix = (blockReward * level2And3SharePercent) / 100L / 100L;
|
||||
final long expectedFounderRewardBeforeFix = blockReward - level1ShareAmountBeforeFix - level2And3ShareAmountBeforeFix; // Alice should receive the remainder
|
||||
|
||||
// Validate the share amounts and balances
|
||||
assertEquals(500000000, level1ShareAmountBeforeFix);
|
||||
assertEquals(1000000000, level2And3ShareAmountBeforeFix);
|
||||
AccountUtils.assertBalance(repository, "alice", Asset.QORT, aliceInitialBalance-expectedFounderRewardBeforeFix);
|
||||
AccountUtils.assertBalance(repository, "bob", Asset.QORT, bobInitialBalance); // Bob not online so his balance remains the same
|
||||
AccountUtils.assertBalance(repository, "chloe", Asset.QORT, chloeInitialBalance-level1ShareAmountBeforeFix);
|
||||
AccountUtils.assertBalance(repository, "dilbert", Asset.QORT, dilbertInitialBalance-level2And3ShareAmountBeforeFix);
|
||||
|
||||
// Orphan the latest block one last time
|
||||
BlockUtils.orphanBlocks(repository, 1);
|
||||
assertEquals(3, (int) repository.getBlockRepository().getLastBlock().getHeight());
|
||||
|
||||
// Validate balances
|
||||
AccountUtils.assertBalance(repository, "alice", Asset.QORT, aliceInitialBalance-(expectedFounderRewardBeforeFix*2));
|
||||
AccountUtils.assertBalance(repository, "bob", Asset.QORT, bobInitialBalance); // Bob not online so his balance remains the same
|
||||
AccountUtils.assertBalance(repository, "chloe", Asset.QORT, chloeInitialBalance-(level1ShareAmountBeforeFix*2));
|
||||
AccountUtils.assertBalance(repository, "dilbert", Asset.QORT, dilbertInitialBalance-(level2And3ShareAmountBeforeFix*2));
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/** Test rewards for level 3 and 4 accounts */
|
||||
@Test
|
||||
public void testLevel3And4Rewards() throws DataException {
|
||||
Common.useSettings("test-settings-v2-reward-levels.json");
|
||||
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
|
||||
List<Integer> cumulativeBlocksByLevel = BlockChain.getInstance().getCumulativeBlocksByLevel();
|
||||
List<PrivateKeyAccount> mintingAndOnlineAccounts = new ArrayList<>();
|
||||
|
||||
// Alice self share online
|
||||
PrivateKeyAccount aliceSelfShare = Common.getTestAccount(repository, "alice-reward-share");
|
||||
mintingAndOnlineAccounts.add(aliceSelfShare);
|
||||
|
||||
// Bob self-share online
|
||||
byte[] bobRewardSharePrivateKey = AccountUtils.rewardShare(repository, "bob", "bob", 0);
|
||||
PrivateKeyAccount bobRewardShareAccount = new PrivateKeyAccount(repository, bobRewardSharePrivateKey);
|
||||
mintingAndOnlineAccounts.add(bobRewardShareAccount);
|
||||
|
||||
// Chloe self share online
|
||||
byte[] chloeRewardSharePrivateKey = AccountUtils.rewardShare(repository, "chloe", "chloe", 0);
|
||||
PrivateKeyAccount chloeRewardShareAccount = new PrivateKeyAccount(repository, chloeRewardSharePrivateKey);
|
||||
mintingAndOnlineAccounts.add(chloeRewardShareAccount);
|
||||
|
||||
// Dilbert self share online
|
||||
byte[] dilbertRewardSharePrivateKey = AccountUtils.rewardShare(repository, "dilbert", "dilbert", 0);
|
||||
PrivateKeyAccount dilbertRewardShareAccount = new PrivateKeyAccount(repository, dilbertRewardSharePrivateKey);
|
||||
mintingAndOnlineAccounts.add(dilbertRewardShareAccount);
|
||||
|
||||
// Mint enough blocks to bump testAccount levels to 3 and 4
|
||||
final int minterBlocksNeeded = cumulativeBlocksByLevel.get(4) - 20; // 20 blocks before level 4, so that the test accounts reach the correct levels
|
||||
for (int bc = 0; bc < minterBlocksNeeded; ++bc)
|
||||
BlockMinter.mintTestingBlock(repository, mintingAndOnlineAccounts.toArray(new PrivateKeyAccount[0]));
|
||||
|
||||
// Ensure that the levels are as we expect
|
||||
assertEquals(3, (int) Common.getTestAccount(repository, "alice").getLevel());
|
||||
assertEquals(3, (int) Common.getTestAccount(repository, "bob").getLevel());
|
||||
assertEquals(3, (int) Common.getTestAccount(repository, "chloe").getLevel());
|
||||
assertEquals(4, (int) Common.getTestAccount(repository, "dilbert").getLevel());
|
||||
|
||||
// Now that everyone is at level 3 or 4, we can capture initial balances
|
||||
Map<String, Map<Long, Long>> initialBalances = AccountUtils.getBalances(repository, Asset.QORT, Asset.LEGACY_QORA, Asset.QORT_FROM_QORA);
|
||||
final long aliceInitialBalance = initialBalances.get("alice").get(Asset.QORT);
|
||||
final long bobInitialBalance = initialBalances.get("bob").get(Asset.QORT);
|
||||
final long chloeInitialBalance = initialBalances.get("chloe").get(Asset.QORT);
|
||||
final long dilbertInitialBalance = initialBalances.get("dilbert").get(Asset.QORT);
|
||||
|
||||
// Mint a block
|
||||
final long blockReward = BlockUtils.getNextBlockReward(repository);
|
||||
BlockMinter.mintTestingBlock(repository, mintingAndOnlineAccounts.toArray(new PrivateKeyAccount[0]));
|
||||
|
||||
// Ensure we are using the correct block reward value
|
||||
assertEquals(100000000L, blockReward);
|
||||
|
||||
/*
|
||||
* Alice, Bob, Chloe, and Dilbert are 'online'.
|
||||
* Bob and Chloe are level 3; Dilbert is level 4.
|
||||
* One founder online (Alice, who is also level 3).
|
||||
* No legacy QORA holders.
|
||||
*
|
||||
* Chloe, Bob and Dilbert should receive equal shares of the 10% block reward for level 3 and 4
|
||||
* Alice should receive the remainder (90%)
|
||||
*/
|
||||
|
||||
// We are after the shareBinFix feature trigger, so we expect level 3 and 4 to share the same reward (10%)
|
||||
final int level3And4SharePercent = 10_00; // 10%
|
||||
final long level3And4ShareAmount = (blockReward * level3And4SharePercent) / 100L / 100L;
|
||||
final long expectedReward = level3And4ShareAmount / 3; // The reward is split between Bob, Chloe, and Dilbert
|
||||
final long expectedFounderReward = blockReward - level3And4ShareAmount; // Alice should receive the remainder
|
||||
|
||||
// Validate the balances to ensure that the correct post-shareBinFix distribution is being applied
|
||||
AccountUtils.assertBalance(repository, "alice", Asset.QORT, aliceInitialBalance+expectedFounderReward);
|
||||
AccountUtils.assertBalance(repository, "bob", Asset.QORT, bobInitialBalance+expectedReward);
|
||||
AccountUtils.assertBalance(repository, "chloe", Asset.QORT, chloeInitialBalance+expectedReward);
|
||||
AccountUtils.assertBalance(repository, "dilbert", Asset.QORT, dilbertInitialBalance+expectedReward);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/** Test rewards for level 5 and 6 accounts */
|
||||
@Test
|
||||
public void testLevel5And6Rewards() throws DataException {
|
||||
Common.useSettings("test-settings-v2-reward-levels.json");
|
||||
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
|
||||
List<Integer> cumulativeBlocksByLevel = BlockChain.getInstance().getCumulativeBlocksByLevel();
|
||||
List<PrivateKeyAccount> mintingAndOnlineAccounts = new ArrayList<>();
|
||||
|
||||
// Alice self share online
|
||||
PrivateKeyAccount aliceSelfShare = Common.getTestAccount(repository, "alice-reward-share");
|
||||
mintingAndOnlineAccounts.add(aliceSelfShare);
|
||||
|
||||
// Bob self-share not initially online
|
||||
|
||||
// Chloe self share online
|
||||
byte[] chloeRewardSharePrivateKey = AccountUtils.rewardShare(repository, "chloe", "chloe", 0);
|
||||
PrivateKeyAccount chloeRewardShareAccount = new PrivateKeyAccount(repository, chloeRewardSharePrivateKey);
|
||||
mintingAndOnlineAccounts.add(chloeRewardShareAccount);
|
||||
|
||||
// Dilbert self share online
|
||||
byte[] dilbertRewardSharePrivateKey = AccountUtils.rewardShare(repository, "dilbert", "dilbert", 0);
|
||||
PrivateKeyAccount dilbertRewardShareAccount = new PrivateKeyAccount(repository, dilbertRewardSharePrivateKey);
|
||||
mintingAndOnlineAccounts.add(dilbertRewardShareAccount);
|
||||
|
||||
// Mint enough blocks to bump testAccount levels to 5 and 6
|
||||
final int minterBlocksNeeded = cumulativeBlocksByLevel.get(6) - 20; // 20 blocks before level 6, so that the test accounts reach the correct levels
|
||||
for (int bc = 0; bc < minterBlocksNeeded; ++bc)
|
||||
BlockMinter.mintTestingBlock(repository, mintingAndOnlineAccounts.toArray(new PrivateKeyAccount[0]));
|
||||
|
||||
// Bob self-share now comes online
|
||||
byte[] bobRewardSharePrivateKey = AccountUtils.rewardShare(repository, "bob", "bob", 0);
|
||||
PrivateKeyAccount bobRewardShareAccount = new PrivateKeyAccount(repository, bobRewardSharePrivateKey);
|
||||
mintingAndOnlineAccounts.add(bobRewardShareAccount);
|
||||
|
||||
// Ensure that the levels are as we expect
|
||||
assertEquals(5, (int) Common.getTestAccount(repository, "alice").getLevel());
|
||||
assertEquals(1, (int) Common.getTestAccount(repository, "bob").getLevel());
|
||||
assertEquals(5, (int) Common.getTestAccount(repository, "chloe").getLevel());
|
||||
assertEquals(6, (int) Common.getTestAccount(repository, "dilbert").getLevel());
|
||||
|
||||
// Now that everyone is at level 5 or 6 (except Bob who has only just started minting, so is at level 1), we can capture initial balances
|
||||
Map<String, Map<Long, Long>> initialBalances = AccountUtils.getBalances(repository, Asset.QORT, Asset.LEGACY_QORA, Asset.QORT_FROM_QORA);
|
||||
final long aliceInitialBalance = initialBalances.get("alice").get(Asset.QORT);
|
||||
final long bobInitialBalance = initialBalances.get("bob").get(Asset.QORT);
|
||||
final long chloeInitialBalance = initialBalances.get("chloe").get(Asset.QORT);
|
||||
final long dilbertInitialBalance = initialBalances.get("dilbert").get(Asset.QORT);
|
||||
|
||||
// Mint a block
|
||||
final long blockReward = BlockUtils.getNextBlockReward(repository);
|
||||
BlockMinter.mintTestingBlock(repository, mintingAndOnlineAccounts.toArray(new PrivateKeyAccount[0]));
|
||||
|
||||
// Ensure we are using the correct block reward value
|
||||
assertEquals(100000000L, blockReward);
|
||||
|
||||
/*
|
||||
* Alice, Bob, Chloe, and Dilbert are 'online'.
|
||||
* Bob is level 1; Chloe is level 5; Dilbert is level 6.
|
||||
* One founder online (Alice, who is also level 5).
|
||||
* No legacy QORA holders.
|
||||
*
|
||||
* Chloe and Dilbert should receive equal shares of the 15% block reward for level 5 and 6
|
||||
* Bob should receive all of the level 1 and 2 reward (5%)
|
||||
* Alice should receive the remainder (80%)
|
||||
*/
|
||||
|
||||
// We are after the shareBinFix feature trigger, so we expect level 5 and 6 to share the same reward (15%)
|
||||
final int level1And2SharePercent = 5_00; // 5%
|
||||
final int level5And6SharePercent = 15_00; // 10%
|
||||
final long level1And2ShareAmount = (blockReward * level1And2SharePercent) / 100L / 100L;
|
||||
final long level5And6ShareAmount = (blockReward * level5And6SharePercent) / 100L / 100L;
|
||||
final long expectedLevel1And2Reward = level1And2ShareAmount; // The reward is given entirely to Bob
|
||||
final long expectedLevel5And6Reward = level5And6ShareAmount / 2; // The reward is split between Chloe and Dilbert
|
||||
final long expectedFounderReward = blockReward - level1And2ShareAmount - level5And6ShareAmount; // Alice should receive the remainder
|
||||
|
||||
// Validate the balances to ensure that the correct post-shareBinFix distribution is being applied
|
||||
AccountUtils.assertBalance(repository, "alice", Asset.QORT, aliceInitialBalance+expectedFounderReward);
|
||||
AccountUtils.assertBalance(repository, "bob", Asset.QORT, bobInitialBalance+expectedLevel1And2Reward);
|
||||
AccountUtils.assertBalance(repository, "chloe", Asset.QORT, chloeInitialBalance+expectedLevel5And6Reward);
|
||||
AccountUtils.assertBalance(repository, "dilbert", Asset.QORT, dilbertInitialBalance+expectedLevel5And6Reward);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/** Test rewards for level 7 and 8 accounts */
|
||||
@Test
|
||||
public void testLevel7And8Rewards() throws DataException {
|
||||
Common.useSettings("test-settings-v2-reward-levels.json");
|
||||
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
|
||||
List<Integer> cumulativeBlocksByLevel = BlockChain.getInstance().getCumulativeBlocksByLevel();
|
||||
List<PrivateKeyAccount> mintingAndOnlineAccounts = new ArrayList<>();
|
||||
|
||||
// Alice self share online
|
||||
PrivateKeyAccount aliceSelfShare = Common.getTestAccount(repository, "alice-reward-share");
|
||||
mintingAndOnlineAccounts.add(aliceSelfShare);
|
||||
|
||||
// Bob self-share NOT online
|
||||
|
||||
// Chloe self share online
|
||||
byte[] chloeRewardSharePrivateKey = AccountUtils.rewardShare(repository, "chloe", "chloe", 0);
|
||||
PrivateKeyAccount chloeRewardShareAccount = new PrivateKeyAccount(repository, chloeRewardSharePrivateKey);
|
||||
mintingAndOnlineAccounts.add(chloeRewardShareAccount);
|
||||
|
||||
// Dilbert self share online
|
||||
byte[] dilbertRewardSharePrivateKey = AccountUtils.rewardShare(repository, "dilbert", "dilbert", 0);
|
||||
PrivateKeyAccount dilbertRewardShareAccount = new PrivateKeyAccount(repository, dilbertRewardSharePrivateKey);
|
||||
mintingAndOnlineAccounts.add(dilbertRewardShareAccount);
|
||||
|
||||
// Mint enough blocks to bump testAccount levels to 7 and 8
|
||||
final int minterBlocksNeeded = cumulativeBlocksByLevel.get(8) - 20; // 20 blocks before level 8, so that the test accounts reach the correct levels
|
||||
for (int bc = 0; bc < minterBlocksNeeded; ++bc)
|
||||
BlockMinter.mintTestingBlock(repository, mintingAndOnlineAccounts.toArray(new PrivateKeyAccount[0]));
|
||||
|
||||
// Ensure that the levels are as we expect
|
||||
assertEquals(7, (int) Common.getTestAccount(repository, "alice").getLevel());
|
||||
assertEquals(1, (int) Common.getTestAccount(repository, "bob").getLevel());
|
||||
assertEquals(7, (int) Common.getTestAccount(repository, "chloe").getLevel());
|
||||
assertEquals(8, (int) Common.getTestAccount(repository, "dilbert").getLevel());
|
||||
|
||||
// Now that everyone is at level 7 or 8 (except Bob who has only just started minting, so is at level 1), we can capture initial balances
|
||||
Map<String, Map<Long, Long>> initialBalances = AccountUtils.getBalances(repository, Asset.QORT, Asset.LEGACY_QORA, Asset.QORT_FROM_QORA);
|
||||
final long aliceInitialBalance = initialBalances.get("alice").get(Asset.QORT);
|
||||
final long bobInitialBalance = initialBalances.get("bob").get(Asset.QORT);
|
||||
final long chloeInitialBalance = initialBalances.get("chloe").get(Asset.QORT);
|
||||
final long dilbertInitialBalance = initialBalances.get("dilbert").get(Asset.QORT);
|
||||
|
||||
// Mint a block
|
||||
final long blockReward = BlockUtils.getNextBlockReward(repository);
|
||||
BlockMinter.mintTestingBlock(repository, mintingAndOnlineAccounts.toArray(new PrivateKeyAccount[0]));
|
||||
|
||||
// Ensure we are using the correct block reward value
|
||||
assertEquals(100000000L, blockReward);
|
||||
|
||||
/*
|
||||
* Alice, Chloe, and Dilbert are 'online'.
|
||||
* Chloe is level 7; Dilbert is level 8.
|
||||
* One founder online (Alice, who is also level 7).
|
||||
* No legacy QORA holders.
|
||||
*
|
||||
* Chloe and Dilbert should receive equal shares of the 20% block reward for level 7 and 8
|
||||
* Alice should receive the remainder (80%)
|
||||
*/
|
||||
|
||||
// We are after the shareBinFix feature trigger, so we expect level 7 and 8 to share the same reward (20%)
|
||||
final int level7And8SharePercent = 20_00; // 20%
|
||||
final long level7And8ShareAmount = (blockReward * level7And8SharePercent) / 100L / 100L;
|
||||
final long expectedLevel7And8Reward = level7And8ShareAmount / 2; // The reward is split between Chloe and Dilbert
|
||||
final long expectedFounderReward = blockReward - level7And8ShareAmount; // Alice should receive the remainder
|
||||
|
||||
// Validate the balances to ensure that the correct post-shareBinFix distribution is being applied
|
||||
AccountUtils.assertBalance(repository, "alice", Asset.QORT, aliceInitialBalance+expectedFounderReward);
|
||||
AccountUtils.assertBalance(repository, "bob", Asset.QORT, bobInitialBalance); // Bob not online so his balance remains the same
|
||||
AccountUtils.assertBalance(repository, "chloe", Asset.QORT, chloeInitialBalance+expectedLevel7And8Reward);
|
||||
AccountUtils.assertBalance(repository, "dilbert", Asset.QORT, dilbertInitialBalance+expectedLevel7And8Reward);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/** Test rewards for level 9 and 10 accounts */
|
||||
@Test
|
||||
public void testLevel9And10Rewards() throws DataException {
|
||||
Common.useSettings("test-settings-v2-reward-levels.json");
|
||||
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
|
||||
List<Integer> cumulativeBlocksByLevel = BlockChain.getInstance().getCumulativeBlocksByLevel();
|
||||
List<PrivateKeyAccount> mintingAndOnlineAccounts = new ArrayList<>();
|
||||
|
||||
// Alice self share online
|
||||
PrivateKeyAccount aliceSelfShare = Common.getTestAccount(repository, "alice-reward-share");
|
||||
mintingAndOnlineAccounts.add(aliceSelfShare);
|
||||
|
||||
// Bob self-share not initially online
|
||||
|
||||
// Chloe self share online
|
||||
byte[] chloeRewardSharePrivateKey = AccountUtils.rewardShare(repository, "chloe", "chloe", 0);
|
||||
PrivateKeyAccount chloeRewardShareAccount = new PrivateKeyAccount(repository, chloeRewardSharePrivateKey);
|
||||
mintingAndOnlineAccounts.add(chloeRewardShareAccount);
|
||||
|
||||
// Dilbert self share online
|
||||
byte[] dilbertRewardSharePrivateKey = AccountUtils.rewardShare(repository, "dilbert", "dilbert", 0);
|
||||
PrivateKeyAccount dilbertRewardShareAccount = new PrivateKeyAccount(repository, dilbertRewardSharePrivateKey);
|
||||
mintingAndOnlineAccounts.add(dilbertRewardShareAccount);
|
||||
|
||||
// Mint enough blocks to bump testAccount levels to 9 and 10
|
||||
final int minterBlocksNeeded = cumulativeBlocksByLevel.get(10) - 20; // 20 blocks before level 10, so that the test accounts reach the correct levels
|
||||
for (int bc = 0; bc < minterBlocksNeeded; ++bc)
|
||||
BlockMinter.mintTestingBlock(repository, mintingAndOnlineAccounts.toArray(new PrivateKeyAccount[0]));
|
||||
|
||||
// Bob self-share now comes online
|
||||
byte[] bobRewardSharePrivateKey = AccountUtils.rewardShare(repository, "bob", "bob", 0);
|
||||
PrivateKeyAccount bobRewardShareAccount = new PrivateKeyAccount(repository, bobRewardSharePrivateKey);
|
||||
mintingAndOnlineAccounts.add(bobRewardShareAccount);
|
||||
|
||||
// Ensure that the levels are as we expect
|
||||
assertEquals(9, (int) Common.getTestAccount(repository, "alice").getLevel());
|
||||
assertEquals(1, (int) Common.getTestAccount(repository, "bob").getLevel());
|
||||
assertEquals(9, (int) Common.getTestAccount(repository, "chloe").getLevel());
|
||||
assertEquals(10, (int) Common.getTestAccount(repository, "dilbert").getLevel());
|
||||
|
||||
// Now that everyone is at level 7 or 8 (except Bob who has only just started minting, so is at level 1), we can capture initial balances
|
||||
Map<String, Map<Long, Long>> initialBalances = AccountUtils.getBalances(repository, Asset.QORT, Asset.LEGACY_QORA, Asset.QORT_FROM_QORA);
|
||||
final long aliceInitialBalance = initialBalances.get("alice").get(Asset.QORT);
|
||||
final long bobInitialBalance = initialBalances.get("bob").get(Asset.QORT);
|
||||
final long chloeInitialBalance = initialBalances.get("chloe").get(Asset.QORT);
|
||||
final long dilbertInitialBalance = initialBalances.get("dilbert").get(Asset.QORT);
|
||||
|
||||
// Mint a block
|
||||
final long blockReward = BlockUtils.getNextBlockReward(repository);
|
||||
BlockMinter.mintTestingBlock(repository, mintingAndOnlineAccounts.toArray(new PrivateKeyAccount[0]));
|
||||
|
||||
// Ensure we are using the correct block reward value
|
||||
assertEquals(100000000L, blockReward);
|
||||
|
||||
/*
|
||||
* Alice, Bob, Chloe, and Dilbert are 'online'.
|
||||
* Bob is level 1; Chloe is level 9; Dilbert is level 10.
|
||||
* One founder online (Alice, who is also level 9).
|
||||
* No legacy QORA holders.
|
||||
*
|
||||
* Chloe and Dilbert should receive equal shares of the 25% block reward for level 9 and 10
|
||||
* Bob should receive all of the level 1 and 2 reward (5%)
|
||||
* Alice should receive the remainder (70%)
|
||||
*/
|
||||
|
||||
// We are after the shareBinFix feature trigger, so we expect level 9 and 10 to share the same reward (25%)
|
||||
final int level1And2SharePercent = 5_00; // 5%
|
||||
final int level9And10SharePercent = 25_00; // 25%
|
||||
final long level1And2ShareAmount = (blockReward * level1And2SharePercent) / 100L / 100L;
|
||||
final long level9And10ShareAmount = (blockReward * level9And10SharePercent) / 100L / 100L;
|
||||
final long expectedLevel1And2Reward = level1And2ShareAmount; // The reward is given entirely to Bob
|
||||
final long expectedLevel9And10Reward = level9And10ShareAmount / 2; // The reward is split between Chloe and Dilbert
|
||||
final long expectedFounderReward = blockReward - level1And2ShareAmount - level9And10ShareAmount; // Alice should receive the remainder
|
||||
|
||||
// Validate the balances to ensure that the correct post-shareBinFix distribution is being applied
|
||||
AccountUtils.assertBalance(repository, "alice", Asset.QORT, aliceInitialBalance+expectedFounderReward);
|
||||
AccountUtils.assertBalance(repository, "bob", Asset.QORT, bobInitialBalance+expectedLevel1And2Reward);
|
||||
AccountUtils.assertBalance(repository, "chloe", Asset.QORT, chloeInitialBalance+expectedLevel9And10Reward);
|
||||
AccountUtils.assertBalance(repository, "dilbert", Asset.QORT, dilbertInitialBalance+expectedLevel9And10Reward);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private int getFlags(Repository repository, String name) throws DataException {
|
||||
TestAccount testAccount = Common.getTestAccount(repository, name);
|
||||
return repository.getAccountRepository().getAccount(testAccount.getAddress()).getFlags();
|
||||
}
|
||||
|
||||
}
|
@@ -44,7 +44,10 @@
|
||||
"powfixTimestamp": 0,
|
||||
"qortalTimestamp": 0,
|
||||
"newAssetPricingTimestamp": 0,
|
||||
"groupApprovalTimestamp": 0
|
||||
"groupApprovalTimestamp": 0,
|
||||
"atFindNextTransactionFix": 0,
|
||||
"newBlockSigHeight": 999999,
|
||||
"shareBinFix": 999999
|
||||
},
|
||||
"genesisInfo": {
|
||||
"version": 4,
|
||||
|
@@ -44,7 +44,10 @@
|
||||
"powfixTimestamp": 0,
|
||||
"qortalTimestamp": 0,
|
||||
"newAssetPricingTimestamp": 0,
|
||||
"groupApprovalTimestamp": 0
|
||||
"groupApprovalTimestamp": 0,
|
||||
"atFindNextTransactionFix": 0,
|
||||
"newBlockSigHeight": 999999,
|
||||
"shareBinFix": 999999
|
||||
},
|
||||
"genesisInfo": {
|
||||
"version": 4,
|
||||
|
@@ -44,7 +44,10 @@
|
||||
"powfixTimestamp": 0,
|
||||
"qortalTimestamp": 0,
|
||||
"newAssetPricingTimestamp": 0,
|
||||
"groupApprovalTimestamp": 0
|
||||
"groupApprovalTimestamp": 0,
|
||||
"atFindNextTransactionFix": 0,
|
||||
"newBlockSigHeight": 999999,
|
||||
"shareBinFix": 999999
|
||||
},
|
||||
"genesisInfo": {
|
||||
"version": 4,
|
||||
|
@@ -44,7 +44,10 @@
|
||||
"powfixTimestamp": 0,
|
||||
"qortalTimestamp": 0,
|
||||
"newAssetPricingTimestamp": 0,
|
||||
"groupApprovalTimestamp": 0
|
||||
"groupApprovalTimestamp": 0,
|
||||
"atFindNextTransactionFix": 0,
|
||||
"newBlockSigHeight": 999999,
|
||||
"shareBinFix": 999999
|
||||
},
|
||||
"genesisInfo": {
|
||||
"version": 4,
|
||||
|
@@ -44,7 +44,10 @@
|
||||
"powfixTimestamp": 0,
|
||||
"qortalTimestamp": 0,
|
||||
"newAssetPricingTimestamp": 0,
|
||||
"groupApprovalTimestamp": 0
|
||||
"groupApprovalTimestamp": 0,
|
||||
"atFindNextTransactionFix": 0,
|
||||
"newBlockSigHeight": 999999,
|
||||
"shareBinFix": 999999
|
||||
},
|
||||
"genesisInfo": {
|
||||
"version": 4,
|
||||
|
74
src/test/resources/test-chain-v2-reward-levels.json
Normal file
74
src/test/resources/test-chain-v2-reward-levels.json
Normal file
@@ -0,0 +1,74 @@
|
||||
{
|
||||
"isTestChain": true,
|
||||
"blockTimestampMargin": 500,
|
||||
"transactionExpiryPeriod": 86400000,
|
||||
"maxBlockSize": 2097152,
|
||||
"maxBytesPerUnitFee": 1024,
|
||||
"unitFee": "0.1",
|
||||
"requireGroupForApproval": false,
|
||||
"minAccountLevelToRewardShare": 5,
|
||||
"maxRewardSharesPerMintingAccount": 20,
|
||||
"founderEffectiveMintingLevel": 10,
|
||||
"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,
|
||||
"qoraPerQortReward": 250,
|
||||
"blocksNeededByLevel": [ 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 ],
|
||||
"blockTimingsByHeight": [
|
||||
{ "height": 1, "target": 60000, "deviation": 30000, "power": 0.2 }
|
||||
],
|
||||
"ciyamAtSettings": {
|
||||
"feePerStep": "0.0001",
|
||||
"maxStepsPerRound": 500,
|
||||
"stepsPerFunctionCall": 10,
|
||||
"minutesPerBlock": 1
|
||||
},
|
||||
"featureTriggers": {
|
||||
"messageHeight": 0,
|
||||
"atHeight": 0,
|
||||
"assetsTimestamp": 0,
|
||||
"votingTimestamp": 0,
|
||||
"arbitraryTimestamp": 0,
|
||||
"powfixTimestamp": 0,
|
||||
"qortalTimestamp": 0,
|
||||
"newAssetPricingTimestamp": 0,
|
||||
"groupApprovalTimestamp": 0,
|
||||
"atFindNextTransactionFix": 0,
|
||||
"newBlockSigHeight": 999999,
|
||||
"shareBinFix": 6
|
||||
},
|
||||
"genesisInfo": {
|
||||
"version": 4,
|
||||
"timestamp": 0,
|
||||
"transactions": [
|
||||
{ "type": "ISSUE_ASSET", "assetName": "QORT", "description": "QORT native coin", "data": "", "quantity": 0, "isDivisible": true, "fee": 0 },
|
||||
{ "type": "ISSUE_ASSET", "assetName": "Legacy-QORA", "description": "Representative legacy QORA", "quantity": 0, "isDivisible": true, "data": "{}", "isUnspendable": true },
|
||||
{ "type": "ISSUE_ASSET", "assetName": "QORT-from-QORA", "description": "QORT gained from holding legacy QORA", "quantity": 0, "isDivisible": true, "data": "{}", "isUnspendable": true },
|
||||
|
||||
{ "type": "GENESIS", "recipient": "QgV4s3xnzLhVBEJxcYui4u4q11yhUHsd9v", "amount": "1000000000" },
|
||||
{ "type": "GENESIS", "recipient": "QixPbJUwsaHsVEofJdozU9zgVqkK6aYhrK", "amount": "1000000" },
|
||||
{ "type": "GENESIS", "recipient": "QaUpHNhT3Ygx6avRiKobuLdusppR5biXjL", "amount": "1000000" },
|
||||
{ "type": "GENESIS", "recipient": "Qci5m9k4rcwe4ruKrZZQKka4FzUUMut3er", "amount": "1000000" },
|
||||
|
||||
{ "type": "ACCOUNT_FLAGS", "target": "QgV4s3xnzLhVBEJxcYui4u4q11yhUHsd9v", "andMask": -1, "orMask": 1, "xorMask": 0 },
|
||||
{ "type": "REWARD_SHARE", "minterPublicKey": "2tiMr5LTpaWCgbRvkPK8TFd7k63DyHJMMFFsz9uBf1ZP", "recipient": "QgV4s3xnzLhVBEJxcYui4u4q11yhUHsd9v", "rewardSharePublicKey": "7PpfnvLSG7y4HPh8hE7KoqAjLCkv7Ui6xw4mKAkbZtox", "sharePercent": 100 },
|
||||
|
||||
{ "type": "ACCOUNT_LEVEL", "target": "QgV4s3xnzLhVBEJxcYui4u4q11yhUHsd9v", "level": 1 },
|
||||
{ "type": "ACCOUNT_LEVEL", "target": "QixPbJUwsaHsVEofJdozU9zgVqkK6aYhrK", "level": 1 },
|
||||
{ "type": "ACCOUNT_LEVEL", "target": "QaUpHNhT3Ygx6avRiKobuLdusppR5biXjL", "level": 1 },
|
||||
{ "type": "ACCOUNT_LEVEL", "target": "Qci5m9k4rcwe4ruKrZZQKka4FzUUMut3er", "level": 2 }
|
||||
]
|
||||
}
|
||||
}
|
@@ -44,7 +44,10 @@
|
||||
"powfixTimestamp": 0,
|
||||
"qortalTimestamp": 0,
|
||||
"newAssetPricingTimestamp": 0,
|
||||
"groupApprovalTimestamp": 0
|
||||
"groupApprovalTimestamp": 0,
|
||||
"atFindNextTransactionFix": 0,
|
||||
"newBlockSigHeight": 999999,
|
||||
"shareBinFix": 999999
|
||||
},
|
||||
"genesisInfo": {
|
||||
"version": 4,
|
||||
|
@@ -44,7 +44,10 @@
|
||||
"powfixTimestamp": 0,
|
||||
"qortalTimestamp": 0,
|
||||
"newAssetPricingTimestamp": 0,
|
||||
"groupApprovalTimestamp": 0
|
||||
"groupApprovalTimestamp": 0,
|
||||
"atFindNextTransactionFix": 0,
|
||||
"newBlockSigHeight": 999999,
|
||||
"shareBinFix": 999999
|
||||
},
|
||||
"genesisInfo": {
|
||||
"version": 4,
|
||||
|
7
src/test/resources/test-settings-v2-reward-levels.json
Normal file
7
src/test/resources/test-settings-v2-reward-levels.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"restrictedApi": false,
|
||||
"blockchainConfig": "src/test/resources/test-chain-v2-reward-levels.json",
|
||||
"wipeUnconfirmedOnStart": false,
|
||||
"testNtpOffset": 0,
|
||||
"minPeers": 0
|
||||
}
|
45
stop.sh
45
stop.sh
@@ -21,21 +21,38 @@ fi
|
||||
read pid 2>/dev/null <run.pid
|
||||
is_pid_valid=$?
|
||||
|
||||
echo 'Calling GET /admin/stop on local Qortal node'
|
||||
if curl --url http://localhost:12391/admin/stop 1>/dev/null 2>&1; then
|
||||
echo "Qortal node responded and should be shutting down"
|
||||
if [ "${is_pid_valid}" -eq 0 ]; then
|
||||
echo -n "Monitoring for Qortal node to end"
|
||||
while s=`ps -p $pid -o stat=` && [[ "$s" && "$s" != 'Z' ]]; do
|
||||
echo -n .
|
||||
sleep 1
|
||||
done
|
||||
echo
|
||||
echo "${green}Qortal ended gracefully${normal}"
|
||||
rm -f run.pid
|
||||
# Swap out the API port if the --testnet (or -t) argument is specified
|
||||
api_port=12391
|
||||
if [[ "$@" = *"--testnet"* ]] || [[ "$@" = *"-t"* ]]; then
|
||||
api_port=62391
|
||||
fi
|
||||
|
||||
# Ensure curl is installed
|
||||
curl_path=$(which curl)
|
||||
|
||||
if [[ -f $curl_path ]]; then
|
||||
|
||||
echo 'Calling GET /admin/stop on local Qortal node'
|
||||
if curl --url "http://localhost:${api_port}/admin/stop" 1>/dev/null 2>&1; then
|
||||
echo "Qortal node responded and should be shutting down"
|
||||
|
||||
if [ "${is_pid_valid}" -eq 0 ]; then
|
||||
echo -n "Monitoring for Qortal node to end"
|
||||
while s=`ps -p $pid -o stat=` && [[ "$s" && "$s" != 'Z' ]]; do
|
||||
echo -n .
|
||||
sleep 1
|
||||
done
|
||||
echo
|
||||
echo "${green}Qortal ended gracefully${normal}"
|
||||
rm -f run.pid
|
||||
fi
|
||||
exit 0
|
||||
else
|
||||
echo "${red}No response from Qortal node - not running on port ${api_port}?${normal}"
|
||||
exit 1
|
||||
fi
|
||||
exit 0
|
||||
|
||||
else
|
||||
echo "${red}No response from Qortal node - not running?${normal}"
|
||||
echo "${red}curl is not installed or in the path${normal}"
|
||||
exit 1
|
||||
fi
|
||||
|
122
tools/build-release.sh
Executable file
122
tools/build-release.sh
Executable file
@@ -0,0 +1,122 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Change this to where AdvancedInstaller outputs built EXE installers
|
||||
WINDOWS_INSTALLER_DIR=/home/transfer/Qortal/Qortal-SetupFiles
|
||||
|
||||
set -e
|
||||
shopt -s expand_aliases
|
||||
|
||||
# optional git tag?
|
||||
if [ $# -ge 1 ]; then
|
||||
git_tag="$1"
|
||||
shift
|
||||
fi
|
||||
|
||||
saved_pwd=$PWD
|
||||
|
||||
alias SHA256='(sha256 -q || sha256sum | cut -d" " -f1) 2>/dev/null'
|
||||
|
||||
# Check we are within a git repo
|
||||
git_dir=$( git rev-parse --show-toplevel )
|
||||
if [ -z "${git_dir}" ]; then
|
||||
echo "Cannot determine top-level directory for git repo"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Change to git top-level
|
||||
cd ${git_dir}
|
||||
|
||||
# Check we are in 'master' branch
|
||||
# branch_name=$( git symbolic-ref -q HEAD )
|
||||
# branch_name=${branch_name##refs/heads/}
|
||||
# if [ "${branch_name}" != "master" ]; then
|
||||
# echo "Unexpected current branch '${branch_name}' - expecting 'master'"
|
||||
# exit 1
|
||||
# fi
|
||||
|
||||
# Determine project name
|
||||
project=$( perl -n -e 'if (m/<artifactId>(\w+)<.artifactId>/) { print $1; exit }' pom.xml $)
|
||||
if [ -z "${project}" ]; then
|
||||
echo "Unable to determine project name from pom.xml?"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Extract git tag
|
||||
if [ -z "${git_tag}" ]; then
|
||||
git_tag=$( git tag --points-at HEAD )
|
||||
if [ -z "${git_tag}" ]; then
|
||||
echo "Unable to extract git tag"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# Find origin URL
|
||||
git_url=$( git remote get-url origin )
|
||||
git_url=https://github.com/${git_url##*:}
|
||||
git_url=${git_url%%.git}
|
||||
|
||||
# Check for EXE
|
||||
exe=${project^}-${git_tag#v}.exe
|
||||
exe_src="${WINDOWS_INSTALLER_DIR}/${exe}"
|
||||
if [ ! -r "${exe_src}" ]; then
|
||||
echo "Cannot find EXE installer at ${exe_src}"
|
||||
exit
|
||||
fi
|
||||
|
||||
# Check for ZIP
|
||||
zip_filename=${project}-${git_tag#v}.zip
|
||||
zip_src=${saved_pwd}/${zip_filename}
|
||||
if [ ! -r "${zip_src}" ]; then
|
||||
echo "Cannot find ZIP at ${zip_src}"
|
||||
exit
|
||||
fi
|
||||
|
||||
|
||||
|
||||
# Changes
|
||||
cat <<"__CHANGES__"
|
||||
*Changes in this release:*
|
||||
*
|
||||
__CHANGES__
|
||||
|
||||
# JAR
|
||||
cat <<__JAR__
|
||||
|
||||
### [${project}.jar](${git_url}/releases/download/${git_tag}/${project}.jar)
|
||||
|
||||
If built using OpenJDK 11:
|
||||
__JAR__
|
||||
3hash target/${project}*.jar
|
||||
|
||||
# EXE
|
||||
cat <<__EXE__
|
||||
|
||||
### [${exe}](${git_url}/releases/download/${git_tag}/${exe})
|
||||
|
||||
__EXE__
|
||||
3hash "${exe_src}"
|
||||
|
||||
# VirusTotal url is SHA256 of github download url:
|
||||
virustotal_url=$( echo -n "${git_url}/releases/download/${git_tag}/${exe}" | SHA256 )
|
||||
cat <<__VIRUSTOTAL__
|
||||
|
||||
[VirusTotal report for ${exe}](https://www.virustotal.com/gui/url/${virustotal_url}/detection)
|
||||
__VIRUSTOTAL__
|
||||
|
||||
# ZIP
|
||||
cat <<__ZIP__
|
||||
|
||||
### [${zip_filename}](${git_url}/releases/download/${git_tag}/${zip_filename})
|
||||
|
||||
Contains bare minimum of:
|
||||
* built \`${project}.jar\`
|
||||
* \`log4j2.properties\` from git repo
|
||||
* \`start.sh\` from git repo
|
||||
* \`stop.sh\` from git repo
|
||||
* \`printf "{\n}\n" > settings.json\`
|
||||
|
||||
All timestamps set to same date-time as commit, obtained via \`git show --no-patch --format=%cI\`
|
||||
Packed with \`7z a -r -tzip ${zip_filename} ${project}/\`
|
||||
|
||||
__ZIP__
|
||||
3hash ${zip_src}
|
Reference in New Issue
Block a user