Compare commits
15 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
2ddb1fa23e | ||
|
82f6e38adb | ||
|
00ac26cf27 | ||
|
fa1aa1c8b2 | ||
|
9156325ffc | ||
|
70131914b2 | ||
|
bd87e6cc1a | ||
|
6c8e96daae | ||
|
cfb8f53849 | ||
|
7bb2f841ad | ||
|
558263521c | ||
|
1db8c06291 | ||
|
edee08a7b5 | ||
|
83bce3ce52 | ||
|
bf288dbfc2 |
2
pom.xml
@@ -3,7 +3,7 @@
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>org.qortal</groupId>
|
||||
<artifactId>qortal</artifactId>
|
||||
<version>1.0.5</version>
|
||||
<version>1.0.6</version>
|
||||
<packaging>jar</packaging>
|
||||
<properties>
|
||||
<bitcoin.version>0.15.4</bitcoin.version>
|
||||
|
17
run.sh
@@ -6,6 +6,23 @@ if [ "$USER" = "root" ]; then
|
||||
exit
|
||||
fi
|
||||
|
||||
# Validate Java is installed and the minimum version is available
|
||||
MIN_JAVA_VER='11'
|
||||
|
||||
if command -v java > /dev/null 2>&1; then
|
||||
version=$(java -version 2>&1 | awk -F '"' '/version/ {print $2}')
|
||||
version=$(echo $version | cut -d'.' -f1,2)
|
||||
if [ `echo "${version}>=${MIN_JAVA_VER}" | bc` -eq 1 ]; then
|
||||
echo 'Passed Java version check'
|
||||
else
|
||||
echo 'Please upgrade your Java to version 11 or greater'
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
echo 'Java is not available, please install Java 11 or greater'
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# No qortal.jar but we have a Maven built one?
|
||||
# Be helpful and copy across to correct location
|
||||
if [ ! -e qortal.jar -a -f target/qortal*.jar ]; then
|
||||
|
@@ -36,8 +36,8 @@ public class ApplyUpdate {
|
||||
private static final String NEW_JAR_FILENAME = AutoUpdate.NEW_JAR_FILENAME;
|
||||
private static final String WINDOWS_EXE_LAUNCHER = "qortal.exe";
|
||||
|
||||
private static final long CHECK_INTERVAL = 5 * 1000L; // ms
|
||||
private static final int MAX_ATTEMPTS = 5;
|
||||
private static final long CHECK_INTERVAL = 10 * 1000L; // ms
|
||||
private static final int MAX_ATTEMPTS = 12;
|
||||
|
||||
public static void main(String[] args) {
|
||||
Security.insertProviderAt(new BouncyCastleProvider(), 0);
|
||||
|
@@ -53,8 +53,8 @@ public class Account {
|
||||
|
||||
// Balance manipulations - assetId is 0 for QORT
|
||||
|
||||
public BigDecimal getBalance(long assetId, int height) throws DataException {
|
||||
AccountBalanceData accountBalanceData = this.repository.getAccountRepository().getBalance(this.address, assetId, height);
|
||||
public BigDecimal getBalance(long assetId) throws DataException {
|
||||
AccountBalanceData accountBalanceData = this.repository.getAccountRepository().getBalance(this.address, assetId);
|
||||
if (accountBalanceData == null)
|
||||
return BigDecimal.ZERO.setScale(8);
|
||||
|
||||
|
@@ -149,8 +149,8 @@ public class ApiRequest {
|
||||
HttpURLConnection con = (HttpURLConnection) url.openConnection();
|
||||
|
||||
con.setRequestMethod("GET");
|
||||
con.setConnectTimeout(5000);
|
||||
con.setReadTimeout(3000);
|
||||
con.setConnectTimeout(30000);
|
||||
con.setReadTimeout(10000);
|
||||
ApiRequest.setConnectionSSL(con, ipAddress);
|
||||
|
||||
int status = con.getResponseCode();
|
||||
|
@@ -108,8 +108,19 @@ public class ApiService {
|
||||
swaggerUIServlet.setInitParameter("pathInfoOnly", "true");
|
||||
context.addServlet(swaggerUIServlet, "/api-documentation/*");
|
||||
|
||||
rewriteHandler.addRule(new RedirectPatternRule("", "/api-documentation/")); // redirect to Swagger UI start page
|
||||
rewriteHandler.addRule(new RedirectPatternRule("/api-documentation", "/api-documentation/")); // redirect to Swagger UI start page
|
||||
rewriteHandler.addRule(new RedirectPatternRule("", "/api-documentation/")); // redirect empty path to API docs
|
||||
rewriteHandler.addRule(new RedirectPatternRule("/api-documentation", "/api-documentation/")); // redirect to add trailing slash if missing
|
||||
} else {
|
||||
// Simple pages that explains that API documentation is disabled
|
||||
ClassLoader loader = this.getClass().getClassLoader();
|
||||
ServletHolder swaggerUIServlet = new ServletHolder("api-docs-disabled", DefaultServlet.class);
|
||||
swaggerUIServlet.setInitParameter("resourceBase", loader.getResource("api-docs-disabled/").toString());
|
||||
swaggerUIServlet.setInitParameter("dirAllowed", "true");
|
||||
swaggerUIServlet.setInitParameter("pathInfoOnly", "true");
|
||||
context.addServlet(swaggerUIServlet, "/api-documentation/*");
|
||||
|
||||
rewriteHandler.addRule(new RedirectPatternRule("", "/api-documentation/")); // redirect empty path to API docs
|
||||
rewriteHandler.addRule(new RedirectPatternRule("/api-documentation", "/api-documentation/")); // redirect to add trailing slash if missing
|
||||
}
|
||||
|
||||
// Start server
|
||||
|
@@ -192,7 +192,7 @@ public class AddressesResource {
|
||||
@Path("/balance/{address}")
|
||||
@Operation(
|
||||
summary = "Returns account balance",
|
||||
description = "Returns account's balance, optionally of given asset and at given height",
|
||||
description = "Returns account's QORT balance, or of other specified asset",
|
||||
responses = {
|
||||
@ApiResponse(
|
||||
description = "the balance",
|
||||
@@ -202,8 +202,7 @@ public class AddressesResource {
|
||||
)
|
||||
@ApiErrors({ApiError.INVALID_ADDRESS, ApiError.INVALID_ASSET_ID, ApiError.INVALID_HEIGHT, ApiError.REPOSITORY_ISSUE})
|
||||
public BigDecimal getBalance(@PathParam("address") String address,
|
||||
@QueryParam("assetId") Long assetId,
|
||||
@QueryParam("height") Integer height) {
|
||||
@QueryParam("assetId") Long assetId) {
|
||||
if (!Crypto.isValidAddress(address))
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_ADDRESS);
|
||||
|
||||
@@ -215,12 +214,7 @@ public class AddressesResource {
|
||||
else if (!repository.getAssetRepository().assetExists(assetId))
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_ASSET_ID);
|
||||
|
||||
if (height == null)
|
||||
height = repository.getBlockRepository().getBlockchainHeight();
|
||||
else if (height <= 0)
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_HEIGHT);
|
||||
|
||||
return account.getBalance(assetId, height);
|
||||
return account.getBalance(assetId);
|
||||
} catch (ApiException e) {
|
||||
throw e;
|
||||
} catch (DataException e) {
|
||||
|
@@ -1466,9 +1466,6 @@ public class Block {
|
||||
decreaseAccountLevels();
|
||||
}
|
||||
|
||||
// Delete orphaned balances
|
||||
this.repository.getAccountRepository().deleteBalancesFromHeight(this.blockData.getHeight());
|
||||
|
||||
// Delete block from blockchain
|
||||
this.repository.getBlockRepository().delete(this.blockData);
|
||||
this.blockData.setHeight(null);
|
||||
|
@@ -41,7 +41,7 @@ public class AutoUpdate extends Thread {
|
||||
public static final String NEW_JAR_FILENAME = "new-" + JAR_FILENAME;
|
||||
|
||||
private static final Logger LOGGER = LogManager.getLogger(AutoUpdate.class);
|
||||
private static final long CHECK_INTERVAL = 5 * 60 * 1000L; // ms
|
||||
private static final long CHECK_INTERVAL = 20 * 60 * 1000L; // ms
|
||||
|
||||
private static final int DEV_GROUP_ID = 1;
|
||||
private static final int UPDATE_SERVICE = 1;
|
||||
@@ -210,7 +210,7 @@ public class AutoUpdate extends Thread {
|
||||
return false; // failed - try another repo
|
||||
}
|
||||
} catch (IOException e) {
|
||||
LOGGER.warn(String.format("Failed to fetch update from %s", repoUri));
|
||||
LOGGER.warn(String.format("Failed to fetch update from %s: %s", repoUri, e.getMessage()));
|
||||
return false; // failed - try another repo
|
||||
}
|
||||
|
||||
|
@@ -104,7 +104,6 @@ public class Controller extends Thread {
|
||||
private static final Object shutdownLock = new Object();
|
||||
private static final String repositoryUrlTemplate = "jdbc:hsqldb:file:%s" + File.separator + "blockchain;create=true;hsqldb.full_log_replay=true";
|
||||
private static final long ARBITRARY_REQUEST_TIMEOUT = 5 * 1000L; // ms
|
||||
private static final long REPOSITORY_BACKUP_PERIOD = 123 * 60 * 1000L; // ms
|
||||
private static final long NTP_PRE_SYNC_CHECK_PERIOD = 5 * 1000L; // ms
|
||||
private static final long NTP_POST_SYNC_CHECK_PERIOD = 5 * 60 * 1000L; // ms
|
||||
private static final long DELETE_EXPIRED_INTERVAL = 5 * 60 * 1000L; // ms
|
||||
@@ -127,7 +126,7 @@ public class Controller extends Thread {
|
||||
|
||||
private volatile BlockData chainTip = null;
|
||||
|
||||
private long repositoryBackupTimestamp = startTime + REPOSITORY_BACKUP_PERIOD; // ms
|
||||
private long repositoryBackupTimestamp = startTime; // ms
|
||||
private long ntpCheckTimestamp = startTime; // ms
|
||||
private long deleteExpiredTimestamp = startTime + DELETE_EXPIRED_INTERVAL; // ms
|
||||
private long onlineAccountsTasksTimestamp = startTime + ONLINE_ACCOUNTS_TASKS_INTERVAL; // ms
|
||||
@@ -388,6 +387,8 @@ public class Controller extends Thread {
|
||||
public void run() {
|
||||
Thread.currentThread().setName("Controller");
|
||||
|
||||
final long repositoryBackupInterval = Settings.getInstance().getRepositoryBackupInterval();
|
||||
|
||||
try {
|
||||
while (!isStopping) {
|
||||
// Maybe update SysTray
|
||||
@@ -428,9 +429,9 @@ public class Controller extends Thread {
|
||||
final long requestMinimumTimestamp = now - ARBITRARY_REQUEST_TIMEOUT;
|
||||
arbitraryDataRequests.entrySet().removeIf(entry -> entry.getValue().getC() < requestMinimumTimestamp);
|
||||
|
||||
// Give repository a chance to backup
|
||||
if (now >= repositoryBackupTimestamp) {
|
||||
repositoryBackupTimestamp = now + REPOSITORY_BACKUP_PERIOD;
|
||||
// Give repository a chance to backup (if enabled)
|
||||
if (repositoryBackupInterval > 0 && now >= repositoryBackupTimestamp + repositoryBackupInterval) {
|
||||
repositoryBackupTimestamp = now + repositoryBackupInterval;
|
||||
|
||||
if (Settings.getInstance().getShowBackupNotification())
|
||||
SysTray.getInstance().showMessage(Translator.INSTANCE.translate("SysTray", "DB_BACKUP"),
|
||||
|
@@ -96,12 +96,6 @@ public interface AccountRepository {
|
||||
|
||||
public AccountBalanceData getBalance(String address, long assetId) throws DataException;
|
||||
|
||||
/** Returns account balance data for address & assetId at (or before) passed block height. */
|
||||
public AccountBalanceData getBalance(String address, long assetId, int height) throws DataException;
|
||||
|
||||
/** Returns per-height historic balance for address & assetId. */
|
||||
public List<AccountBalanceData> getHistoricBalances(String address, long assetId) throws DataException;
|
||||
|
||||
public enum BalanceOrdering {
|
||||
ASSET_BALANCE_ACCOUNT,
|
||||
ACCOUNT_ASSET,
|
||||
@@ -118,9 +112,6 @@ public interface AccountRepository {
|
||||
|
||||
public void delete(String address, long assetId) throws DataException;
|
||||
|
||||
/** Deletes orphaned balances at block height >= <tt>height</tt>. */
|
||||
public int deleteBalancesFromHeight(int height) throws DataException;
|
||||
|
||||
// Reward-shares
|
||||
|
||||
public RewardShareData getRewardShare(byte[] mintingAccountPublicKey, String recipientAccount) throws DataException;
|
||||
|
@@ -327,44 +327,6 @@ public class HSQLDBAccountRepository implements AccountRepository {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public AccountBalanceData getBalance(String address, long assetId, int height) throws DataException {
|
||||
String sql = "SELECT IFNULL(balance, 0) FROM HistoricAccountBalances WHERE account = ? AND asset_id = ? AND height <= ? ORDER BY height DESC LIMIT 1";
|
||||
|
||||
try (ResultSet resultSet = this.repository.checkedExecute(sql, address, assetId, height)) {
|
||||
if (resultSet == null)
|
||||
return null;
|
||||
|
||||
BigDecimal balance = resultSet.getBigDecimal(1).setScale(8);
|
||||
|
||||
return new AccountBalanceData(address, assetId, balance);
|
||||
} catch (SQLException e) {
|
||||
throw new DataException("Unable to fetch account balance from repository", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<AccountBalanceData> getHistoricBalances(String address, long assetId) throws DataException {
|
||||
String sql = "SELECT height, balance FROM HistoricAccountBalances WHERE account = ? AND asset_id = ? ORDER BY height DESC";
|
||||
|
||||
List<AccountBalanceData> historicBalances = new ArrayList<>();
|
||||
try (ResultSet resultSet = this.repository.checkedExecute(sql, address, assetId)) {
|
||||
if (resultSet == null)
|
||||
return historicBalances;
|
||||
|
||||
do {
|
||||
int height = resultSet.getInt(1);
|
||||
BigDecimal balance = resultSet.getBigDecimal(2);
|
||||
|
||||
historicBalances.add(new AccountBalanceData(address, assetId, balance, height));
|
||||
} while (resultSet.next());
|
||||
|
||||
return historicBalances;
|
||||
} catch (SQLException e) {
|
||||
throw new DataException("Unable to fetch historic account balances from repository", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<AccountBalanceData> getAssetBalances(long assetId, Boolean excludeZero) throws DataException {
|
||||
StringBuilder sql = new StringBuilder(1024);
|
||||
@@ -510,19 +472,6 @@ public class HSQLDBAccountRepository implements AccountRepository {
|
||||
} catch (SQLException e) {
|
||||
throw new DataException("Unable to reduce account balance in repository", e);
|
||||
}
|
||||
|
||||
// If balance is now zero, and there are no prior historic balances, then simply delete row for this address-assetId (typically during orphaning)
|
||||
String deleteWhereSql = "account = ? AND asset_id = ? AND balance = 0 " + // covers "if balance now zero"
|
||||
"AND (" +
|
||||
"SELECT TRUE FROM HistoricAccountBalances " +
|
||||
"WHERE account = ? AND asset_id = ? AND height < (SELECT height - 1 FROM NextBlockHeight) " +
|
||||
"LIMIT 1" +
|
||||
")";
|
||||
try {
|
||||
this.repository.delete("AccountBalances", deleteWhereSql, address, assetId, address, assetId);
|
||||
} catch (SQLException e) {
|
||||
throw new DataException("Unable to prune account balance in repository", e);
|
||||
}
|
||||
} else {
|
||||
// We have to ensure parent row exists to satisfy foreign key constraint
|
||||
try {
|
||||
@@ -545,47 +494,12 @@ public class HSQLDBAccountRepository implements AccountRepository {
|
||||
|
||||
@Override
|
||||
public void save(AccountBalanceData accountBalanceData) throws DataException {
|
||||
// If balance is zero and there are no prior historic balance, then simply delete balances for this assetId (typically during orphaning)
|
||||
if (accountBalanceData.getBalance().signum() == 0) {
|
||||
String existsSql = "account = ? AND asset_id = ? AND height < (SELECT height - 1 FROM NextBlockHeight)"; // height prior to current block. no matches (obviously) prior to genesis block
|
||||
|
||||
boolean hasPriorBalances;
|
||||
try {
|
||||
hasPriorBalances = this.repository.exists("HistoricAccountBalances", existsSql, accountBalanceData.getAddress(), accountBalanceData.getAssetId());
|
||||
} catch (SQLException e) {
|
||||
throw new DataException("Unable to check for historic account balances in repository", e);
|
||||
}
|
||||
|
||||
if (!hasPriorBalances) {
|
||||
try {
|
||||
this.repository.delete("AccountBalances", "account = ? AND asset_id = ?", accountBalanceData.getAddress(), accountBalanceData.getAssetId());
|
||||
} catch (SQLException e) {
|
||||
throw new DataException("Unable to delete account balance from repository", e);
|
||||
}
|
||||
|
||||
/*
|
||||
* I don't think we need to do this as Block.orphan() would do this for us?
|
||||
|
||||
try {
|
||||
this.repository.delete("HistoricAccountBalances", "account = ? AND asset_id = ?", accountBalanceData.getAddress(), accountBalanceData.getAssetId());
|
||||
} catch (SQLException e) {
|
||||
throw new DataException("Unable to delete historic account balances from repository", e);
|
||||
}
|
||||
|
||||
*/
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
HSQLDBSaver saveHelper = new HSQLDBSaver("AccountBalances");
|
||||
|
||||
saveHelper.bind("account", accountBalanceData.getAddress()).bind("asset_id", accountBalanceData.getAssetId()).bind("balance",
|
||||
accountBalanceData.getBalance());
|
||||
|
||||
try {
|
||||
// HistoricAccountBalances auto-updated via trigger
|
||||
|
||||
saveHelper.execute(this.repository);
|
||||
} catch (SQLException e) {
|
||||
throw new DataException("Unable to save account balance into repository", e);
|
||||
@@ -599,21 +513,6 @@ public class HSQLDBAccountRepository implements AccountRepository {
|
||||
} catch (SQLException e) {
|
||||
throw new DataException("Unable to delete account balance from repository", e);
|
||||
}
|
||||
|
||||
try {
|
||||
this.repository.delete("HistoricAccountBalances", "account = ? AND asset_id = ?", address, assetId);
|
||||
} catch (SQLException e) {
|
||||
throw new DataException("Unable to delete historic account balances from repository", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int deleteBalancesFromHeight(int height) throws DataException {
|
||||
try {
|
||||
return this.repository.delete("HistoricAccountBalances", "height >= ?", height);
|
||||
} catch (SQLException e) {
|
||||
throw new DataException("Unable to delete historic account balances from repository", e);
|
||||
}
|
||||
}
|
||||
|
||||
// Reward-Share
|
||||
|
@@ -956,6 +956,25 @@ public class HSQLDBDatabaseUpdates {
|
||||
stmt.execute("SET FILES WRITE DELAY 5"); // only fsync() every 5 seconds
|
||||
break;
|
||||
|
||||
case 69:
|
||||
// Get rid of historic account balances as they simply use up way too much space
|
||||
stmt.execute("DROP TRIGGER Historic_Account_Balance_Insert_Trigger");
|
||||
stmt.execute("DROP TRIGGER Historic_Account_Balance_Update_Trigger");
|
||||
stmt.execute("DROP TABLE HistoricAccountBalances");
|
||||
// Reclaim space
|
||||
stmt.execute("CHECKPOINT");
|
||||
stmt.execute("CHECKPOINT DEFRAG");
|
||||
break;
|
||||
|
||||
case 70:
|
||||
// Reduce space used for storing online account in Blocks
|
||||
stmt.execute("ALTER TABLE Blocks ALTER COLUMN online_accounts BLOB(1M)");
|
||||
stmt.execute("ALTER TABLE Blocks ALTER COLUMN online_accounts_signatures BLOB(1M)");
|
||||
// Reclaim space
|
||||
stmt.execute("CHECKPOINT");
|
||||
stmt.execute("CHECKPOINT DEFRAG");
|
||||
break;
|
||||
|
||||
default:
|
||||
// nothing to do
|
||||
return false;
|
||||
|
@@ -71,6 +71,8 @@ public class Settings {
|
||||
private int maxTransactionTimestampFuture = 24 * 60 * 60 * 1000; // milliseconds
|
||||
/** Whether we check, fetch and install auto-updates */
|
||||
private boolean autoUpdateEnabled = true;
|
||||
/** How long between repository backups (ms), or 0 if disabled. */
|
||||
private long repositoryBackupInterval = 0; // ms
|
||||
/** Whether to show a notification when we backup repository. */
|
||||
private boolean showBackupNotification = false;
|
||||
|
||||
@@ -79,11 +81,11 @@ public class Settings {
|
||||
/** Port number for inbound peer-to-peer connections. */
|
||||
private Integer listenPort;
|
||||
/** Minimum number of peers to allow block minting / synchronization. */
|
||||
private int minBlockchainPeers = 10;
|
||||
private int minBlockchainPeers = 8;
|
||||
/** Target number of outbound connections to peers we should make. */
|
||||
private int minOutboundPeers = 25;
|
||||
private int minOutboundPeers = 16;
|
||||
/** Maximum number of peer connections we allow. */
|
||||
private int maxPeers = 50;
|
||||
private int maxPeers = 32;
|
||||
/** Maximum number of threads for network engine. */
|
||||
private int maxNetworkThreadPoolSize = 20;
|
||||
|
||||
@@ -371,6 +373,10 @@ public class Settings {
|
||||
return this.testNtpOffset;
|
||||
}
|
||||
|
||||
public long getRepositoryBackupInterval() {
|
||||
return this.repositoryBackupInterval;
|
||||
}
|
||||
|
||||
public boolean getShowBackupNotification() {
|
||||
return this.showBackupNotification;
|
||||
}
|
||||
|
13
src/main/resources/api-docs-disabled/index.html
Normal file
@@ -0,0 +1,13 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<title>API Documentation disabled</title>
|
||||
</head>
|
||||
<body>
|
||||
API documentation pages are currently disabled.
|
||||
<p>
|
||||
To enable, add an entry like this to your <tt>settings.json</tt> file:
|
||||
<p>
|
||||
<code>"apiDocumentationEnabled": true,</code>
|
||||
</body>
|
||||
</html>
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 27 KiB |
Before Width: | Height: | Size: 770 B After Width: | Height: | Size: 852 B |
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 2.6 KiB |
Before Width: | Height: | Size: 6.9 KiB After Width: | Height: | Size: 8.4 KiB |
Before Width: | Height: | Size: 250 KiB After Width: | Height: | Size: 250 KiB |
Before Width: | Height: | Size: 74 KiB After Width: | Height: | Size: 77 KiB |
@@ -14,15 +14,10 @@ import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.qortal.account.Account;
|
||||
import org.qortal.account.PrivateKeyAccount;
|
||||
import org.qortal.account.PublicKeyAccount;
|
||||
import org.qortal.asset.Asset;
|
||||
import org.qortal.block.BlockChain;
|
||||
import org.qortal.data.account.AccountBalanceData;
|
||||
import org.qortal.data.account.AccountData;
|
||||
import org.qortal.data.transaction.BaseTransactionData;
|
||||
import org.qortal.data.transaction.PaymentTransactionData;
|
||||
import org.qortal.data.transaction.TransactionData;
|
||||
import org.qortal.repository.DataException;
|
||||
import org.qortal.repository.Repository;
|
||||
import org.qortal.repository.RepositoryManager;
|
||||
@@ -30,7 +25,6 @@ import org.qortal.repository.AccountRepository.BalanceOrdering;
|
||||
import org.qortal.test.common.BlockUtils;
|
||||
import org.qortal.test.common.Common;
|
||||
import org.qortal.test.common.TestAccount;
|
||||
import org.qortal.test.common.TransactionUtils;
|
||||
|
||||
public class AccountBalanceTests extends Common {
|
||||
|
||||
@@ -88,123 +82,6 @@ public class AccountBalanceTests extends Common {
|
||||
}
|
||||
}
|
||||
|
||||
/** Tests we can fetch initial balance when newer balance exists. */
|
||||
@Test
|
||||
public void testGetBalanceAtHeight() throws DataException {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
TestAccount alice = Common.getTestAccount(repository, "alice");
|
||||
|
||||
BigDecimal initialBalance = testNewerBalance(repository, alice);
|
||||
|
||||
// Fetch all historic balances
|
||||
List<AccountBalanceData> historicBalances = repository.getAccountRepository().getHistoricBalances(alice.getAddress(), Asset.QORT);
|
||||
for (AccountBalanceData historicBalance : historicBalances)
|
||||
System.out.println(String.format("Balance at height %d: %s", historicBalance.getHeight(), historicBalance.getBalance().toPlainString()));
|
||||
|
||||
// Fetch balance at height 1, even though newer balance exists
|
||||
AccountBalanceData accountBalanceData = repository.getAccountRepository().getBalance(alice.getAddress(), Asset.QORT, 1);
|
||||
BigDecimal genesisBalance = accountBalanceData.getBalance();
|
||||
|
||||
// Confirm genesis balance is same as initial
|
||||
assertEqualBigDecimals("Genesis balance should match initial", initialBalance, genesisBalance);
|
||||
}
|
||||
}
|
||||
|
||||
/** Tests we can fetch balance with a height where no balance change occurred. */
|
||||
@Test
|
||||
public void testGetBalanceAtNearestHeight() throws DataException {
|
||||
Random random = new Random();
|
||||
|
||||
byte[] publicKey = new byte[32];
|
||||
random.nextBytes(publicKey);
|
||||
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
PublicKeyAccount recipientAccount = new PublicKeyAccount(repository, publicKey);
|
||||
System.out.println(String.format("Test recipient: %s", recipientAccount.getAddress()));
|
||||
|
||||
// Mint a few blocks
|
||||
for (int i = 0; i < 10; ++i)
|
||||
BlockUtils.mintBlock(repository);
|
||||
|
||||
// Confirm recipient balance is zero
|
||||
BigDecimal balance = recipientAccount.getConfirmedBalance(Asset.QORT);
|
||||
assertEqualBigDecimals("recipient's balance should be zero", BigDecimal.ZERO, balance);
|
||||
|
||||
// Confirm recipient has no historic balances
|
||||
List<AccountBalanceData> historicBalances = repository.getAccountRepository().getHistoricBalances(recipientAccount.getAddress(), Asset.QORT);
|
||||
for (AccountBalanceData historicBalance : historicBalances)
|
||||
System.err.println(String.format("Block %d: %s", historicBalance.getHeight(), historicBalance.getBalance().toPlainString()));
|
||||
assertTrue("recipient should not have historic balances yet", historicBalances.isEmpty());
|
||||
|
||||
// Send 1 QORT to recipient
|
||||
TestAccount sendingAccount = Common.getTestAccount(repository, "alice");
|
||||
pay(repository, sendingAccount, recipientAccount, BigDecimal.ONE);
|
||||
|
||||
// Mint some more blocks
|
||||
for (int i = 0; i < 10; ++i)
|
||||
BlockUtils.mintBlock(repository);
|
||||
|
||||
// Send more QORT to recipient
|
||||
BigDecimal amount = BigDecimal.valueOf(random.nextInt(123456));
|
||||
pay(repository, sendingAccount, recipientAccount, amount);
|
||||
BigDecimal totalAmount = BigDecimal.ONE.add(amount);
|
||||
|
||||
// Mint some more blocks
|
||||
for (int i = 0; i < 10; ++i)
|
||||
BlockUtils.mintBlock(repository);
|
||||
|
||||
// Confirm recipient balance is as expected
|
||||
balance = recipientAccount.getConfirmedBalance(Asset.QORT);
|
||||
assertEqualBigDecimals("recipient's balance incorrect", totalAmount, balance);
|
||||
|
||||
historicBalances = repository.getAccountRepository().getHistoricBalances(recipientAccount.getAddress(), Asset.QORT);
|
||||
for (AccountBalanceData historicBalance : historicBalances)
|
||||
System.out.println(String.format("Block %d: %s", historicBalance.getHeight(), historicBalance.getBalance().toPlainString()));
|
||||
|
||||
// Confirm balance as of 2 blocks ago
|
||||
int height = repository.getBlockRepository().getBlockchainHeight();
|
||||
balance = repository.getAccountRepository().getBalance(recipientAccount.getAddress(), Asset.QORT, height - 2).getBalance();
|
||||
assertEqualBigDecimals("recipient's historic balance incorrect", totalAmount, balance);
|
||||
|
||||
// Confirm balance prior to last payment
|
||||
balance = repository.getAccountRepository().getBalance(recipientAccount.getAddress(), Asset.QORT, height - 15).getBalance();
|
||||
assertEqualBigDecimals("recipient's historic balance incorrect", BigDecimal.ONE, balance);
|
||||
|
||||
// Orphan blocks to before last payment
|
||||
BlockUtils.orphanBlocks(repository, 10 + 5);
|
||||
|
||||
// Re-check balance from (now) invalid height
|
||||
AccountBalanceData accountBalanceData = repository.getAccountRepository().getBalance(recipientAccount.getAddress(), Asset.QORT, height - 2);
|
||||
balance = accountBalanceData.getBalance();
|
||||
assertEqualBigDecimals("recipient's invalid-height balance should be one", BigDecimal.ONE, balance);
|
||||
|
||||
// Orphan blocks to before initial 1 QORT payment
|
||||
BlockUtils.orphanBlocks(repository, 10 + 5);
|
||||
|
||||
// Re-check balance from (now) invalid height
|
||||
accountBalanceData = repository.getAccountRepository().getBalance(recipientAccount.getAddress(), Asset.QORT, height - 2);
|
||||
assertNull("recipient's invalid-height balance data should be null", accountBalanceData);
|
||||
|
||||
// Confirm recipient has no historic balances
|
||||
historicBalances = repository.getAccountRepository().getHistoricBalances(recipientAccount.getAddress(), Asset.QORT);
|
||||
for (AccountBalanceData historicBalance : historicBalances)
|
||||
System.err.println(String.format("Block %d: %s", historicBalance.getHeight(), historicBalance.getBalance().toPlainString()));
|
||||
assertTrue("recipient should have no remaining historic balances", historicBalances.isEmpty());
|
||||
}
|
||||
}
|
||||
|
||||
private void pay(Repository repository, PrivateKeyAccount sendingAccount, Account recipientAccount, BigDecimal amount) throws DataException {
|
||||
byte[] reference = sendingAccount.getLastReference();
|
||||
long timestamp = repository.getTransactionRepository().fromSignature(reference).getTimestamp() + 1;
|
||||
|
||||
int txGroupId = 0;
|
||||
BigDecimal fee = BlockChain.getInstance().getUnitFee();
|
||||
BaseTransactionData baseTransactionData = new BaseTransactionData(timestamp, txGroupId, reference, sendingAccount.getPublicKey(), fee, null);
|
||||
TransactionData transactionData = new PaymentTransactionData(baseTransactionData, recipientAccount.getAddress(), amount);
|
||||
|
||||
TransactionUtils.signAndMint(repository, transactionData, sendingAccount);
|
||||
}
|
||||
|
||||
/** Tests SQL query speed for account balance fetches. */
|
||||
@Test
|
||||
public void testRepositorySpeed() throws DataException, SQLException {
|
||||
|
35
stop.sh
@@ -1,10 +1,41 @@
|
||||
#!/bin/sh
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Check for color support
|
||||
if [ -t 1 ]; then
|
||||
ncolors=$( tput colors )
|
||||
if [ -n "${ncolors}" -a "${ncolors}" -ge 8 ]; then
|
||||
if normal="$( tput sgr0 )"; then
|
||||
# use terminfo names
|
||||
red="$( tput setaf 1 )"
|
||||
green="$( tput setaf 2)"
|
||||
else
|
||||
# use termcap names for FreeBSD compat
|
||||
normal="$( tput me )"
|
||||
red="$( tput AF 1 )"
|
||||
green="$( tput AF 2)"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# Track the pid if we can find it
|
||||
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
|
||||
fi
|
||||
exit 0
|
||||
else
|
||||
echo "No response from Qortal node - not running?"
|
||||
echo "${red}No response from Qortal node - not running?${normal}"
|
||||
exit 1
|
||||
fi
|
||||
|
57
tools/build-zip.sh
Executable file
@@ -0,0 +1,57 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -e
|
||||
|
||||
saved_pwd=$PWD
|
||||
|
||||
# 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/}
|
||||
echo "Current git branch: ${branch_name}"
|
||||
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
|
||||
git_tag=$( git tag --points-at HEAD )
|
||||
if [ -z "${git_tag}" ]; then
|
||||
echo "Unable to extract git tag"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
build_dir=/tmp/${project}
|
||||
commit_ts=$( git show --no-patch --format=%cI )
|
||||
|
||||
/bin/rm -fr ${build_dir}
|
||||
mkdir -p ${build_dir}
|
||||
|
||||
cp target/${project}*.jar ${build_dir}/${project}.jar
|
||||
|
||||
git show HEAD:log4j2.properties > ${build_dir}/log4j2.properties
|
||||
|
||||
git show HEAD:run.sh > ${build_dir}/run.sh
|
||||
|
||||
printf "{\n}\n" > ${build_dir}/settings.json
|
||||
|
||||
touch -d ${commit_ts%%+??:??} ${build_dir} ${build_dir}/*
|
||||
|
||||
rm -f ${saved_pwd}/${project}.zip
|
||||
(cd ${build_dir}/..; 7z a -r -tzip ${saved_pwd}/${project}-${git_tag#v}.zip ${project}/)
|
@@ -86,11 +86,11 @@ die("Can't convert base58 public key to hex:\n$pubkey_hex\n") unless $pubkey_hex
|
||||
printf "Public key hex: %s\n", $pubkey_hex;
|
||||
|
||||
my $address = `curl --silent --url http://localhost:${port}/addresses/convert/${pubkey}`;
|
||||
die("Can't convert base58 public key to address:\n$address\n") unless $address =~ m/^\w{34}$/;
|
||||
die("Can't convert base58 public key to address:\n$address\n") unless $address =~ m/^\w{33,34}$/;
|
||||
printf "Address: %s\n", $address;
|
||||
|
||||
my $reference = `curl --silent --url http://localhost:${port}/addresses/lastreference/${address}`;
|
||||
die("Can't fetch last reference for $address:\n$reference\n") unless $reference =~ m/^\w{88}$/;
|
||||
die("Can't fetch last reference for $address:\n$reference\n") unless $reference =~ m/^\w{87,88}$/;
|
||||
printf "Last reference: %s\n", $reference;
|
||||
|
||||
my $reference_hex = `curl --silent --url http://localhost:${port}/utils/frombase58 --data ${reference}`;
|
||||
|