mirror of
https://github.com/Qortal/qortal.git
synced 2025-07-23 04:36:50 +00:00
NTP and performance changes + fixes.
New NTP class now runs as a simplistic NTP client, repeatedly polling several NTP servers and maintaining a more accurate time independent of operating system. Several occurrences of System.currentTimeMillis() replaced with NTP.getTime() particularly where block/transaction/networking is involved. GET /admin/info now includes "currentTimestamp" as reported from NTP. Added support for block timestamps determined by generator, instead of supplied by clock. (BlockChain.newBlockTimestampHeight - not yet activated). Incorrect timestamps will produce a TIMESTAMP_INCORRECT Block.ValidationResult. Block.calcMinimumTimestamp repurposed as Block.calcTimestamp for above. Block timestamps are now allowed to be max 2000ms in the future, was previously max 500ms. Block generation prohibited until initial NTP sync. Instead of deleting INVALID unconfirmed transactions in BlockGenerator, Controller now deletes EXPIRED unconfirmed transactions every so often. This also fixes persistent expired unconfirmed transactions on nodes that do not generate blocks, as BlockGenerator.deleteInvalidTransactions() was never reached. Abbreviated block sigs added to log entries declaring a new block is generated in BlockGenerator. Controller checks for NTP sync much faster during start-up and SysTray's tooltip text starts as "Synchronizing clock" until NTP sync occurs. After NTP sync, Controller logs NTP offset every so often (currently every 5 mins). When considering synchronizing, Controller skips peers that have the same block sig as last time when synchronization resulted in no action, e.g. INFERIOR_CHAIN, NOTHING_TO_DO and also OK. OK is included as another sync attempt would result in NOTHING_TO_DO. Previously this skipping check only happened after prior INFERIOR_CHAIN. During inbound peer handshaking, if we receive a peer ID that matches an existing inbound peer then send peer ID of all zeros, then close connection. Remote end should detect this and cleanly close connection instead of waiting for handshake timeout. Randomly generated peer IDs have lowest bit set to avoid all zeros. Might need further work. Networking doesn't connect, or accept, until NTP has synced. Transaction validation can fail with CLOCK_NOT_SYNCED if NTP not synced.
This commit is contained in:
@@ -5,7 +5,13 @@ import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Deque;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletionService;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.ExecutorCompletionService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
import org.apache.commons.net.ntp.NTPUDPClient;
|
||||
import org.apache.commons.net.ntp.NtpV3Packet;
|
||||
@@ -13,74 +19,182 @@ import org.apache.commons.net.ntp.TimeInfo;
|
||||
|
||||
public class NTPTests {
|
||||
|
||||
private static final List<String> CC_TLDS = Arrays.asList("oceania", "europe", "lat", "asia", "africa");
|
||||
private static final List<String> CC_TLDS = Arrays.asList("oceania", "europe", "cn", "asia", "africa");
|
||||
|
||||
public static void main(String[] args) throws UnknownHostException, IOException {
|
||||
public static void main(String[] args) throws UnknownHostException, IOException, InterruptedException {
|
||||
NTPUDPClient client = new NTPUDPClient();
|
||||
client.setDefaultTimeout(2000);
|
||||
|
||||
System.out.println(String.format("%c%16s %16s %2s %c %4s %4s %3s %7s %7s %7s",
|
||||
' ', "remote", "refid", "st", 't', "when", "poll", "reach", "delay", "offset", "jitter"
|
||||
));
|
||||
class NTPServer {
|
||||
private static final int MIN_POLL = 8;
|
||||
|
||||
List<Double> offsets = new ArrayList<>();
|
||||
public char usage = ' ';
|
||||
public String remote;
|
||||
public String refId;
|
||||
public Integer stratum;
|
||||
public char type = 'u'; // unicast
|
||||
public int poll = MIN_POLL;
|
||||
public byte reach = 0;
|
||||
public Long delay;
|
||||
public Double offset;
|
||||
public Double jitter;
|
||||
|
||||
List<String> ntpServers = new ArrayList<>();
|
||||
for (String ccTld : CC_TLDS) {
|
||||
ntpServers.add(ccTld + ".pool.ntp.org");
|
||||
for (int subpool = 0; subpool <=3; ++subpool)
|
||||
ntpServers.add(subpool + "." + ccTld + ".pool.ntp.org");
|
||||
}
|
||||
private Deque<Double> offsets = new LinkedList<>();
|
||||
private double totalSquareOffsets = 0.0;
|
||||
private long nextPoll;
|
||||
private Long lastGood;
|
||||
|
||||
for (String server : ntpServers) {
|
||||
try {
|
||||
TimeInfo timeInfo = client.getTime(InetAddress.getByName(server));
|
||||
public NTPServer(String remote) {
|
||||
this.remote = remote;
|
||||
}
|
||||
|
||||
timeInfo.computeDetails();
|
||||
NtpV3Packet ntpMessage = timeInfo.getMessage();
|
||||
public boolean poll(NTPUDPClient client) {
|
||||
final long now = System.currentTimeMillis();
|
||||
|
||||
System.out.println(String.format("%c%16.16s %16.16s %2d %c %4d %4d %3o %6dms % 5dms % 5dms",
|
||||
' ',
|
||||
server,
|
||||
ntpMessage.getReferenceIdString(),
|
||||
ntpMessage.getStratum(),
|
||||
'u',
|
||||
0,
|
||||
1 << ntpMessage.getPoll(),
|
||||
1,
|
||||
timeInfo.getDelay(),
|
||||
timeInfo.getOffset(),
|
||||
0
|
||||
));
|
||||
if (now < this.nextPoll)
|
||||
return false;
|
||||
|
||||
offsets.add((double) timeInfo.getOffset());
|
||||
} catch (IOException e) {
|
||||
// Try next server...
|
||||
boolean isUpdated = false;
|
||||
try {
|
||||
TimeInfo timeInfo = client.getTime(InetAddress.getByName(remote));
|
||||
|
||||
timeInfo.computeDetails();
|
||||
NtpV3Packet ntpMessage = timeInfo.getMessage();
|
||||
|
||||
this.refId = ntpMessage.getReferenceIdString();
|
||||
this.stratum = ntpMessage.getStratum();
|
||||
this.poll = Math.max(MIN_POLL, 1 << ntpMessage.getPoll());
|
||||
|
||||
this.delay = timeInfo.getDelay();
|
||||
this.offset = (double) timeInfo.getOffset();
|
||||
|
||||
if (this.offsets.size() == 8) {
|
||||
double oldOffset = this.offsets.removeFirst();
|
||||
this.totalSquareOffsets -= oldOffset * oldOffset;
|
||||
}
|
||||
|
||||
this.offsets.addLast(this.offset);
|
||||
this.totalSquareOffsets += this.offset * this.offset;
|
||||
|
||||
this.jitter = Math.sqrt(this.totalSquareOffsets / this.offsets.size());
|
||||
|
||||
this.reach = (byte) ((this.reach << 1) | 1);
|
||||
this.lastGood = now;
|
||||
|
||||
isUpdated = true;
|
||||
} catch (IOException e) {
|
||||
this.reach <<= 1;
|
||||
}
|
||||
|
||||
this.nextPoll = now + this.poll * 1000;
|
||||
return isUpdated;
|
||||
}
|
||||
|
||||
public Integer getWhen() {
|
||||
if (this.lastGood == null)
|
||||
return null;
|
||||
|
||||
return (int) ((System.currentTimeMillis() - this.lastGood) / 1000);
|
||||
}
|
||||
}
|
||||
|
||||
if (offsets.size() < ntpServers.size() / 2) {
|
||||
System.err.println("Not enough replies");
|
||||
System.exit(1);
|
||||
List<NTPServer> ntpServers = new ArrayList<>();
|
||||
|
||||
for (String ccTld : CC_TLDS)
|
||||
for (int subpool = 0; subpool <=3; ++subpool)
|
||||
ntpServers.add(new NTPServer(subpool + "." + ccTld + ".pool.ntp.org"));
|
||||
|
||||
while (true) {
|
||||
Thread.sleep(1000);
|
||||
|
||||
CompletionService<Boolean> ecs = new ExecutorCompletionService<Boolean>(Executors.newCachedThreadPool());
|
||||
for (NTPServer server : ntpServers)
|
||||
ecs.submit(() -> server.poll(client));
|
||||
|
||||
boolean showReport = false;
|
||||
for (int i = 0; i < ntpServers.size(); ++i)
|
||||
try {
|
||||
showReport = ecs.take().get() || showReport;
|
||||
} catch (ExecutionException e) {
|
||||
// skip
|
||||
}
|
||||
|
||||
if (showReport) {
|
||||
double s0 = 0;
|
||||
double s1 = 0;
|
||||
double s2 = 0;
|
||||
|
||||
for (NTPServer server : ntpServers) {
|
||||
if (server.offset == null) {
|
||||
server.usage = ' ';
|
||||
continue;
|
||||
}
|
||||
|
||||
server.usage = '+';
|
||||
double value = server.offset * (double) server.stratum;
|
||||
|
||||
s0 += 1;
|
||||
s1 += value;
|
||||
s2 += value * value;
|
||||
}
|
||||
|
||||
if (s0 < ntpServers.size() / 3 + 1) {
|
||||
System.out.println("Not enough replies to calculate network time");
|
||||
} else {
|
||||
double filterStddev = Math.sqrt(((s0 * s2) - (s1 * s1)) / (s0 * (s0 - 1)));
|
||||
double filterMean = s1 / s0;
|
||||
|
||||
// Now only consider offsets within 1 stddev?
|
||||
s0 = 0;
|
||||
s1 = 0;
|
||||
s2 = 0;
|
||||
|
||||
for (NTPServer server : ntpServers) {
|
||||
if (server.offset == null || server.reach == 0)
|
||||
continue;
|
||||
|
||||
if (Math.abs(server.offset * (double)server.stratum - filterMean) > filterStddev)
|
||||
continue;
|
||||
|
||||
server.usage = '*';
|
||||
s0 += 1;
|
||||
s1 += server.offset;
|
||||
s2 += server.offset * server.offset;
|
||||
}
|
||||
|
||||
if (s0 <= 1) {
|
||||
System.out.println(String.format("Not enough values to calculate network time. stddev: %7.4f", filterStddev));
|
||||
} else {
|
||||
double mean = s1 / s0;
|
||||
double newStddev = Math.sqrt(((s0 * s2) - (s1 * s1)) / (s0 * (s0 - 1)));
|
||||
System.out.println(String.format("filtering stddev: %7.3f, mean: %7.3f, new stddev: %7.3f, nValues: %.0f / %d", filterStddev, mean, newStddev, s0, ntpServers.size()));
|
||||
}
|
||||
}
|
||||
|
||||
System.out.println(String.format("%c%16s %16s %2s %c %4s %4s %3s %7s %7s %7s",
|
||||
' ', "remote", "refid", "st", 't', "when", "poll", "reach", "delay", "offset", "jitter"
|
||||
));
|
||||
|
||||
for (NTPServer server : ntpServers)
|
||||
System.out.println(String.format("%c%16.16s %16.16s %2s %c %4s %4d %3o %7s %7s %7s",
|
||||
server.usage,
|
||||
server.remote,
|
||||
formatNull("%s", server.refId, ""),
|
||||
formatNull("%2d", server.stratum, ""),
|
||||
server.type,
|
||||
formatNull("%4d", server.getWhen(), "-"),
|
||||
server.poll,
|
||||
server.reach,
|
||||
formatNull("%5dms", server.delay, ""),
|
||||
formatNull("% 5.0fms", server.offset, ""),
|
||||
formatNull("%5.2fms", server.jitter, "")
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
double s0 = 0;
|
||||
double s1 = 0;
|
||||
double s2 = 0;
|
||||
|
||||
for (Double offset : offsets) {
|
||||
// Exclude nearby results for more extreme testing
|
||||
if (offset < 100.0)
|
||||
continue;
|
||||
|
||||
s0 += 1;
|
||||
s1 += offset;
|
||||
s2 += offset * offset;
|
||||
}
|
||||
|
||||
double mean = s1 / s0;
|
||||
double stddev = Math.sqrt(((s0 * s2) - (s1 * s1)) / (s0 * (s0 - 1)));
|
||||
System.out.println(String.format("mean: %7.3f, stddev: %7.3f", mean, stddev));
|
||||
private static String formatNull(String format, Object arg, String nullOutput) {
|
||||
return arg != null ? String.format(format, arg) : nullOutput;
|
||||
}
|
||||
|
||||
}
|
||||
|
Reference in New Issue
Block a user