com.swirlds.platform.Browser Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of swirlds-platform-core Show documentation
Show all versions of swirlds-platform-core Show documentation
Swirlds is a software platform designed to build fully-distributed applications that harness the power of the cloud without servers. Now you can develop applications with fairness in decision making, speed, trust and reliability, at a fraction of the cost of traditional server-based platforms.
The newest version!
/*
* Copyright (C) 2016-2024 Hedera Hashgraph, LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.swirlds.platform;
import static com.swirlds.common.io.utility.FileUtils.getAbsolutePath;
import static com.swirlds.common.io.utility.FileUtils.rethrowIO;
import static com.swirlds.common.threading.manager.AdHocThreadManager.getStaticThreadManager;
import static com.swirlds.logging.legacy.LogMarker.EXCEPTION;
import static com.swirlds.platform.builder.PlatformBuildConstants.DEFAULT_CONFIG_FILE_NAME;
import static com.swirlds.platform.builder.PlatformBuildConstants.DEFAULT_SETTINGS_FILE_NAME;
import static com.swirlds.platform.builder.PlatformBuildConstants.LOG4J_FILE_NAME;
import static com.swirlds.platform.gui.internal.BrowserWindowManager.addPlatforms;
import static com.swirlds.platform.gui.internal.BrowserWindowManager.getStateHierarchy;
import static com.swirlds.platform.gui.internal.BrowserWindowManager.moveBrowserWindowToFront;
import static com.swirlds.platform.gui.internal.BrowserWindowManager.setBrowserWindow;
import static com.swirlds.platform.gui.internal.BrowserWindowManager.setStateHierarchy;
import static com.swirlds.platform.gui.internal.BrowserWindowManager.showBrowserWindow;
import static com.swirlds.platform.util.BootstrapUtils.checkNodesToRun;
import static com.swirlds.platform.util.BootstrapUtils.getNodesToRun;
import static com.swirlds.platform.util.BootstrapUtils.loadSwirldMains;
import static com.swirlds.platform.util.BootstrapUtils.setupBrowserWindow;
import com.swirlds.common.platform.NodeId;
import com.swirlds.common.startup.Log4jSetup;
import com.swirlds.common.threading.framework.config.ThreadConfiguration;
import com.swirlds.common.utility.CommonUtils;
import com.swirlds.config.api.Configuration;
import com.swirlds.config.api.ConfigurationBuilder;
import com.swirlds.metrics.api.Metrics;
import com.swirlds.platform.builder.PlatformBuilder;
import com.swirlds.platform.config.PathsConfig;
import com.swirlds.platform.crypto.CryptoConstants;
import com.swirlds.platform.gui.GuiEventStorage;
import com.swirlds.platform.gui.hashgraph.HashgraphGuiSource;
import com.swirlds.platform.gui.hashgraph.internal.StandardGuiSource;
import com.swirlds.platform.gui.internal.StateHierarchy;
import com.swirlds.platform.gui.internal.WinBrowser;
import com.swirlds.platform.gui.model.InfoApp;
import com.swirlds.platform.gui.model.InfoMember;
import com.swirlds.platform.gui.model.InfoSwirld;
import com.swirlds.platform.state.snapshot.SignedStateFileUtils;
import com.swirlds.platform.system.SwirldMain;
import com.swirlds.platform.system.SystemExitCode;
import com.swirlds.platform.system.SystemExitUtils;
import com.swirlds.platform.util.BootstrapUtils;
import edu.umd.cs.findbugs.annotations.NonNull;
import java.awt.GraphicsEnvironment;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicBoolean;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
/**
* The Browser that launches the Platforms that run the apps.
*/
public class Browser {
// Each member is represented by an AddressBook entry in config.txt. On a given computer, a single java
// process runs all members whose listed internal IP address matches some address on that computer. That
// Java process will instantiate one Platform per member running on that machine. But there will be only
// one static Browser that they all share.
//
// Every member, whatever computer it is running on, listens on 0.0.0.0, on its internal port. Every
// member connects to every other member, by computing its IP address as follows: If that other member
// is also on the same host, use 127.0.0.1. If it is on the same LAN[*], use its internal address.
// Otherwise, use its external address.
//
// This way, a single config.txt can be shared across computers unchanged, even if, for example, those
// computers are on different networks in Amazon EC2.
//
// [*] Two members are considered to be on the same LAN if their listed external addresses are the same.
private static Logger logger = LogManager.getLogger(Browser.class);
/**
* True if the browser has been launched
*/
private static final AtomicBoolean STARTED = new AtomicBoolean(false);
/**
* Main method for starting the browser
*
* @param args command line arguments
*/
public static void main(final String[] args) {
parseCommandLineArgsAndLaunch(args);
}
/**
* Parse the command line arguments and launch the browser
*
* @param args command line arguments
*/
public static void parseCommandLineArgsAndLaunch(@NonNull final String... args) {
final CommandLineArgs commandLineArgs = CommandLineArgs.parse(args);
launch(commandLineArgs, false);
}
/**
* Launch the browser with the command line arguments already parsed
*
* @param commandLineArgs the parsed command line arguments
* @param pcesRecovery if true, the platform will be started in PCES recovery mode
*/
public static void launch(@NonNull final CommandLineArgs commandLineArgs, final boolean pcesRecovery) {
if (STARTED.getAndSet(true)) {
return;
}
final Path log4jPath = getAbsolutePath(LOG4J_FILE_NAME);
try {
Log4jSetup.startLoggingFramework(log4jPath).await();
} catch (final InterruptedException e) {
CommonUtils.tellUserConsole("Interrupted while waiting for log4j to initialize");
Thread.currentThread().interrupt();
}
logger = LogManager.getLogger(Browser.class);
try {
launchUnhandled(commandLineArgs, pcesRecovery);
} catch (final Throwable e) {
logger.error(EXCEPTION.getMarker(), "Unable to start Browser", e);
throw new RuntimeException("Unable to start Browser", e);
}
}
/**
* Launch the browser but do not handle any exceptions
*
* @param commandLineArgs the parsed command line arguments
* @param pcesRecovery if true, the platform will be started in PCES recovery mode
*/
private static void launchUnhandled(@NonNull final CommandLineArgs commandLineArgs, final boolean pcesRecovery)
throws Exception {
Objects.requireNonNull(commandLineArgs);
final PathsConfig defaultPathsConfig = ConfigurationBuilder.create()
.withConfigDataType(PathsConfig.class)
.build()
.getConfigData(PathsConfig.class);
// Load config.txt file, parse application jar file name, main class name, address book, and parameters
final ApplicationDefinition appDefinition =
ApplicationDefinitionLoader.loadDefault(defaultPathsConfig, getAbsolutePath(DEFAULT_CONFIG_FILE_NAME));
// Determine which nodes to run locally
final List nodesToRun =
getNodesToRun(appDefinition.getConfigAddressBook(), commandLineArgs.localNodesToStart());
checkNodesToRun(nodesToRun);
// Load all SwirldMain instances for locally run nodes.
final Map appMains = loadSwirldMains(appDefinition, nodesToRun);
ParameterProvider.getInstance().setParameters(appDefinition.getAppParameters());
final boolean showUi = !GraphicsEnvironment.isHeadless();
final GuiEventStorage guiEventStorage;
final HashgraphGuiSource guiSource;
Metrics guiMetrics = null;
if (showUi) {
setupBrowserWindow();
setStateHierarchy(new StateHierarchy(null));
final InfoApp infoApp = getStateHierarchy().getInfoApp(appDefinition.getApplicationName());
final InfoSwirld infoSwirld = new InfoSwirld(infoApp, new byte[CryptoConstants.HASH_SIZE_BYTES]);
new InfoMember(infoSwirld, "Node" + nodesToRun.getFirst().id());
// Duplicating config here is ugly, but Browser is test only code now.
// In the future we should clean it up, but it's not urgent to do so.
final ConfigurationBuilder guiConfigBuilder = ConfigurationBuilder.create();
BootstrapUtils.setupConfigBuilder(guiConfigBuilder, getAbsolutePath(DEFAULT_SETTINGS_FILE_NAME));
final Configuration guiConfig = guiConfigBuilder.build();
guiEventStorage = new GuiEventStorage(guiConfig, appDefinition.getConfigAddressBook());
guiSource = new StandardGuiSource(appDefinition.getConfigAddressBook(), guiEventStorage);
} else {
guiSource = null;
guiEventStorage = null;
}
final Map platforms = new HashMap<>();
for (int index = 0; index < nodesToRun.size(); index++) {
final NodeId nodeId = nodesToRun.get(index);
final SwirldMain appMain = appMains.get(nodeId);
final ConfigurationBuilder configBuilder = ConfigurationBuilder.create();
final List> configTypes = appMain.getConfigDataTypes();
for (final Class extends Record> configType : configTypes) {
configBuilder.withConfigDataType(configType);
}
rethrowIO(() ->
BootstrapUtils.setupConfigBuilder(configBuilder, getAbsolutePath(DEFAULT_SETTINGS_FILE_NAME)));
final Configuration configuration = configBuilder.build();
final PlatformBuilder builder = PlatformBuilder.create(
appMain.getClass().getName(),
appDefinition.getSwirldName(),
appMain.getSoftwareVersion(),
appMain::newMerkleStateRoot,
SignedStateFileUtils::readState,
nodeId);
if (showUi && index == 0) {
builder.withPreconsensusEventCallback(guiEventStorage::handlePreconsensusEvent);
builder.withConsensusSnapshotOverrideCallback(guiEventStorage::handleSnapshotOverride);
}
final SwirldsPlatform platform =
(SwirldsPlatform) builder.withConfiguration(configuration).build();
platforms.put(nodeId, platform);
if (showUi) {
if (index == 0) {
guiMetrics = platform.getContext().getMetrics();
}
}
}
addPlatforms(platforms.values());
// FUTURE WORK: PCES recovery not compatible with non-Browser launched apps
if (pcesRecovery) {
// PCES recovery is only expected to be done on a single node
// due to the structure of Browser atm, it makes more sense to enable the feature for multiple platforms
platforms.values().forEach(SwirldsPlatform::performPcesRecovery);
SystemExitUtils.exitSystem(SystemExitCode.NO_ERROR, "PCES recovery done");
}
startPlatforms(new ArrayList<>(platforms.values()), appMains);
if (showUi) {
setBrowserWindow(
new WinBrowser(nodesToRun.getFirst(), guiSource, guiEventStorage.getConsensus(), guiMetrics));
showBrowserWindow(null);
moveBrowserWindowToFront();
}
}
/**
* Start all local platforms.
*
* @param platforms the platforms to start
*/
private static void startPlatforms(
@NonNull final List platforms, @NonNull final Map appMains) {
final List startThreads = new ArrayList<>();
for (final SwirldsPlatform platform : platforms) {
final Thread thread = new ThreadConfiguration(getStaticThreadManager())
.setThreadName("start-node-" + platform.getSelfId().id())
.setRunnable(() -> startPlatform(platform, appMains.get(platform.getSelfId())))
.build(true);
startThreads.add(thread);
}
for (final Thread startThread : startThreads) {
try {
startThread.join();
} catch (final InterruptedException e) {
logger.error(EXCEPTION.getMarker(), "Interrupted while waiting for platform to start", e);
Thread.currentThread().interrupt();
}
}
}
/**
* Start a platform and its associated app.
*
* @param platform the platform to start
* @param appMain the app to start
*/
private static void startPlatform(@NonNull final SwirldsPlatform platform, @NonNull final SwirldMain appMain) {
appMain.init(platform, platform.getSelfId());
platform.start();
new ThreadConfiguration(getStaticThreadManager())
.setNodeId(platform.getSelfId())
.setComponent("app")
.setThreadName("appMain")
.setRunnable(appMain)
.setDaemon(false)
.build(true);
}
}