/******************************************************************************
* Copyright © 2013-2016 The Nxt Core Developers. *
* *
* See the AUTHORS.txt, DEVELOPER-AGREEMENT.txt and LICENSE.txt files at *
* the top-level directory of this distribution for the individual copyright *
* holder information and the developer policies on copyright and licensing. *
* *
* Unless otherwise agreed in a custom licensing agreement, no part of the *
* Nxt software, including this file, may be copied, modified, propagated, *
* or distributed except according to the terms contained in the LICENSE.txt *
* file. *
* *
* Removal or modification of this copyright notice is prohibited. *
* *
******************************************************************************/
package nxt.tools;
import nxt.Constants;
import nxt.util.Convert;
import nxt.util.Logger;
import java.math.BigInteger;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.List;
public final class BaseTargetTest {
private static final long MIN_BASE_TARGET = Constants.INITIAL_BASE_TARGET * 9 / 10;
private static final long MAX_BASE_TARGET = Constants.isTestnet ? Constants.MAX_BASE_TARGET : Constants.INITIAL_BASE_TARGET * 50;
private static final int MIN_BLOCKTIME_LIMIT = 53;
private static final int MAX_BLOCKTIME_LIMIT = 67;
private static final int GAMMA = 64;
private static final int START_HEIGHT = 170000;
private static final boolean USE_EWMA = false;
private static final int EWMA_N = 8;
private static final int SMA_N = 3;
private static final int FREQUENCY = 2;
private static long calculateBaseTarget(long previousBaseTarget, long blocktimeEMA) {
long baseTarget;
if (blocktimeEMA > 60) {
baseTarget = (previousBaseTarget * Math.min(blocktimeEMA, MAX_BLOCKTIME_LIMIT)) / 60;
} else {
baseTarget = previousBaseTarget - previousBaseTarget * GAMMA * (60 - Math.max(blocktimeEMA, MIN_BLOCKTIME_LIMIT)) / 6000;
}
if (baseTarget < 0 || baseTarget > MAX_BASE_TARGET) {
baseTarget = MAX_BASE_TARGET;
}
if (baseTarget < MIN_BASE_TARGET) {
baseTarget = MIN_BASE_TARGET;
}
return baseTarget;
}
public static void main(String[] args) {
try {
BigInteger testCumulativeDifficulty = BigInteger.ZERO;
long testBaseTarget;
int testTimestamp;
BigInteger cumulativeDifficulty = BigInteger.ZERO;
long baseTarget;
int timestamp;
BigInteger previousCumulativeDifficulty = null;
long previousBaseTarget = 0;
int previousTimestamp = 0;
BigInteger previousTestCumulativeDifficulty = null;
long previousTestBaseTarget = 0;
int previousTestTimestamp = 0;
int height = START_HEIGHT;
if (args.length == 1) {
height = Integer.parseInt(args[0]);
}
long totalBlocktime = 0;
long totalTestBlocktime = 0;
long maxBlocktime = 0;
long minBlocktime = Integer.MAX_VALUE;
long maxTestBlocktime = 0;
long minTestBlocktime = Integer.MAX_VALUE;
double M = 0.0;
double S = 0.0;
double testM = 0.0;
double testS = 0.0;
long testBlocktimeEMA = 0;
List<Integer> testBlocktimes = new ArrayList<>();
int count = 0;
String dbLocation = Constants.isTestnet ? "nxt_test_db" : "nxt_db";
try (Connection con = DriverManager.getConnection("jdbc:h2:./" + dbLocation + "/nxt;DB_CLOSE_ON_EXIT=FALSE;MVCC=TRUE", "sa", "sa");
PreparedStatement selectBlocks = con.prepareStatement("SELECT * FROM block WHERE height > " + height + " ORDER BY db_id ASC");
ResultSet rs = selectBlocks.executeQuery()) {
while (rs.next()) {
cumulativeDifficulty = new BigInteger(rs.getBytes("cumulative_difficulty"));
baseTarget = rs.getLong("base_target");
timestamp = rs.getInt("timestamp");
height = rs.getInt("height");
if (previousCumulativeDifficulty == null) {
previousCumulativeDifficulty = cumulativeDifficulty;
previousBaseTarget = baseTarget;
previousTimestamp = timestamp;
previousTestCumulativeDifficulty = previousCumulativeDifficulty;
previousTestBaseTarget = previousBaseTarget;
previousTestTimestamp = previousTimestamp;
continue;
}
int testBlocktime = (int)((previousBaseTarget * (timestamp - previousTimestamp - 1)) / previousTestBaseTarget) + 1;
if (testBlocktimeEMA == 0) {
testBlocktimeEMA = testBlocktime;
} else {
testBlocktimeEMA = (testBlocktime + testBlocktimeEMA * (EWMA_N - 1)) / EWMA_N;
}
testTimestamp = previousTestTimestamp + testBlocktime;
testBlocktimes.add(testBlocktime);
if (testBlocktimes.size() > SMA_N) {
testBlocktimes.remove(0);
}
int testBlocktimeSMA = 0;
for (int t : testBlocktimes) {
testBlocktimeSMA += t;
}
testBlocktimeSMA = testBlocktimeSMA / testBlocktimes.size();
if (testBlocktimes.size() < SMA_N) {
testBaseTarget = baseTarget;
} else if ((height - 1) % FREQUENCY == 0) {
testBaseTarget = calculateBaseTarget(previousTestBaseTarget, USE_EWMA ? testBlocktimeEMA : testBlocktimeSMA);
} else {
testBaseTarget = previousTestBaseTarget;
}
testCumulativeDifficulty = previousTestCumulativeDifficulty.add(Convert.two64.divide(BigInteger.valueOf(testBaseTarget)));
int blocktime = timestamp - previousTimestamp;
if (blocktime > maxBlocktime) {
maxBlocktime = blocktime;
}
if (blocktime < minBlocktime) {
minBlocktime = blocktime;
}
if (testBlocktime > maxTestBlocktime) {
maxTestBlocktime = testBlocktime;
}
if (testBlocktime < minTestBlocktime) {
minTestBlocktime = testBlocktime;
}
totalBlocktime += blocktime;
totalTestBlocktime += testBlocktime;
count += 1;
double tmp = M;
M += (blocktime - tmp) / count;
S += (blocktime - tmp) * (blocktime - M);
tmp = testM;
testM += (testBlocktime - tmp) / count;
testS += (testBlocktime - tmp) * (testBlocktime - testM);
previousTestTimestamp = testTimestamp;
previousTestBaseTarget = testBaseTarget;
previousTestCumulativeDifficulty = testCumulativeDifficulty;
previousTimestamp = timestamp;
previousBaseTarget = baseTarget;
previousCumulativeDifficulty = cumulativeDifficulty;
}
}
Logger.logMessage("Cumulative difficulty " + cumulativeDifficulty.toString());
Logger.logMessage("Test cumulative difficulty " + testCumulativeDifficulty.toString());
Logger.logMessage("Cumulative difficulty difference " + (testCumulativeDifficulty.subtract(cumulativeDifficulty))
.multiply(BigInteger.valueOf(100)).divide(cumulativeDifficulty).toString());
Logger.logMessage("Max blocktime " + maxBlocktime);
Logger.logMessage("Max test blocktime " + maxTestBlocktime);
Logger.logMessage("Min blocktime " + minBlocktime);
Logger.logMessage("Min test blocktime " + minTestBlocktime);
Logger.logMessage("Average blocktime " + ((double)totalBlocktime) / count);
Logger.logMessage("Average test blocktime " + ((double)totalTestBlocktime) / count);
Logger.logMessage("Standard deviation of blocktime " + Math.sqrt(S / count));
Logger.logMessage("Standard deviation of test blocktime " + Math.sqrt(testS / count));
} catch (Exception e) {
e.printStackTrace();
}
}
}