From cd7c032f10a0b90774a641be50f46ea4e2941aa5 Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Fri, 28 Aug 2020 01:48:36 -0500 Subject: [PATCH 1/2] Use a prog arg for the hour of the restart to avoid the risk that multiple seeds restart around the same time which can cause loss of data. The option --seedNodeRestartTime=[0-23] is used to determine the hour when the node restarts. GMT-0 is used. All nodes need to have a synced clock (timezone does not matter). A coordinator need to define which seed uses which hour for the restart. --- .../main/java/bisq/common/config/Config.java | 9 ++++ .../app/misc/ExecutableForAppWithP2p.java | 43 ++++++++++++++----- 2 files changed, 42 insertions(+), 10 deletions(-) diff --git a/common/src/main/java/bisq/common/config/Config.java b/common/src/main/java/bisq/common/config/Config.java index fb5f16659ec..11a83734331 100644 --- a/common/src/main/java/bisq/common/config/Config.java +++ b/common/src/main/java/bisq/common/config/Config.java @@ -118,6 +118,7 @@ public class Config { public static final String ALLOW_FAULTY_DELAYED_TXS = "allowFaultyDelayedTxs"; public static final String API_PASSWORD = "apiPassword"; public static final String API_PORT = "apiPort"; + public static final String SEED_NODE_RESTART_TIME = "seedNodeRestartTime"; // Default values for certain options public static final int UNSPECIFIED_PORT = -1; @@ -203,6 +204,7 @@ public class Config { public final boolean allowFaultyDelayedTxs; public final String apiPassword; public final int apiPort; + public final int seedNodeRestartTime; // Properties derived from options but not exposed as options themselves public final File torDir; @@ -630,6 +632,12 @@ public Config(String defaultAppName, File defaultUserDataDir, String... args) { .ofType(Integer.class) .defaultsTo(9998); + ArgumentAcceptingOptionSpec seedNodeRestartTimeOpt = + parser.accepts(SEED_NODE_RESTART_TIME, "Seed node restart time in GMT-0 (values: 0-23)") + .withRequiredArg() + .ofType(Integer.class) + .defaultsTo(-1); + try { CompositeOptionSet options = new CompositeOptionSet(); @@ -744,6 +752,7 @@ public Config(String defaultAppName, File defaultUserDataDir, String... args) { this.allowFaultyDelayedTxs = options.valueOf(allowFaultyDelayedTxsOpt); this.apiPassword = options.valueOf(apiPasswordOpt); this.apiPort = options.valueOf(apiPortOpt); + this.seedNodeRestartTime = options.valueOf(seedNodeRestartTimeOpt); } catch (OptionException ex) { throw new ConfigException("problem parsing option '%s': %s", ex.options().get(0), diff --git a/core/src/main/java/bisq/core/app/misc/ExecutableForAppWithP2p.java b/core/src/main/java/bisq/core/app/misc/ExecutableForAppWithP2p.java index 813660dd724..4652407817c 100644 --- a/core/src/main/java/bisq/core/app/misc/ExecutableForAppWithP2p.java +++ b/core/src/main/java/bisq/core/app/misc/ExecutableForAppWithP2p.java @@ -36,6 +36,10 @@ import com.google.common.util.concurrent.ThreadFactoryBuilder; +import java.time.Instant; +import java.time.ZoneId; +import java.time.ZonedDateTime; + import java.io.IOException; import java.util.concurrent.Executors; @@ -129,17 +133,36 @@ protected void keepRunning() { } protected void startShutDownInterval(GracefulShutDownHandler gracefulShutDownHandler) { - UserThread.runPeriodically(() -> { - if (System.currentTimeMillis() - startTime > SHUTDOWN_INTERVAL) { - log.warn("\n\n%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n" + - "Shut down as node was running longer as {} hours" + - "\n%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n\n", - SHUTDOWN_INTERVAL / 3600000); - - shutDown(gracefulShutDownHandler); - } + if (config.seedNodeRestartTime < 0 || config.seedNodeRestartTime > 23) { + // -1 is default value which means not defined. Valid values are 0-23, anything else is ignored. + // We restart 24 hours after started. There might be some risk for restart of multiple seed nodes around the + // same time which can lead to lost data. + UserThread.runPeriodically(() -> { + if (System.currentTimeMillis() - startTime > SHUTDOWN_INTERVAL) { + log.warn("\n\n%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n" + + "Shut down as node was running longer as {} hours" + + "\n%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n\n", + SHUTDOWN_INTERVAL / 3600000); - }, CHECK_SHUTDOWN_SEC); + shutDown(gracefulShutDownHandler); + } + }, CHECK_SHUTDOWN_SEC); + } else { + // We interpret the value as hour of day (0-23). If all seeds have updated clocks and a different hour for + // the restart we avoid the risk of a restart of multiple nodes. + + // We wrap our periodic check in a delay of 2 hours to avoid that we get + // triggered multiple times after a restart while being in the same hour + UserThread.runAfter(() -> { + // We check every hour if we are in the target hour. + UserThread.runPeriodically(() -> { + int currentHour = ZonedDateTime.ofInstant(Instant.now(), ZoneId.of("GMT0")).getHour(); + if (currentHour == config.seedNodeRestartTime) { + shutDown(gracefulShutDownHandler); + } + }, TimeUnit.MINUTES.toSeconds(10)); + }, TimeUnit.HOURS.toSeconds(2)); + } } protected void checkMemory(Config config, GracefulShutDownHandler gracefulShutDownHandler) { From 3f26737e2332d12cc3009e226df85c8f419fb6ec Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Fri, 28 Aug 2020 02:22:55 -0500 Subject: [PATCH 2/2] Use index instead of option --- .../main/java/bisq/common/config/Config.java | 9 --- .../app/misc/ExecutableForAppWithP2p.java | 60 ++++++++++--------- .../main/java/bisq/seednode/SeedNodeMain.java | 39 +++++++++++- 3 files changed, 70 insertions(+), 38 deletions(-) diff --git a/common/src/main/java/bisq/common/config/Config.java b/common/src/main/java/bisq/common/config/Config.java index 11a83734331..fb5f16659ec 100644 --- a/common/src/main/java/bisq/common/config/Config.java +++ b/common/src/main/java/bisq/common/config/Config.java @@ -118,7 +118,6 @@ public class Config { public static final String ALLOW_FAULTY_DELAYED_TXS = "allowFaultyDelayedTxs"; public static final String API_PASSWORD = "apiPassword"; public static final String API_PORT = "apiPort"; - public static final String SEED_NODE_RESTART_TIME = "seedNodeRestartTime"; // Default values for certain options public static final int UNSPECIFIED_PORT = -1; @@ -204,7 +203,6 @@ public class Config { public final boolean allowFaultyDelayedTxs; public final String apiPassword; public final int apiPort; - public final int seedNodeRestartTime; // Properties derived from options but not exposed as options themselves public final File torDir; @@ -632,12 +630,6 @@ public Config(String defaultAppName, File defaultUserDataDir, String... args) { .ofType(Integer.class) .defaultsTo(9998); - ArgumentAcceptingOptionSpec seedNodeRestartTimeOpt = - parser.accepts(SEED_NODE_RESTART_TIME, "Seed node restart time in GMT-0 (values: 0-23)") - .withRequiredArg() - .ofType(Integer.class) - .defaultsTo(-1); - try { CompositeOptionSet options = new CompositeOptionSet(); @@ -752,7 +744,6 @@ public Config(String defaultAppName, File defaultUserDataDir, String... args) { this.allowFaultyDelayedTxs = options.valueOf(allowFaultyDelayedTxsOpt); this.apiPassword = options.valueOf(apiPasswordOpt); this.apiPort = options.valueOf(apiPortOpt); - this.seedNodeRestartTime = options.valueOf(seedNodeRestartTimeOpt); } catch (OptionException ex) { throw new ConfigException("problem parsing option '%s': %s", ex.options().get(0), diff --git a/core/src/main/java/bisq/core/app/misc/ExecutableForAppWithP2p.java b/core/src/main/java/bisq/core/app/misc/ExecutableForAppWithP2p.java index 4652407817c..f62a7c3194e 100644 --- a/core/src/main/java/bisq/core/app/misc/ExecutableForAppWithP2p.java +++ b/core/src/main/java/bisq/core/app/misc/ExecutableForAppWithP2p.java @@ -24,7 +24,9 @@ import bisq.core.offer.OpenOfferManager; import bisq.core.support.dispute.arbitration.arbitrator.ArbitratorManager; +import bisq.network.p2p.NodeAddress; import bisq.network.p2p.P2PService; +import bisq.network.p2p.seed.SeedNodeRepository; import bisq.common.UserThread; import bisq.common.config.Config; @@ -42,6 +44,9 @@ import java.io.IOException; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; import java.util.concurrent.Executors; import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; @@ -132,37 +137,36 @@ protected void keepRunning() { } } - protected void startShutDownInterval(GracefulShutDownHandler gracefulShutDownHandler) { - if (config.seedNodeRestartTime < 0 || config.seedNodeRestartTime > 23) { - // -1 is default value which means not defined. Valid values are 0-23, anything else is ignored. - // We restart 24 hours after started. There might be some risk for restart of multiple seed nodes around the - // same time which can lead to lost data. - UserThread.runPeriodically(() -> { - if (System.currentTimeMillis() - startTime > SHUTDOWN_INTERVAL) { - log.warn("\n\n%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n" + - "Shut down as node was running longer as {} hours" + - "\n%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n\n", - SHUTDOWN_INTERVAL / 3600000); + public void startShutDownInterval(GracefulShutDownHandler gracefulShutDownHandler) { + List seedNodeAddresses = new ArrayList<>(injector.getInstance(SeedNodeRepository.class).getSeedNodeAddresses()); + seedNodeAddresses.sort(Comparator.comparing(NodeAddress::getFullAddress)); + + NodeAddress myAddress = injector.getInstance(P2PService.class).getNetworkNode().getNodeAddress(); + int myIndex = -1; + for (int i = 0; i < seedNodeAddresses.size(); i++) { + if (seedNodeAddresses.get(i).equals(myAddress)) { + myIndex = i; + break; + } + } + // We interpret the value of myIndex as hour of day (0-23). That way we avoid the risk of a restart of + // multiple nodes around the same time in case it would be not deterministic. + + // We wrap our periodic check in a delay of 2 hours to avoid that we get + // triggered multiple times after a restart while being in the same hour. It can be that we miss our target + // hour during that delay but that is not considered problematic, the seed would just restart a bit longer than + // 24 hours. + int finalMyIndex = myIndex; + UserThread.runAfter(() -> { + // We check every hour if we are in the target hour. + UserThread.runPeriodically(() -> { + int currentHour = ZonedDateTime.ofInstant(Instant.now(), ZoneId.of("GMT0")).getHour(); + if (currentHour == finalMyIndex) { shutDown(gracefulShutDownHandler); } - }, CHECK_SHUTDOWN_SEC); - } else { - // We interpret the value as hour of day (0-23). If all seeds have updated clocks and a different hour for - // the restart we avoid the risk of a restart of multiple nodes. - - // We wrap our periodic check in a delay of 2 hours to avoid that we get - // triggered multiple times after a restart while being in the same hour - UserThread.runAfter(() -> { - // We check every hour if we are in the target hour. - UserThread.runPeriodically(() -> { - int currentHour = ZonedDateTime.ofInstant(Instant.now(), ZoneId.of("GMT0")).getHour(); - if (currentHour == config.seedNodeRestartTime) { - shutDown(gracefulShutDownHandler); - } - }, TimeUnit.MINUTES.toSeconds(10)); - }, TimeUnit.HOURS.toSeconds(2)); - } + }, TimeUnit.MINUTES.toSeconds(10)); + }, TimeUnit.HOURS.toSeconds(2)); } protected void checkMemory(Config config, GracefulShutDownHandler gracefulShutDownHandler) { diff --git a/seednode/src/main/java/bisq/seednode/SeedNodeMain.java b/seednode/src/main/java/bisq/seednode/SeedNodeMain.java index d8a853deeb4..d8926438589 100644 --- a/seednode/src/main/java/bisq/seednode/SeedNodeMain.java +++ b/seednode/src/main/java/bisq/seednode/SeedNodeMain.java @@ -20,6 +20,9 @@ import bisq.core.app.misc.ExecutableForAppWithP2p; import bisq.core.app.misc.ModuleForAppWithP2p; +import bisq.network.p2p.P2PService; +import bisq.network.p2p.P2PServiceListener; + import bisq.common.UserThread; import bisq.common.app.AppModule; import bisq.common.app.Capabilities; @@ -47,7 +50,6 @@ protected void doExecute() { super.doExecute(); checkMemory(config, this); - startShutDownInterval(this); CommonSetup.setup(this); keepRunning(); @@ -95,5 +97,40 @@ protected void applyInjector() { @Override protected void startApplication() { seedNode.startApplication(); + + injector.getInstance(P2PService.class).addP2PServiceListener(new P2PServiceListener() { + @Override + public void onDataReceived() { + } + + @Override + public void onNoSeedNodeAvailable() { + } + + @Override + public void onNoPeersAvailable() { + } + + @Override + public void onUpdatedDataReceived() { + } + + @Override + public void onTorNodeReady() { + } + + @Override + public void onHiddenServicePublished() { + startShutDownInterval(SeedNodeMain.this); + } + + @Override + public void onSetupFailed(Throwable throwable) { + } + + @Override + public void onRequestCustomBridges() { + } + }); } }