Compare commits

...

31 Commits

Author SHA1 Message Date
CalDescent
16453ed602 Added unit tests for level 3+4, 5+6, 7+8, and 9+10 rewards.
These are simpler than the level 1+2 tests; they only test that the rewards are correct for each level post-shareBinFix. I don't think we need multiple instances of the pre-shareBinFix or block orphaning tests. There are a few subtle differences between each test, such as the online status of Bob, in order the make the tests slightly more comprehensive.
2021-03-17 08:50:53 +00:00
CalDescent
fde68dc598 Added unit test to test level 1 and 2 rewards.
1. Assign 3 minters (one founder, one level 1, one level 2)
2. Mint a block after the shareBinFix, ensuring that level 1 and 2 are being rewarded evenly from the same share bin.
3. Orphan the block and ensure the rewards are reversed.
4. Orphan two more blocks, each time checking that the balances are being reduced in accordance with the pre-shareBinFix mapping.
2021-03-16 09:11:49 +00:00
CalDescent
847e81e95c Fixed a mapping issue in Block->getShareBins(), to take effect at some future (undecided) height.
Post trigger, account levels will map correctly to share bins, subtracting 1 to account for the 0th element of the shareBinsByLevel array.
Pre-trigger, the legacy mapping will remain in effect.
2021-03-12 19:48:49 +00:00
CalDescent
7918622e2e Merge pull request #31 from sakumatto/master
Initial Italian translation by Pabs 2021
2021-03-11 11:06:03 +00:00
CalDescent
427fa1816d "blockCacheSize" can now be configured via settings.json. 2021-03-07 10:00:49 +00:00
catbref
0c7e388463 Bump to v1.4.3 2021-02-27 18:24:09 +00:00
catbref
be3af53011 Set new block sig go-live block height: block 320000 2021-02-27 18:23:49 +00:00
catbref
414399b2a0 Merge branch 'blocksig' into master 2021-02-27 18:20:13 +00:00
catbref
c592051a80 Speed up BlockMinter by filtering out 'unconfirmable' transaction types like CHAT & PRESENCE 2021-02-27 17:29:19 +00:00
catbref
33a8f311e5 Reduce logging noise from lost trade-bot ATs and self-clean if AT does not exist after 24 hours 2021-02-24 21:00:52 +00:00
catbref
018c3cdcd4 Allow users to delete trade-bot entries in any state if corresponding AT does not exist 2021-02-24 20:46:47 +00:00
sakumatto
384dffbf9a Initial Italian translation by Pabs 2021
UI localized to Italian by @Pabs
2021-02-22 20:03:11 +02:00
catbref
0306ecb03d AdvancedInstaller updates for v1.4.2 2021-02-21 17:26:32 +00:00
catbref
e5ce732557 More detail in AutoUpdates.md 2021-02-21 17:12:02 +00:00
catbref
f19e0498bf Bump to v1.4.2 2021-02-21 17:06:23 +00:00
CalDescent
32ec02225a Added optional "--testnet" or "-t" argument to stop.sh. When passing this argument, it will attempt to stop the core using the default testnet port (62391) rather than the default mainnet port (12391). 2021-02-21 11:50:02 +00:00
catbref
3920933fc7 Add block fetch TRACE logging to Synchronizer 2021-02-20 13:36:54 +00:00
catbref
1fdd7f156c Reduced logging noise from deleteExpiredTransactions but increased detection & logging on "serilization failures" from HSQLDB 2021-02-20 13:25:53 +00:00
catbref
91925cf931 Change block "minter" signature code, to take effect at some future (undecided) height.
Post trigger, this change will use all 128 bytes of previous block's signature when
calculating/validating next block's "minter" signature (itself the first 64 bytes of a block signature).

Prior to trigger, current behaviour is to only use first 64 bytes of previous block's
signature, which doesn't encompass transactions signature.

New block sig code should help reduce forking and help improve transactional
security.

Added "newBlockSigHeight" to blockchain.json but initially set to block 999999
pending decision on when to merge, auto-update, go-live, etc.
2021-02-20 12:08:51 +00:00
catbref
30e58f1c19 Merge branch 'hsqldb-checkpoint' into master 2021-02-20 10:56:22 +00:00
CalDescent
8d5c6db39f Exclude IntelliJ IDEA files from git. 2021-02-14 18:17:22 +00:00
catbref
3453f0efaf Rework HSQLDB CHECKPOINTing to defer until there are no ongoing SQL transactions, in order to prevent DB deadlocks.
Symptoms of a CHECKPOINT-related DB deadlock:

On Controller thread:
"Controller" #20 prio=5 os_prio=31 cpu=1577665.56ms elapsed=17666.97s allocated=475G defined_classes=412 tid=0x00007fe99f97b000 nid=0x1644b waiting on condition  [0x0000700009a21000]
   java.lang.Thread.State: WAITING (parking)
	at jdk.internal.misc.Unsafe.park(java.base@14.0.2/Native Method)
	- parking to wait for  <0x0000000602f2a6f8> (a org.hsqldb.lib.CountUpDownLatch$Sync)
[...some more lines...]
[this next line is the best indicator: ]
	at org.qortal.repository.hsqldb.HSQLDBRepository.checkpoint(HSQLDBRepository.java:385)
	at org.qortal.repository.RepositoryManager.checkpoint(RepositoryManager.java:51)
	at org.qortal.controller.Controller.run(Controller.java:544)

Other threads stuck at:
	- parking to wait for  <0x00000007ff09f0b0> (a org.hsqldb.lib.CountUpDownLatch$Sync)
	at java.util.concurrent.locks.LockSupport.park(java.base@14.0.2/LockSupport.java:211)
	at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquire(java.base@14.0.2/AbstractQueuedSynchronizer.java:714)
	at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireSharedInterruptibly(java.base@14.0.2/AbstractQueuedSynchronizer.java:1046)
	at org.hsqldb.lib.CountUpDownLatch.await(Unknown Source)
	at org.hsqldb.Session.executeCompiledStatement(Unknown Source)
2021-02-13 17:32:09 +00:00
catbref
eb23940996 Fix potential NPE when trying to obtain opportunistic database connection.
Could have affected:
Controller.deleteExpiredTransactions()
Network.getConnectablePeer()
Network.opportunisticMergePeers()
Network.prunePeers()

Symptoms:

2021-02-12 16:46:06 WARN  NetworkProcessor:152 - [1556] exception while trying to produce task
java.lang.NullPointerException: null
        at org.qortal.repository.hsqldb.HSQLDBRepository.<init>(HSQLDBRepository.java:92) ~[qortal.jar:1.4.1]
        at org.qortal.repository.hsqldb.HSQLDBRepositoryFactory.tryRepository(HSQLDBRepositoryFactory.java:97) ~[qortal.jar:1.4.1]
        at org.qortal.repository.RepositoryManager.tryRepository(RepositoryManager.java:33) ~[qortal.jar:1.4.1]
        at org.qortal.network.Network.getConnectablePeer(Network.java:525) ~[qortal.jar:1.4.1]
2021-02-13 11:24:26 +00:00
catbref
6cd86d86a6 Add "atFindNextTransactionFix" feature-trigger to all test blockchain configs 2021-02-13 11:24:26 +00:00
CalDescent
c3fa34f5b9 Fixed .gitignore to ensure that all .DS_Store files (autogenerated by macOS) are excluded. 2021-02-13 11:17:10 +00:00
CalDescent
0af0aaaa21 Merge pull request #29 from sakumatto/master
Initial translations of UI terms into Finnish
2021-02-13 11:12:24 +00:00
CalDescent
02100c502b Exit from stop.sh with an error if curl isn't installed. Based on code submitted by TRM13 in issue #28. 2021-02-12 16:13:01 +00:00
sakumatto
b55154cd3c Initial translations of UI terms into Finnish
Initial translations of UI terms into Finnish language fi_ by Saku Mättö
2021-02-12 17:02:31 +02:00
catbref
dc6eda1355 Added tool to help build release notes 2021-02-06 11:02:36 +00:00
catbref
6224bc3bca Updated AdvancedInstaller config file to latest Qortal v1.4.1 2021-02-06 11:00:32 +00:00
catbref
9ceac8c991 Documentation updates 2021-02-06 10:59:31 +00:00
49 changed files with 1705 additions and 112 deletions

BIN
.DS_Store vendored Normal file

Binary file not shown.

3
.gitignore vendored
View File

@@ -16,3 +16,6 @@
/settings*.json
/testchain.json
/run-testnet.sh
/.idea
/qortal.iml
*.DS_Store

View File

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

View File

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

View File

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

View File

@@ -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"/>

View File

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

Binary file not shown.

BIN
src/main/.DS_Store vendored Normal file

Binary file not shown.

View File

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

View File

@@ -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())

View File

@@ -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) {

View File

@@ -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);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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.
*

View File

@@ -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());
}
}

View File

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

View File

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

View File

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

View File

@@ -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());

View File

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

Binary file not shown.

View File

@@ -48,7 +48,9 @@
"minutesPerBlock": 1
},
"featureTriggers": {
"atFindNextTransactionFix": 275000
"atFindNextTransactionFix": 275000,
"newBlockSigHeight": 320000,
"shareBinFix": 999999
},
"genesisInfo": {
"version": 4,

View 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

View 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

View 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

View 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

View 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

View 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

View File

@@ -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();
}
}

View File

@@ -44,7 +44,10 @@
"powfixTimestamp": 0,
"qortalTimestamp": 0,
"newAssetPricingTimestamp": 0,
"groupApprovalTimestamp": 0
"groupApprovalTimestamp": 0,
"atFindNextTransactionFix": 0,
"newBlockSigHeight": 999999,
"shareBinFix": 999999
},
"genesisInfo": {
"version": 4,

View File

@@ -44,7 +44,10 @@
"powfixTimestamp": 0,
"qortalTimestamp": 0,
"newAssetPricingTimestamp": 0,
"groupApprovalTimestamp": 0
"groupApprovalTimestamp": 0,
"atFindNextTransactionFix": 0,
"newBlockSigHeight": 999999,
"shareBinFix": 999999
},
"genesisInfo": {
"version": 4,

View File

@@ -44,7 +44,10 @@
"powfixTimestamp": 0,
"qortalTimestamp": 0,
"newAssetPricingTimestamp": 0,
"groupApprovalTimestamp": 0
"groupApprovalTimestamp": 0,
"atFindNextTransactionFix": 0,
"newBlockSigHeight": 999999,
"shareBinFix": 999999
},
"genesisInfo": {
"version": 4,

View File

@@ -44,7 +44,10 @@
"powfixTimestamp": 0,
"qortalTimestamp": 0,
"newAssetPricingTimestamp": 0,
"groupApprovalTimestamp": 0
"groupApprovalTimestamp": 0,
"atFindNextTransactionFix": 0,
"newBlockSigHeight": 999999,
"shareBinFix": 999999
},
"genesisInfo": {
"version": 4,

View File

@@ -44,7 +44,10 @@
"powfixTimestamp": 0,
"qortalTimestamp": 0,
"newAssetPricingTimestamp": 0,
"groupApprovalTimestamp": 0
"groupApprovalTimestamp": 0,
"atFindNextTransactionFix": 0,
"newBlockSigHeight": 999999,
"shareBinFix": 999999
},
"genesisInfo": {
"version": 4,

View 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 }
]
}
}

View File

@@ -44,7 +44,10 @@
"powfixTimestamp": 0,
"qortalTimestamp": 0,
"newAssetPricingTimestamp": 0,
"groupApprovalTimestamp": 0
"groupApprovalTimestamp": 0,
"atFindNextTransactionFix": 0,
"newBlockSigHeight": 999999,
"shareBinFix": 999999
},
"genesisInfo": {
"version": 4,

View File

@@ -44,7 +44,10 @@
"powfixTimestamp": 0,
"qortalTimestamp": 0,
"newAssetPricingTimestamp": 0,
"groupApprovalTimestamp": 0
"groupApprovalTimestamp": 0,
"atFindNextTransactionFix": 0,
"newBlockSigHeight": 999999,
"shareBinFix": 999999
},
"genesisInfo": {
"version": 4,

View 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
View File

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