From 122c3df0b5159fc74298caa5cefc1b0ed3725683 Mon Sep 17 00:00:00 2001 From: Alexandra Date: Fri, 17 Aug 2018 17:53:09 -0400 Subject: [PATCH 01/31] new command line arguments parser --- lib/picocli-3.5.1.jar | Bin 0 -> 228881 bytes modAionImpl/build.xml | 4 +- .../src/org/aion/zero/impl/cli/Arguments.java | 218 ++++++++++++++++++ .../org/aion/zero/impl/cli/ArgumentsTest.java | 181 +++++++++++++++ 4 files changed, 402 insertions(+), 1 deletion(-) create mode 100644 lib/picocli-3.5.1.jar create mode 100644 modAionImpl/src/org/aion/zero/impl/cli/Arguments.java create mode 100644 modAionImpl/test/org/aion/zero/impl/cli/ArgumentsTest.java diff --git a/lib/picocli-3.5.1.jar b/lib/picocli-3.5.1.jar new file mode 100644 index 0000000000000000000000000000000000000000..26f45e6498eb45652067dab2314e087d23e416e4 GIT binary patch literal 228881 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

syEwwzG8pkCa!%24*HFd= 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

  • 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 + @@ -162,7 +163,7 @@ - + @@ -171,6 +172,7 @@ + diff --git a/modAionImpl/src/org/aion/zero/impl/cli/Arguments.java b/modAionImpl/src/org/aion/zero/impl/cli/Arguments.java new file mode 100644 index 0000000000..c3bb6e52a4 --- /dev/null +++ b/modAionImpl/src/org/aion/zero/impl/cli/Arguments.java @@ -0,0 +1,218 @@ +/* + * Copyright (c) 2017-2018 Aion foundation. + * + * This file is part of the aion network project. + * + * The aion network project is free software: you can redistribute it + * and/or modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or any later version. + * + * The aion network project is distributed in the hope that it will + * be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with the aion network project source files. + * If not, see . + * + * Contributors: + * Aion foundation. + */ +package org.aion.zero.impl.cli; + +import java.util.ArrayList; +import java.util.List; +import picocli.CommandLine; +import picocli.CommandLine.Command; +import picocli.CommandLine.Help.Ansi.Style; +import picocli.CommandLine.Help.ColorScheme; +import picocli.CommandLine.Option; + +/** + * Command line arguments for the Aion kernel. + * + * @author Alexandra Roatis + */ +@Command(name = "./aion.sh", separator = " ", sortOptions = false, abbreviateSynopsis = true) +public class Arguments { + + // usage information + @Option( + names = {"-h", "--help"}, + usageHelp = true, + description = "display help information" + ) + private boolean help = false; + + // account management + @Option( + names = {"ac", "-a create"}, + description = "create a new account" + ) + private boolean createAccount = false; + + @Option( + names = {"al", "-a list"}, + description = "list all existing accounts" + ) + private boolean listAccounts = false; + + @Option( + names = {"ae", "-a export"}, + paramLabel = "", + description = "export private key of an account" + ) + private String exportAccount; + + @Option( + names = {"ai", "-a import"}, + paramLabel = "", + description = "import private key" + ) + private String importAccount; + + // config generation + @Option( + names = {"-c", "--config"}, + paramLabel = "", + description = "create config for the selected network\noptions: mainnet, conquest" + ) + public String config; + + // get information and version + @Option( + names = {"-i", "--info"}, + description = "display information" + ) + public boolean info = false; + + @Option( + names = {"-v"}, + description = "display version" + ) + public boolean version = false; + + @Option( + names = {"--version"}, + description = "display version tag" + ) + public boolean versionTag = false; + + // create ssl certificate + @Option( + names = {"sc", "-s create"}, + arity = "0..2", + paramLabel = " ", + description = + "create an ssl certificate for:\n - localhost (when no parameters are given), or" + + "\n - the given hostname and ip" + ) + public String[] ssl; + + // offline block management + @Option( + names = {"pb", "--prune-blocks"}, + description = "remove blocks on side chains and update block info" + ) + public boolean rebuildBlockInfo = false; + + @Option( + names = {"-r", "--revert"}, + arity = "1", + paramLabel = "", + description = "revert database state to given block number" + ) + private long revertToBlock; + + // network and directory setup + @Option( + names = {"-n", "--network"}, + description = "execute kernel with selected network\noptions: mainnet, conquest" + ) + public String network; + + @Option( + names = {"-d", "--datadir"}, + description = "execute kernel with selected database directory" + ) + public String directory; + + // offline database query and update + @Option( + names = {"--state"}, + paramLabel = "", + description = "reorganize the state storage\noptions: FULL, TOP, SPREAD" + ) + public String stateOption; + + // print info from db + @Option( + names = {"--dump-blocks"}, + arity = "1", + paramLabel = "", + description = "print top blocks from database" + ) + public int dumpBlocksCount = 10; + + @Option( + names = {"--dump-state-size"}, + paramLabel = "", + description = "retrieves the state size (node count) for the top blocks" + ) + public int dumpStateSizeCount = 2; + + @Option( + names = {"--dump-state"}, + paramLabel = "", + description = "retrieves the state for the top main chain blocks" + ) + public int dumpStateCount = 2; + + @Option( + names = {"--db-compact"}, + description = "if using leveldb, it triggers its database compaction processes" + ) + public boolean dbCompact; + + /** Compacts the account options into specific commands. */ + public static String[] preprocess(String[] arguments) { + List list = new ArrayList<>(); + + int i = 0; + while (i < arguments.length) { + if (arguments[i].equals("-a") + || arguments[i].equals("--account") + || arguments[i].equals("-s")) { + list.add(arguments[i] + " " + arguments[i + 1]); + i++; + } else { + list.add(arguments[i]); + } + i++; + } + + return list.toArray(new String[list.size()]); + } + + public static void main(String... args) { + Arguments params = new Arguments(); + String[] argv = {"-a create", "-a list", "-a export", "0x123"}; + + CommandLine commandLine = new CommandLine(params); + ColorScheme colorScheme = + new ColorScheme() + .commands(Style.bold, Style.underline) // combine multiple styles + .options(Style.fg_yellow) // yellow foreground color + .parameters(Style.fg_yellow) + .optionParams(Style.italic); + commandLine.usage(System.out, colorScheme); + // commandLine.usage(System.out); + + commandLine.parse(argv); + System.out.println(params.createAccount); + System.out.println(params.listAccounts); + System.out.println(params.exportAccount.toString()); + } +} diff --git a/modAionImpl/test/org/aion/zero/impl/cli/ArgumentsTest.java b/modAionImpl/test/org/aion/zero/impl/cli/ArgumentsTest.java new file mode 100644 index 0000000000..787d9e01f2 --- /dev/null +++ b/modAionImpl/test/org/aion/zero/impl/cli/ArgumentsTest.java @@ -0,0 +1,181 @@ +/* + * Copyright (c) 2017-2018 Aion foundation. + * + * This file is part of the aion network project. + * + * The aion network project is free software: you can redistribute it + * and/or modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or any later version. + * + * The aion network project is distributed in the hope that it will + * be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with the aion network project source files. + * If not, see . + * + * Contributors: + * Aion foundation. + */ +package org.aion.zero.impl.cli; + +import static com.google.common.truth.Truth.assertThat; + +import org.junit.Test; + +/** @author Alexandra Roatis */ +public class ArgumentsTest { + + @Test + public void testPreprocess_wCreate() { + // first parameter + String[] input = + new String[] { + "-a", "create", "-n", "mainnet", "-d", "abc", + }; + String[] expected = + new String[] { + "-a create", "-n", "mainnet", "-d", "abc", + }; + + String[] actual = Arguments.preprocess(input); + assertThat(actual).isEqualTo(expected); + + // middle parameter + input = new String[] {"-d", "abc", "-a", "create", "-n", "mainnet"}; + expected = new String[] {"-d", "abc", "-a create", "-n", "mainnet"}; + + actual = Arguments.preprocess(input); + assertThat(actual).isEqualTo(expected); + + // last parameter + input = new String[] {"-n", "mainnet", "-d", "abc", "-a", "create"}; + expected = new String[] {"-n", "mainnet", "-d", "abc", "-a create"}; + + actual = Arguments.preprocess(input); + assertThat(actual).isEqualTo(expected); + } + + @Test + public void testPreprocess_wImport() { + // first parameter + String[] input = + new String[] { + "-a", "import", "0x123", "-n", "mainnet", "-d", "abc", + }; + String[] expected = + new String[] { + "-a import", "0x123", "-n", "mainnet", "-d", "abc", + }; + + String[] actual = Arguments.preprocess(input); + assertThat(actual).isEqualTo(expected); + + // middle parameter + input = new String[] {"-d", "abc", "-a", "import", "0x123", "-n", "mainnet"}; + expected = new String[] {"-d", "abc", "-a import", "0x123", "-n", "mainnet"}; + + actual = Arguments.preprocess(input); + assertThat(actual).isEqualTo(expected); + + // last parameter + input = new String[] {"-n", "mainnet", "-d", "abc", "-a", "import", "0x123"}; + expected = new String[] {"-n", "mainnet", "-d", "abc", "-a import", "0x123"}; + + actual = Arguments.preprocess(input); + assertThat(actual).isEqualTo(expected); + } + + @Test + public void testPreprocess_wExport() { + // first parameter + String[] input = + new String[] { + "-a", "export", "0x123", "-n", "mainnet", "-d", "abc", + }; + String[] expected = + new String[] { + "-a export", "0x123", "-n", "mainnet", "-d", "abc", + }; + + String[] actual = Arguments.preprocess(input); + assertThat(actual).isEqualTo(expected); + + // middle parameter + input = new String[] {"-d", "abc", "-a", "export", "0x123", "-n", "mainnet"}; + expected = new String[] {"-d", "abc", "-a export", "0x123", "-n", "mainnet"}; + + actual = Arguments.preprocess(input); + assertThat(actual).isEqualTo(expected); + + // last parameter + input = new String[] {"-n", "mainnet", "-d", "abc", "-a", "export", "0x123"}; + expected = new String[] {"-n", "mainnet", "-d", "abc", "-a export", "0x123"}; + + actual = Arguments.preprocess(input); + assertThat(actual).isEqualTo(expected); + } + + @Test + public void testPreprocess_wList() { + // first parameter + String[] input = + new String[] { + "-a", "list", "-n", "mainnet", "-d", "abc", + }; + String[] expected = + new String[] { + "-a list", "-n", "mainnet", "-d", "abc", + }; + + String[] actual = Arguments.preprocess(input); + assertThat(actual).isEqualTo(expected); + + // middle parameter + input = new String[] {"-d", "abc", "-a", "list", "-n", "mainnet"}; + expected = new String[] {"-d", "abc", "-a list", "-n", "mainnet"}; + + actual = Arguments.preprocess(input); + assertThat(actual).isEqualTo(expected); + + // last parameter + input = new String[] {"-n", "mainnet", "-d", "abc", "-a", "list"}; + expected = new String[] {"-n", "mainnet", "-d", "abc", "-a list"}; + + actual = Arguments.preprocess(input); + assertThat(actual).isEqualTo(expected); + } + + @Test + public void testPreprocess_wSsl() { + // first parameter + String[] input = + new String[] { + "-s", "create", "-n", "mainnet", "-d", "abc", + }; + String[] expected = + new String[] { + "-s create", "-n", "mainnet", "-d", "abc", + }; + + String[] actual = Arguments.preprocess(input); + assertThat(actual).isEqualTo(expected); + + // middle parameter + input = new String[] {"-d", "abc", "-s", "create", "-n", "mainnet"}; + expected = new String[] {"-d", "abc", "-s create", "-n", "mainnet"}; + + actual = Arguments.preprocess(input); + assertThat(actual).isEqualTo(expected); + + // last parameter + input = new String[] {"-n", "mainnet", "-d", "abc", "-s", "create"}; + expected = new String[] {"-n", "mainnet", "-d", "abc", "-s create"}; + + actual = Arguments.preprocess(input); + assertThat(actual).isEqualTo(expected); + } +} From 9fa05ce77a964a511dd46dad2f2de2481cce8348 Mon Sep 17 00:00:00 2001 From: Alexandra Roatis Date: Wed, 29 Aug 2018 12:41:14 -0400 Subject: [PATCH 02/31] printing help using the command line parser usage functionality --- .../src/org/aion/zero/impl/cli/Arguments.java | 6 +- .../src/org/aion/zero/impl/cli/Cli.java | 65 ++++++++++--------- 2 files changed, 38 insertions(+), 33 deletions(-) diff --git a/modAionImpl/src/org/aion/zero/impl/cli/Arguments.java b/modAionImpl/src/org/aion/zero/impl/cli/Arguments.java index c3bb6e52a4..6a19bd4468 100644 --- a/modAionImpl/src/org/aion/zero/impl/cli/Arguments.java +++ b/modAionImpl/src/org/aion/zero/impl/cli/Arguments.java @@ -106,7 +106,7 @@ public class Arguments { arity = "0..2", paramLabel = " ", description = - "create an ssl certificate for:\n - localhost (when no parameters are given), or" + "create a ssl certificate for:\n - localhost (when no parameters are given), or" + "\n - the given hostname and ip" ) public String[] ssl; @@ -141,7 +141,7 @@ public class Arguments { // offline database query and update @Option( - names = {"--state"}, + names = {"ps", "--state"}, paramLabel = "", description = "reorganize the state storage\noptions: FULL, TOP, SPREAD" ) @@ -214,5 +214,7 @@ public static void main(String... args) { System.out.println(params.createAccount); System.out.println(params.listAccounts); System.out.println(params.exportAccount.toString()); + + System.out.println(commandLine.getUsageMessage(colorScheme)); } } diff --git a/modAionImpl/src/org/aion/zero/impl/cli/Cli.java b/modAionImpl/src/org/aion/zero/impl/cli/Cli.java index e9bc87d33f..0fea1d5e06 100644 --- a/modAionImpl/src/org/aion/zero/impl/cli/Cli.java +++ b/modAionImpl/src/org/aion/zero/impl/cli/Cli.java @@ -24,12 +24,6 @@ package org.aion.zero.impl.cli; -import java.io.BufferedReader; -import java.io.File; -import java.io.IOException; -import java.io.InputStreamReader; -import java.util.*; - import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; import com.google.common.io.Files; @@ -42,8 +36,13 @@ import org.aion.zero.impl.Version; import org.aion.zero.impl.config.CfgAion; import org.aion.zero.impl.db.RecoveryUtils; +import picocli.CommandLine; -import java.io.Console; +import java.io.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.UUID; /** * Command line interface. @@ -62,6 +61,9 @@ public class Cli { File keystoreDir = new File(System.getProperty("user.dir") + File.separator + CfgSsl.SSL_KEYSTORE_DIR); + private Arguments options = new Arguments(); + private CommandLine parser = new CommandLine(options); + enum Network { MAINNET, CONQUEST; @@ -82,8 +84,24 @@ public int call(final String[] args, Cfg cfg) { } public int call(final String[] args, Cfg cfg, String path) { + try { + // the preprocess method handles arguments that are separated by space + parser.parse(Arguments.preprocess(args)); + } catch (Exception e) { + System.out.println("Unable to parse the input arguments due to: "); + if (e.getMessage() != null) { + System.out.println(e.getMessage()); + } else { + e.printStackTrace(); + } + + System.out.println(); + printHelp(); + } + try { cfg.fromXML(); + switch (args[0].toLowerCase()) { case "-h": printHelp(); @@ -461,30 +479,15 @@ else if (isValid(args[3])) { * Print the CLI help info. */ private void printHelp() { - System.out.println("Usage: ./aion.sh [options] [arguments]"); - System.out.println(); - System.out.println(" -h show help info"); - System.out.println(); - System.out.println(" -a create create a new account"); - System.out.println(" -a list list all existing accounts"); - System.out.println(" -a export [address] export private key of an account"); - System.out.println(" -a import [private_key] import private key"); - System.out.println(); - System.out.println(" -c [network] create config to selected network; mainnet, conquest"); - System.out.println(); - System.out.println(" -n, --network [network] execute kernel with selected network; mainnet, conquest"); - System.out.println(); - System.out.println(" -d, --datadir [directory] execute kernel with selected database directory"); - System.out.println(); - System.out.println(" -i show information"); - System.out.println(); - System.out.println(" -s create create an ssl certificate for localhost"); - System.out.println(" -s create [[hostname] [ip]] create an ssl certificate for a custom hostname and ip"); - System.out.println(); - System.out.println(" -r remove blocks on side chains and correct block info"); - System.out.println(" -r [block_number] revert db up to specific block number"); - System.out.println(); - System.out.println(" -v show version"); + String usage = parser.getUsageMessage(); + + usage = usage.replaceFirst("OPTIONS]", "OPTIONS] [ARGUMENTS]"); + + // the command line output has some styling characters in addition to the actual string + // making the use of a regular expression necessary here + usage = usage.replaceFirst(" \\[[^ ]* .*]", "]"); + + System.out.println(usage.toString()); } /** From 79834e58bc77e5018b7bfd468699dd431593e5c7 Mon Sep 17 00:00:00 2001 From: Alexandra Roatis Date: Wed, 29 Aug 2018 15:09:26 -0400 Subject: [PATCH 03/31] used new parser for help, info and version --- .../src/org/aion/zero/impl/cli/Arguments.java | 76 ++++ .../src/org/aion/zero/impl/cli/Cli.java | 81 +++-- modAionImpl/test/org/aion/cli/CliTest.java | 325 +++++++++--------- 3 files changed, 295 insertions(+), 187 deletions(-) diff --git a/modAionImpl/src/org/aion/zero/impl/cli/Arguments.java b/modAionImpl/src/org/aion/zero/impl/cli/Arguments.java index 6a19bd4468..3ae4cb9055 100644 --- a/modAionImpl/src/org/aion/zero/impl/cli/Arguments.java +++ b/modAionImpl/src/org/aion/zero/impl/cli/Arguments.java @@ -196,6 +196,82 @@ public static String[] preprocess(String[] arguments) { return list.toArray(new String[list.size()]); } + public boolean isHelp() { + return help; + } + + public boolean isCreateAccount() { + return createAccount; + } + + public boolean isListAccounts() { + return listAccounts; + } + + public String getExportAccount() { + return exportAccount; + } + + public String getImportAccount() { + return importAccount; + } + + public String getConfig() { + return config; + } + + public boolean isInfo() { + return info; + } + + public boolean isVersion() { + return version; + } + + public boolean isVersionTag() { + return versionTag; + } + + public String[] getSsl() { + return ssl; + } + + public boolean isRebuildBlockInfo() { + return rebuildBlockInfo; + } + + public long getRevertToBlock() { + return revertToBlock; + } + + public String getNetwork() { + return network; + } + + public String getDirectory() { + return directory; + } + + public String getStateOption() { + return stateOption; + } + + public int getDumpBlocksCount() { + return dumpBlocksCount; + } + + public int getDumpStateSizeCount() { + return dumpStateSizeCount; + } + + public int getDumpStateCount() { + return dumpStateCount; + } + + public boolean isDbCompact() { + return dbCompact; + } + public static void main(String... args) { Arguments params = new Arguments(); String[] argv = {"-a create", "-a list", "-a export", "0x123"}; diff --git a/modAionImpl/src/org/aion/zero/impl/cli/Cli.java b/modAionImpl/src/org/aion/zero/impl/cli/Cli.java index 0fea1d5e06..dcfcbfe6b2 100644 --- a/modAionImpl/src/org/aion/zero/impl/cli/Cli.java +++ b/modAionImpl/src/org/aion/zero/impl/cli/Cli.java @@ -86,6 +86,7 @@ public int call(final String[] args, Cfg cfg) { public int call(final String[] args, Cfg cfg, String path) { try { // the preprocess method handles arguments that are separated by space + // parsing populates the options object parser.parse(Arguments.preprocess(args)); } catch (Exception e) { System.out.println("Unable to parse the input arguments due to: "); @@ -100,12 +101,31 @@ public int call(final String[] args, Cfg cfg, String path) { } try { + // the first set of options don't mix + if (options.isHelp()) { + printHelp(); + return 0; + } + + if (options.isVersion() || options.isVersionTag()) { + if (options.isVersion()) { + System.out.println("\nVersion"); + System.out.println("--------------------------------------------"); + } + System.out.println(Version.KERNEL_VERSION); + return 0; + } + + if (options.isInfo()) { + cfg.fromXML(); + printInfo(cfg); + return 0; + } + + // the following options can be combined cfg.fromXML(); switch (args[0].toLowerCase()) { - case "-h": - printHelp(); - break; case "-a": int index = 0; @@ -191,26 +211,6 @@ public int call(final String[] args, Cfg cfg, String path) { cfg.setId(UUID.randomUUID().toString()); cfg.toXML(null); break; - case "-i": - cfg.fromXML(); - System.out.println("\nInformation"); - System.out.println("--------------------------------------------"); - System.out.println( - "current: p2p://" + cfg.getId() + "@" + cfg.getNet().getP2p().getIp() + ":" - + cfg.getNet().getP2p().getPort()); - String[] nodes = cfg.getNet().getNodes(); - if (nodes != null && nodes.length > 0) { - System.out.println("boot nodes list:"); - for (String node : nodes) { - System.out.println(" " + node); - } - } else { - System.out.println("boot nodes list: 0"); - } - System.out.println( - "p2p: " + cfg.getNet().getP2p().getIp() + ":" + cfg.getNet().getP2p() - .getPort()); - break; case "-s": if ((args.length == 2 || args.length == 4) && (args[1].equals("create"))) { createKeystoreDirIfMissing(); @@ -453,13 +453,6 @@ else if (isValid(args[3])) { RecoveryUtils.dumpBlocks(count); } break; - case "-v": - System.out.println("\nVersion"); - System.out.println("--------------------------------------------"); - // Don't put break here!! - case "--version": - System.out.println(Version.KERNEL_VERSION); - break; default: System.out.println("Unable to parse the input arguments"); printHelp(); @@ -475,9 +468,7 @@ else if (isValid(args[3])) { return 0; } - /** - * Print the CLI help info. - */ + /** Print the CLI help info. */ private void printHelp() { String usage = parser.getUsageMessage(); @@ -490,6 +481,30 @@ private void printHelp() { System.out.println(usage.toString()); } + private void printInfo(Cfg cfg) { + System.out.println("\nInformation"); + System.out.println( + "----------------------------------------------------------------------------"); + System.out.println( + "current: p2p://" + + cfg.getId() + + "@" + + cfg.getNet().getP2p().getIp() + + ":" + + cfg.getNet().getP2p().getPort()); + String[] nodes = cfg.getNet().getNodes(); + if (nodes != null && nodes.length > 0) { + System.out.println("boot nodes list:"); + for (String node : nodes) { + System.out.println(" " + node); + } + } else { + System.out.println("boot nodes list is empty"); + } + System.out.println( + "p2p: " + cfg.getNet().getP2p().getIp() + ":" + cfg.getNet().getP2p().getPort()); + } + /** * Determines the correct network (mainnet / conquest) enum based on argument * diff --git a/modAionImpl/test/org/aion/cli/CliTest.java b/modAionImpl/test/org/aion/cli/CliTest.java index 7da9a73294..0f854ca89e 100644 --- a/modAionImpl/test/org/aion/cli/CliTest.java +++ b/modAionImpl/test/org/aion/cli/CliTest.java @@ -1,31 +1,30 @@ package org.aion.cli; +import static org.junit.Assert.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doReturn; + import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; import com.google.common.io.Files; -import org.aion.mcf.account.Keystore; +import java.io.File; +import java.io.IOException; +import java.nio.file.FileVisitResult; +import java.nio.file.Path; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributes; import org.aion.base.util.Hex; import org.aion.crypto.ECKey; import org.aion.crypto.ECKeyFac; +import org.aion.mcf.account.Keystore; import org.aion.mcf.config.Cfg; +import org.aion.zero.impl.cli.Cli; import org.aion.zero.impl.config.CfgAion; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.mockito.Mockito; -import static org.junit.Assert.assertEquals; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.doReturn; -import org.aion.zero.impl.cli.Cli; - -import java.io.File; -import java.io.IOException; -import java.nio.file.FileVisitResult; -import java.nio.file.Path; -import java.nio.file.SimpleFileVisitor; -import java.nio.file.attribute.BasicFileAttributes; - public class CliTest { private static final Cli cli = Mockito.spy(new Cli()); @@ -35,8 +34,8 @@ public class CliTest { String BASE_PATH = cfg.getBasePath(); /** - * Sets up a spy Cli class that returns the String "password" when the cli.readPassword() - * is called using any two params. + * Sets up a spy Cli class that returns the String "password" when the cli.readPassword() is + * called using any two params. */ @Before public void setup() throws IOException { @@ -60,7 +59,11 @@ public void shutdown() { File path3 = new File(BASE_PATH + "/abcccccc"); File path4 = new File(BASE_PATH + "/keystore"); File path5 = new File(BASE_PATH + "/config"); - if(path1.exists() || path2.exists() || path3.exists() || path4.exists() || path5.exists()) { + if (path1.exists() + || path2.exists() + || path3.exists() + || path4.exists() + || path5.exists()) { deleteRecursively(path1); deleteRecursively(path2); deleteRecursively(path3); @@ -73,36 +76,49 @@ public void shutdown() { Keystore.setKeystorePath(BASE_PATH + "/keystore"); } - /** - * Tests the -h argument does not fail. - */ + /** Ensures that the -h argument does not fail. */ @Test public void testHelp() { String args[] = {"-h"}; assertEquals(0, cli.call(args, CfgAion.inst())); } - /** - * Tests the -a create arguments do not fail. - */ + /** Ensures that the -v argument does not fail. */ + @Test + public void testVersion() { + String args[] = {"-v"}; + assertEquals(0, cli.call(args, CfgAion.inst())); + } + + /** Ensures that the --version argument does not fail. */ + @Test + public void testVersionTag() { + String args[] = {"--version"}; + assertEquals(0, cli.call(args, CfgAion.inst())); + } + + /** Ensures that the -i and --info arguments do not fail. */ + @Test + public void testInfo() { + assertEquals(0, cli.call(new String[] {"-i"}, CfgAion.inst())); + assertEquals(0, cli.call(new String[] {"--info"}, CfgAion.inst())); + } + + /** Tests the -a create arguments do not fail. */ @Test public void testCreateAccount() { String args[] = {"-a", "create"}; assertEquals(0, cli.call(args, CfgAion.inst())); } - /** - * Tests the -a list arguments do not fail. - */ + /** Tests the -a list arguments do not fail. */ @Test public void testListAccounts() { String args[] = {"-a", "list"}; assertEquals(0, cli.call(args, CfgAion.inst())); } - /** - * Tests the -a export arguments do not fail on a valid account. - */ + /** Tests the -a export arguments do not fail on a valid account. */ @Test public void testExportPrivateKey() { String account = Keystore.create("password"); @@ -124,9 +140,7 @@ public void testExportSubstringOfAccount() { assertEquals(1, cli.call(args, CfgAion.inst())); } - /** - * Tests the -a import arguments do not fail on a fail import key. - */ + /** Tests the -a import arguments do not fail on a fail import key. */ @Test public void testImportPrivateKey() { ECKey key = ECKeyFac.inst().create(); @@ -135,9 +149,7 @@ public void testImportPrivateKey() { assertEquals(0, cli.call(args, CfgAion.inst())); } - /** - * Tests the -a import arguments fail when a non-private key is supplied. - */ + /** Tests the -a import arguments fail when a non-private key is supplied. */ @Test public void testImportNonPrivateKey() { String account = Keystore.create("password"); @@ -146,9 +158,7 @@ public void testImportNonPrivateKey() { assertEquals(1, cli.call(args, CfgAion.inst())); } - /** - * Tests the -a import arguments do not fail when a valid private key is supplied. - */ + /** Tests the -a import arguments do not fail when a valid private key is supplied. */ @Test public void testImportPrivateKey2() { ECKey key = ECKeyFac.inst().create(); @@ -165,9 +175,7 @@ public void testImportPrivateKey2() { System.out.println("Imported private key: " + Hex.toHexString(key2.getPrivKeyBytes())); } - /** - * Tests the -a import arguments fail given an invalid private key. - */ + /** Tests the -a import arguments fail given an invalid private key. */ @Test public void testImportPrivateKeyWrong() { String[] args = {"-a", "import", "hello"}; @@ -175,20 +183,20 @@ public void testImportPrivateKeyWrong() { } /** - * Test the -d | --datadir option to see if; - * 1. Access the correct dbPath - * 2. Defaults correctly to "database" + * Test the -d | --datadir option to see if; 1. Access the correct dbPath 2. Defaults correctly + * to "database" */ @Test public void testDatadir() { - final String[][] networkArgs = new String[][] { - { "-d" , "" }, // Unspecified - { "-d" , "{@.@}" }, // Invalid (illegal characters) - { "-d" , "test_db" , "test"}, // Invalid (number of arguments) - { "-d" , "aaaaaaaa" }, // Valid - { "-d" , "abbbbbbb" }, // Valid - }; + final String[][] networkArgs = + new String[][] { + {"-d", ""}, // Unspecified + {"-d", "{@.@}"}, // Invalid (illegal characters) + {"-d", "test_db", "test"}, // Invalid (number of arguments) + {"-d", "aaaaaaaa"}, // Valid + {"-d", "abbbbbbb"}, // Valid + }; Cli cli = new Cli(); @@ -206,90 +214,88 @@ public void testDatadir() { System.out.println(cfg.getLog().getLogPath() + " " + cfg.getDb().getPath()); System.out.println("\nValid Datadir 1: " + networkArgs[3][1]); - assertEquals(2, cli.call(networkArgs[3], cfg) ); - assertEquals("aaaaaaaa/mainnet/database", cfg.getDb().getPath() ); - assertEquals("aaaaaaaa/mainnet/log", cfg.getLog().getLogPath() ); + assertEquals(2, cli.call(networkArgs[3], cfg)); + assertEquals("aaaaaaaa/mainnet/database", cfg.getDb().getPath()); + assertEquals("aaaaaaaa/mainnet/log", cfg.getLog().getLogPath()); assertEquals(BASE_PATH + "/aaaaaaaa/mainnet/keystore", Keystore.getKeystorePath()); printPaths(BASE_PATH, cfg, cli); System.out.println("\nValid Datadir 2: " + networkArgs[4][1]); - assertEquals(2, cli.call(networkArgs[4], cfg) ); - assertEquals("abbbbbbb/mainnet/database", cfg.getDb().getPath() ); - assertEquals("abbbbbbb/mainnet/log", cfg.getLog().getLogPath() ); + assertEquals(2, cli.call(networkArgs[4], cfg)); + assertEquals("abbbbbbb/mainnet/database", cfg.getDb().getPath()); + assertEquals("abbbbbbb/mainnet/log", cfg.getLog().getLogPath()); assertEquals(BASE_PATH + "/abbbbbbb/mainnet/keystore", Keystore.getKeystorePath()); printPaths(BASE_PATH, cfg, cli); - } /** - * Test the -n | --network option to see if; - * 1. Access the correct CONF_FILE_PATH - * 2. Access the correct GENESIS_FILE_PATH - * 3. Defaults correctly to "mainnet" + * Test the -n | --network option to see if; 1. Access the correct CONF_FILE_PATH 2. Access the + * correct GENESIS_FILE_PATH 3. Defaults correctly to "mainnet" */ @Test public void testNetwork() { - final String[][] networkArgs = new String[][] { - { "-n" , "" }, // Unspecified - { "-n" , "{@.@}" }, // Invalid (illegal characters) - { "-n" , "testnet", "test" }, // Invalid (number of arguments) - { "-n" , "mainnet" }, // Mainnet - { "-n" , "testnet" }, // Testnet - { "-n" , "conquest" }, // Conquest - }; + final String[][] networkArgs = + new String[][] { + {"-n", ""}, // Unspecified + {"-n", "{@.@}"}, // Invalid (illegal characters) + {"-n", "testnet", "test"}, // Invalid (number of arguments) + {"-n", "mainnet"}, // Mainnet + {"-n", "testnet"}, // Testnet + {"-n", "conquest"}, // Conquest + }; Cli cli = new Cli(); System.out.println("Invalid Networks:"); - assertEquals(1, cli.call(networkArgs[0], cfg) ); - assertEquals(1, cli.call(networkArgs[1], cfg) ); - assertEquals(1, cli.call(networkArgs[2], cfg) ); + assertEquals(1, cli.call(networkArgs[0], cfg)); + assertEquals(1, cli.call(networkArgs[1], cfg)); + assertEquals(1, cli.call(networkArgs[2], cfg)); System.out.println("\nValid Network 1: " + networkArgs[3][1]); - assertEquals(2, cli.call(networkArgs[3], cfg) ); - assertEquals("mainnet", CfgAion.getNetwork() ); - assertEquals(BASE_PATH + "/config/mainnet/config.xml", CfgAion.getConfFilePath() ); - assertEquals(BASE_PATH + "/config/mainnet/genesis.json", CfgAion.getGenesisFilePath() ); + assertEquals(2, cli.call(networkArgs[3], cfg)); + assertEquals("mainnet", CfgAion.getNetwork()); + assertEquals(BASE_PATH + "/config/mainnet/config.xml", CfgAion.getConfFilePath()); + assertEquals(BASE_PATH + "/config/mainnet/genesis.json", CfgAion.getGenesisFilePath()); printPaths(BASE_PATH, cfg, cli); System.out.println("\nValid Network 2: " + networkArgs[4][1]); - assertEquals(2, cli.call(networkArgs[4], cfg) ); - assertEquals("conquest", CfgAion.getNetwork() ); - assertEquals(BASE_PATH + "/config/conquest/config.xml", CfgAion.getConfFilePath() ); - assertEquals(BASE_PATH + "/config/conquest/genesis.json", CfgAion.getGenesisFilePath() ); + assertEquals(2, cli.call(networkArgs[4], cfg)); + assertEquals("conquest", CfgAion.getNetwork()); + assertEquals(BASE_PATH + "/config/conquest/config.xml", CfgAion.getConfFilePath()); + assertEquals(BASE_PATH + "/config/conquest/genesis.json", CfgAion.getGenesisFilePath()); printPaths(BASE_PATH, cfg, cli); System.out.println("\nValid Network 3: " + networkArgs[5][1]); - assertEquals(2, cli.call(networkArgs[5], cfg) ); - assertEquals("conquest", CfgAion.getNetwork() ); - assertEquals(BASE_PATH + "/config/conquest/config.xml", CfgAion.getConfFilePath() ); - assertEquals(BASE_PATH + "/config/conquest/genesis.json", CfgAion.getGenesisFilePath() ); + assertEquals(2, cli.call(networkArgs[5], cfg)); + assertEquals("conquest", CfgAion.getNetwork()); + assertEquals(BASE_PATH + "/config/conquest/config.xml", CfgAion.getConfFilePath()); + assertEquals(BASE_PATH + "/config/conquest/genesis.json", CfgAion.getGenesisFilePath()); printPaths(BASE_PATH, cfg, cli); - } @Test public void testMultiNetworkDatadir() { - final String[][] multiArgs = new String[][] { - { "-d" , "aaaaaaaa" , "-n" , "mainnet" }, // New network - { "-d" , "abbbbbbb" , "-n" , "conquest" }, // New dir - { "-n" , "conquest" , "-d" , "abcccccc" }, // New dir - { "-d" , "aaaaaaaa" , "-n" , "conquest" }, // Existing dir / network - { "-n" , "conquest" , "-d" , "aaaaaaaa" }, // Exisitng network / dir - { "-d" , "{@.@}" , "-n" , "conquest" }, // Invalid dir - { "-n" , "conquest" , "-d" , "{@.@}" }, // Invalid dir - { "-d" , "aaaaaaaa" , "-n" , "{@.@}" }, // Invalid network - { "-n" , "{@.@}" , "-d" , "aaaaaaaa" }, // Invalid network - }; + final String[][] multiArgs = + new String[][] { + {"-d", "aaaaaaaa", "-n", "mainnet"}, // New network + {"-d", "abbbbbbb", "-n", "conquest"}, // New dir + {"-n", "conquest", "-d", "abcccccc"}, // New dir + {"-d", "aaaaaaaa", "-n", "conquest"}, // Existing dir / network + {"-n", "conquest", "-d", "aaaaaaaa"}, // Exisitng network / dir + {"-d", "{@.@}", "-n", "conquest"}, // Invalid dir + {"-n", "conquest", "-d", "{@.@}"}, // Invalid dir + {"-d", "aaaaaaaa", "-n", "{@.@}"}, // Invalid network + {"-n", "{@.@}", "-d", "aaaaaaaa"}, // Invalid network + }; Cli cli = new Cli(); Cfg cfg = CfgAion.inst(); System.out.println("\nNew 1: " + multiArgs[0][1] + " " + multiArgs[0][3]); assertEquals(2, cli.call(multiArgs[0], cfg)); - assertEquals("mainnet", CfgAion.getNetwork() ); + assertEquals("mainnet", CfgAion.getNetwork()); assertEquals("aaaaaaaa/mainnet/log", cfg.getLog().getLogPath()); assertEquals("aaaaaaaa/mainnet/database", cfg.getDb().getPath()); assertEquals(BASE_PATH + "/aaaaaaaa/mainnet/keystore", Keystore.getKeystorePath()); @@ -297,7 +303,7 @@ public void testMultiNetworkDatadir() { System.out.println("\nNew 2: " + multiArgs[1][1] + " " + multiArgs[1][3]); assertEquals(2, cli.call(multiArgs[1], cfg)); - assertEquals("conquest", CfgAion.getNetwork() ); + assertEquals("conquest", CfgAion.getNetwork()); assertEquals("abbbbbbb/conquest/log", cfg.getLog().getLogPath()); assertEquals("abbbbbbb/conquest/database", cfg.getDb().getPath()); assertEquals(BASE_PATH + "/abbbbbbb/conquest/keystore", Keystore.getKeystorePath()); @@ -305,7 +311,7 @@ public void testMultiNetworkDatadir() { System.out.println("\nNew 3: " + multiArgs[2][1] + " " + multiArgs[2][3]); assertEquals(2, cli.call(multiArgs[2], cfg)); - assertEquals("conquest", CfgAion.getNetwork() ); + assertEquals("conquest", CfgAion.getNetwork()); assertEquals("abcccccc/conquest/log", cfg.getLog().getLogPath()); assertEquals("abcccccc/conquest/database", cfg.getDb().getPath()); assertEquals(BASE_PATH + "/abcccccc/conquest/keystore", Keystore.getKeystorePath()); @@ -313,7 +319,7 @@ public void testMultiNetworkDatadir() { System.out.println("\n Exist 1: " + multiArgs[3][1] + " " + multiArgs[3][3]); assertEquals(2, cli.call(multiArgs[3], cfg)); - assertEquals("conquest", CfgAion.getNetwork() ); + assertEquals("conquest", CfgAion.getNetwork()); assertEquals("aaaaaaaa/conquest/log", cfg.getLog().getLogPath()); assertEquals("aaaaaaaa/conquest/database", cfg.getDb().getPath()); assertEquals(BASE_PATH + "/aaaaaaaa/conquest/keystore", Keystore.getKeystorePath()); @@ -321,7 +327,7 @@ public void testMultiNetworkDatadir() { System.out.println("\nExist 2: " + multiArgs[4][1] + " " + multiArgs[4][3]); assertEquals(2, cli.call(multiArgs[4], cfg)); - assertEquals("conquest", CfgAion.getNetwork() ); + assertEquals("conquest", CfgAion.getNetwork()); assertEquals("aaaaaaaa/conquest/log", cfg.getLog().getLogPath()); assertEquals("aaaaaaaa/conquest/database", cfg.getDb().getPath()); assertEquals(BASE_PATH + "/aaaaaaaa/conquest/keystore", Keystore.getKeystorePath()); @@ -335,37 +341,45 @@ public void testMultiNetworkDatadir() { } private void printPaths(String BASE_PATH, Cfg cfg, Cli cli) { - System.out.println("\n-------------------------------- USED PATHS --------------------------------" + - "\n> Logger path: " + BASE_PATH + "/" + cfg.getLog().getLogPath() + - "\n> Database path: " + BASE_PATH + "/" + cfg.getDb().getPath() + - "\n> Keystore path: " + Keystore.getKeystorePath() + - "\n> Config write: " + cli.getDstConfig() + - "\n> Genesis write: " + cli.getDstGenesis() + - "\n----------------------------------------------------------------------------" + - "\n> Config read: " + CfgAion.getConfFilePath() + - "\n> Genesis read: " + CfgAion.getGenesisFilePath() + - "\n----------------------------------------------------------------------------\n\n"); + System.out.println( + "\n-------------------------------- USED PATHS --------------------------------" + + "\n> Logger path: " + + BASE_PATH + + "/" + + cfg.getLog().getLogPath() + + "\n> Database path: " + + BASE_PATH + + "/" + + cfg.getDb().getPath() + + "\n> Keystore path: " + + Keystore.getKeystorePath() + + "\n> Config write: " + + cli.getDstConfig() + + "\n> Genesis write: " + + cli.getDstGenesis() + + "\n----------------------------------------------------------------------------" + + "\n> Config read: " + + CfgAion.getConfFilePath() + + "\n> Genesis read: " + + CfgAion.getGenesisFilePath() + + "\n----------------------------------------------------------------------------\n\n"); } // Methods below taken from FileUtils class - private static boolean copyRecursively(File src, File target) - { + private static boolean copyRecursively(File src, File target) { if (src.isDirectory()) { return copyDirectoryContents(src, target); - } - else { + } else { try { Files.copy(src, target); return true; - } - catch (IOException e) { + } catch (IOException e) { return false; } } } - private static boolean copyDirectoryContents(File src, File target) - { + private static boolean copyDirectoryContents(File src, File target) { Preconditions.checkArgument(src.isDirectory(), "Source dir is not a directory: %s", src); // Don't delete symbolic link directories @@ -383,26 +397,25 @@ private static boolean copyDirectoryContents(File src, File target) return success; } - private static boolean isSymbolicLink(File file) - { + private static boolean isSymbolicLink(File file) { try { File canonicalFile = file.getCanonicalFile(); File absoluteFile = file.getAbsoluteFile(); File parentFile = file.getParentFile(); // a symbolic link has a different name between the canonical and absolute path - return !canonicalFile.getName().equals(absoluteFile.getName()) || + return !canonicalFile.getName().equals(absoluteFile.getName()) + || // or the canonical parent path is not the same as the file's parent path, // provided the file has a parent path - parentFile != null && !parentFile.getCanonicalPath().equals(canonicalFile.getParent()); - } - catch (IOException e) { + parentFile != null + && !parentFile.getCanonicalPath().equals(canonicalFile.getParent()); + } catch (IOException e) { // error on the side of caution return true; } } - private static ImmutableList listFiles(File dir) - { + private static ImmutableList listFiles(File dir) { File[] files = dir.listFiles(); if (files == null) { return ImmutableList.of(); @@ -413,31 +426,36 @@ private static ImmutableList listFiles(File dir) private static boolean deleteRecursively(File file) { Path path = file.toPath(); try { - java.nio.file.Files.walkFileTree(path, new SimpleFileVisitor() { - @Override - public FileVisitResult visitFile(final Path file, final BasicFileAttributes attrs) throws IOException { - java.nio.file.Files.delete(file); - return FileVisitResult.CONTINUE; - } - - @Override - public FileVisitResult visitFileFailed(final Path file, final IOException e) { - return handleException(e); - } - - private FileVisitResult handleException(final IOException e) { - // e.printStackTrace(); - return FileVisitResult.TERMINATE; - } - - @Override - public FileVisitResult postVisitDirectory(final Path dir, final IOException e) throws IOException { - if (e != null) - return handleException(e); - java.nio.file.Files.delete(dir); - return FileVisitResult.CONTINUE; - } - }); + java.nio.file.Files.walkFileTree( + path, + new SimpleFileVisitor() { + @Override + public FileVisitResult visitFile( + final Path file, final BasicFileAttributes attrs) + throws IOException { + java.nio.file.Files.delete(file); + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult visitFileFailed( + final Path file, final IOException e) { + return handleException(e); + } + + private FileVisitResult handleException(final IOException e) { + // e.printStackTrace(); + return FileVisitResult.TERMINATE; + } + + @Override + public FileVisitResult postVisitDirectory( + final Path dir, final IOException e) throws IOException { + if (e != null) return handleException(e); + java.nio.file.Files.delete(dir); + return FileVisitResult.CONTINUE; + } + }); } catch (IOException e) { e.printStackTrace(); return false; @@ -445,5 +463,4 @@ public FileVisitResult postVisitDirectory(final Path dir, final IOException e) t return true; } - -} \ No newline at end of file +} From de0b8fed04ef4c8b0065d10bee7eb2170c768197 Mon Sep 17 00:00:00 2001 From: Alexandra Roatis Date: Wed, 29 Aug 2018 15:40:22 -0400 Subject: [PATCH 04/31] moved the CliTest class to the correct folder --- modAionImpl/test/org/aion/{ => zero/impl}/cli/CliTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename modAionImpl/test/org/aion/{ => zero/impl}/cli/CliTest.java (99%) diff --git a/modAionImpl/test/org/aion/cli/CliTest.java b/modAionImpl/test/org/aion/zero/impl/cli/CliTest.java similarity index 99% rename from modAionImpl/test/org/aion/cli/CliTest.java rename to modAionImpl/test/org/aion/zero/impl/cli/CliTest.java index 0f854ca89e..effb59457a 100644 --- a/modAionImpl/test/org/aion/cli/CliTest.java +++ b/modAionImpl/test/org/aion/zero/impl/cli/CliTest.java @@ -1,4 +1,4 @@ -package org.aion.cli; +package org.aion.zero.impl.cli; import static org.junit.Assert.assertEquals; import static org.mockito.ArgumentMatchers.any; From 5457afc54c95d149367fa25bedacda77c2e6a047 Mon Sep 17 00:00:00 2001 From: Alexandra Roatis Date: Wed, 29 Aug 2018 17:41:29 -0400 Subject: [PATCH 05/31] config using the new parser --- .../src/org/aion/zero/impl/cli/Arguments.java | 1 + .../src/org/aion/zero/impl/cli/Cli.java | 76 ++++++++++--------- .../test/org/aion/zero/impl/cli/CliTest.java | 42 ++++++---- 3 files changed, 71 insertions(+), 48 deletions(-) diff --git a/modAionImpl/src/org/aion/zero/impl/cli/Arguments.java b/modAionImpl/src/org/aion/zero/impl/cli/Arguments.java index 3ae4cb9055..287b52924f 100644 --- a/modAionImpl/src/org/aion/zero/impl/cli/Arguments.java +++ b/modAionImpl/src/org/aion/zero/impl/cli/Arguments.java @@ -76,6 +76,7 @@ public class Arguments { // config generation @Option( names = {"-c", "--config"}, + arity = "0..1", paramLabel = "", description = "create config for the selected network\noptions: mainnet, conquest" ) diff --git a/modAionImpl/src/org/aion/zero/impl/cli/Cli.java b/modAionImpl/src/org/aion/zero/impl/cli/Cli.java index dcfcbfe6b2..123f18ef64 100644 --- a/modAionImpl/src/org/aion/zero/impl/cli/Cli.java +++ b/modAionImpl/src/org/aion/zero/impl/cli/Cli.java @@ -98,6 +98,7 @@ public int call(final String[] args, Cfg cfg, String path) { System.out.println(); printHelp(); + return 1; } try { @@ -122,6 +123,46 @@ public int call(final String[] args, Cfg cfg, String path) { return 0; } + if (options.getConfig() != null) { + String strNet = options.getConfig().toLowerCase(); + + if (strNet.isEmpty()) { + // no network give; use default path + // for compatibility with old kernels + CfgAion.setNetwork(strNet); + File dir = new File(BASE_PATH + "/config"); + if (!dir.exists()) { + dir.mkdirs(); + } + CfgAion.setConfFilePath(BASE_PATH + "/config/config.xml"); + System.out.println("\nNew config generated for default (release) network."); + } else { + net = determineNetwork(strNet); + + if (net != null) { + strNet = net.toString(); + CfgAion.setNetwork(strNet); + File dir = new File(BASE_PATH + "/config/" + net); + if (!dir.exists()) { + dir.mkdirs(); + } + CfgAion.setConfFilePath(BASE_PATH + "/config/" + strNet + "/config.xml"); + System.out.println("\nNew config generated for " + strNet + "."); + } else { + System.out.println("\nInvalid network selected!"); + System.out.println("--- Available Networks ---"); + System.out.println(" mainnet, conquest"); + System.out.println("--------------------------"); + return 1; + } + } + + cfg.fromXML(); + cfg.setId(UUID.randomUUID().toString()); + cfg.toXML(null); + return 0; + } + // the following options can be combined cfg.fromXML(); @@ -177,40 +218,6 @@ public int call(final String[] args, Cfg cfg, String path) { return 1; } break; - case "-c": - if (args.length == 2 && isValid(args[1])) { - - net = determineNetwork(args[1].toLowerCase()); - - switch (net) { - case MAINNET: - case CONQUEST: - CfgAion.setNetwork(net.toString()); - File dir = new File(BASE_PATH + "/config/" + net); - if(!dir.exists()) { - dir.mkdirs(); - } - CfgAion.setConfFilePath(BASE_PATH + "/config/" + args[1] + "/config.xml"); - System.out.println("\nNew config generated for " + args[1]); - break; - default: - System.out.println("\nInvalid network selected!"); - System.out.println("--- Available Networks ---"); - System.out.println(" mainnet, conquest"); - System.out.println("--------------------------"); - return 1; - } - } else if (args.length == 1) { - System.out.println("\nInvalid network selected!"); - System.out.println("--- Available Networks ---"); - System.out.println(" mainnet, conquest"); - System.out.println("--------------------------"); - return 1; - } - cfg.fromXML(); - cfg.setId(UUID.randomUUID().toString()); - cfg.toXML(null); - break; case "-s": if ((args.length == 2 || args.length == 4) && (args[1].equals("create"))) { createKeystoreDirIfMissing(); @@ -462,6 +469,7 @@ else if (isValid(args[3])) { System.out.println(""); } catch (Throwable e) { System.out.println(""); + e.printStackTrace(); return 1; } diff --git a/modAionImpl/test/org/aion/zero/impl/cli/CliTest.java b/modAionImpl/test/org/aion/zero/impl/cli/CliTest.java index effb59457a..27be96a0ab 100644 --- a/modAionImpl/test/org/aion/zero/impl/cli/CliTest.java +++ b/modAionImpl/test/org/aion/zero/impl/cli/CliTest.java @@ -18,7 +18,6 @@ import org.aion.crypto.ECKeyFac; import org.aion.mcf.account.Keystore; import org.aion.mcf.config.Cfg; -import org.aion.zero.impl.cli.Cli; import org.aion.zero.impl.config.CfgAion; import org.junit.After; import org.junit.Before; @@ -76,25 +75,18 @@ public void shutdown() { Keystore.setKeystorePath(BASE_PATH + "/keystore"); } - /** Ensures that the -h argument does not fail. */ + /** Ensures that the -h and --help arguments do not fail. */ @Test public void testHelp() { - String args[] = {"-h"}; - assertEquals(0, cli.call(args, CfgAion.inst())); + assertEquals(0, cli.call(new String[] {"-h"}, CfgAion.inst())); + assertEquals(0, cli.call(new String[] {"--help"}, CfgAion.inst())); } - /** Ensures that the -v argument does not fail. */ + /** Ensures that the -v and --version arguments do not fail. */ @Test public void testVersion() { - String args[] = {"-v"}; - assertEquals(0, cli.call(args, CfgAion.inst())); - } - - /** Ensures that the --version argument does not fail. */ - @Test - public void testVersionTag() { - String args[] = {"--version"}; - assertEquals(0, cli.call(args, CfgAion.inst())); + assertEquals(0, cli.call(new String[] {"-v"}, CfgAion.inst())); + assertEquals(0, cli.call(new String[] {"--version"}, CfgAion.inst())); } /** Ensures that the -i and --info arguments do not fail. */ @@ -104,6 +96,28 @@ public void testInfo() { assertEquals(0, cli.call(new String[] {"--info"}, CfgAion.inst())); } + /** Ensures correct behavior for the -c and --config arguments. */ + @Test + public void testConfig() { + // compatibility with old kernels + assertEquals(0, cli.call(new String[] {"-c"}, CfgAion.inst())); + assertEquals(0, cli.call(new String[] {"--config"}, CfgAion.inst())); + + // available networks + for (Cli.Network net : Cli.Network.values()) { + assertEquals(0, cli.call(new String[] {"-c", net.toString()}, CfgAion.inst())); + assertEquals(0, cli.call(new String[] {"--config", net.toString()}, CfgAion.inst())); + } + + // accepted alias + assertEquals(0, cli.call(new String[] {"-c", "testnet"}, CfgAion.inst())); + assertEquals(0, cli.call(new String[] {"--config", "testnet"}, CfgAion.inst())); + + // incorrect value + assertEquals(1, cli.call(new String[] {"-c", "random"}, CfgAion.inst())); + assertEquals(1, cli.call(new String[] {"--config", "random"}, CfgAion.inst())); + } + /** Tests the -a create arguments do not fail. */ @Test public void testCreateAccount() { From d71200d669f0a4771c5ecd12ac39fc2279c9a3cb Mon Sep 17 00:00:00 2001 From: Alexandra Roatis Date: Thu, 30 Aug 2018 11:00:07 -0400 Subject: [PATCH 06/31] enum used for Cli return types --- .../src/org/aion/zero/impl/cli/Cli.java | 81 +++++++++++------- .../test/org/aion/zero/impl/cli/CliTest.java | 85 ++++++++++--------- modBoot/src/org/aion/Aion.java | 7 +- 3 files changed, 96 insertions(+), 77 deletions(-) diff --git a/modAionImpl/src/org/aion/zero/impl/cli/Cli.java b/modAionImpl/src/org/aion/zero/impl/cli/Cli.java index 123f18ef64..d1ee8b0156 100644 --- a/modAionImpl/src/org/aion/zero/impl/cli/Cli.java +++ b/modAionImpl/src/org/aion/zero/impl/cli/Cli.java @@ -44,6 +44,10 @@ import java.util.List; import java.util.UUID; +import static org.aion.zero.impl.cli.Cli.ReturnType.ERROR; +import static org.aion.zero.impl.cli.Cli.ReturnType.EXIT; +import static org.aion.zero.impl.cli.Cli.ReturnType.RUN; + /** * Command line interface. * @@ -64,6 +68,19 @@ public class Cli { private Arguments options = new Arguments(); private CommandLine parser = new CommandLine(options); + public enum ReturnType { + RUN(2), EXIT(0), ERROR(1); + private int value; + + ReturnType(int _value) { + this.value = _value; + } + + public int getValue() { + return value; + } + } + enum Network { MAINNET, CONQUEST; @@ -79,11 +96,11 @@ public String toString() { private Network net = Network.MAINNET; - public int call(final String[] args, Cfg cfg) { + public ReturnType call(final String[] args, Cfg cfg) { return call(args, cfg, BASE_PATH); } - public int call(final String[] args, Cfg cfg, String path) { + public ReturnType call(final String[] args, Cfg cfg, String path) { try { // the preprocess method handles arguments that are separated by space // parsing populates the options object @@ -98,14 +115,14 @@ public int call(final String[] args, Cfg cfg, String path) { System.out.println(); printHelp(); - return 1; + return ERROR; } try { // the first set of options don't mix if (options.isHelp()) { printHelp(); - return 0; + return EXIT; } if (options.isVersion() || options.isVersionTag()) { @@ -114,13 +131,13 @@ public int call(final String[] args, Cfg cfg, String path) { System.out.println("--------------------------------------------"); } System.out.println(Version.KERNEL_VERSION); - return 0; + return EXIT; } if (options.isInfo()) { cfg.fromXML(); printInfo(cfg); - return 0; + return EXIT; } if (options.getConfig() != null) { @@ -153,14 +170,14 @@ public int call(final String[] args, Cfg cfg, String path) { System.out.println("--- Available Networks ---"); System.out.println(" mainnet, conquest"); System.out.println("--------------------------"); - return 1; + return ERROR; } } cfg.fromXML(); cfg.setId(UUID.randomUUID().toString()); cfg.toXML(null); - return 0; + return EXIT; } // the following options can be combined @@ -174,7 +191,7 @@ public int call(final String[] args, Cfg cfg, String path) { if (args.length < 2) { printHelp(); - return 1; + return ERROR; } else { while (index < args.length) { if(args[index].equals("-d")||args[index].equals("-n")||args[index].equals("--datadir")||args[index].equals("--network")) { @@ -195,27 +212,27 @@ public int call(final String[] args, Cfg cfg, String path) { switch (args[1]) { case "create": if (!createAccount()) { - return 1; + return ERROR; } break; case "list": if (!listAccounts()) { - return 1; + return ERROR; } break; case "export": if (args.length < 3 || !exportPrivateKey(args[2])) { - return 1; + return ERROR; } break; case "import": if (args.length < 3 || !importPrivateKey(args[2])) { - return 1; + return ERROR; } break; default: printHelp(); - return 1; + return ERROR; } break; case "-s": @@ -235,7 +252,7 @@ public int call(final String[] args, Cfg cfg, String path) { } else { System.out.println("Incorrect usage of -s create command.\n" + "Command must enter both hostname AND ip or else neither one."); - return 1; + return ERROR; } break; case "-r": @@ -253,10 +270,10 @@ public int call(final String[] args, Cfg cfg, String path) { case FAILURE: System.out .println("Unable to revert to block number " + args[1] + "."); - return 1; + return ERROR; case ILLEGAL_ARGUMENT: default: - return 1; + return ERROR; } } break; @@ -283,7 +300,7 @@ public int call(final String[] args, Cfg cfg, String path) { cfg.getLog().setLogPath(net.toString() + "/log"); cfg.getDb().setDatabasePath(net.toString() + "/database"); Keystore.setKeystorePath(path + "/" + net.toString() + "/keystore"); - return 2; + return RUN; } @@ -297,17 +314,17 @@ else if ((args[2].equals("-d")||args[2].equals("--datadir")) && isValid(args[3]) String[] newArgs = Arrays.copyOfRange(args, 2, args.length); call(newArgs, cfg); - return 2; + return RUN; } else if (!(args[2].equals("-d")||args[2].equals("--datadir"))) { System.out.println("\nInvalid multi arguments!\n"); printHelp(); - return 1; + return ERROR; } else { System.out.println("\nInvalid datadir selected!"); System.out.println("Please choose valid directory name!\n"); - return 1; + return ERROR; } default: @@ -315,7 +332,7 @@ else if ((args[2].equals("-d")||args[2].equals("--datadir")) && isValid(args[3]) System.out.println("--- Available Networks ---"); System.out.println(" mainnet, conquest"); System.out.println("--------------------------\n"); - return 1; + return ERROR; } } else { @@ -323,7 +340,7 @@ else if ((args[2].equals("-d")||args[2].equals("--datadir")) && isValid(args[3]) System.out.println("--- Available Networks ---"); System.out.println(" mainnet , conquest"); System.out.println("--------------------------\n"); - return 1; + return ERROR; } // Determines database folder path @@ -338,7 +355,7 @@ else if ((args[2].equals("-d")||args[2].equals("--datadir")) && isValid(args[3]) cfg.getLog().setLogPath(args[1] + "/" + net + "/log"); cfg.getDb().setDatabasePath(args[1] + "/" + net + "/database"); Keystore.setKeystorePath(path + "/" + args[1] + "/" + net + "/keystore"); - return 2; + return RUN; } @@ -352,25 +369,25 @@ else if (isValid(args[3])) { cfg.getLog().setLogPath(args[1] + "/" + net + "/log"); cfg.getDb().setDatabasePath(args[1] + "/" + net + "/database"); Keystore.setKeystorePath(path + "/" + args[1] + "/" + net + "/keystore"); - return 2; + return RUN; } else if (!(args[2].equals("-n")||args[2].equals("--network"))) { System.out.println("\nInvalid multi arguments!\n"); printHelp(); - return 1; + return ERROR; } else { System.out.println("\nInvalid network selected!"); System.out.println("--- Available Networks ---"); System.out.println(" mainnet , conquest"); System.out.println("--------------------------\n"); - return 1; + return ERROR; } } else { System.out.println("\nInvalid datadir selected!"); System.out.println("Please choose valid directory name!\n"); - return 1; + return ERROR; } case "--state": { @@ -383,7 +400,7 @@ else if (isValid(args[3])) { } catch (Throwable t) { System.out.println("Reorganizing the state storage FAILED due to:"); t.printStackTrace(); - return 1; + return ERROR; } break; } @@ -463,17 +480,17 @@ else if (isValid(args[3])) { default: System.out.println("Unable to parse the input arguments"); printHelp(); - return 1; + return ERROR; } System.out.println(""); } catch (Throwable e) { System.out.println(""); e.printStackTrace(); - return 1; + return ERROR; } - return 0; + return EXIT; } /** Print the CLI help info. */ diff --git a/modAionImpl/test/org/aion/zero/impl/cli/CliTest.java b/modAionImpl/test/org/aion/zero/impl/cli/CliTest.java index 27be96a0ab..d7e1349db6 100644 --- a/modAionImpl/test/org/aion/zero/impl/cli/CliTest.java +++ b/modAionImpl/test/org/aion/zero/impl/cli/CliTest.java @@ -1,5 +1,6 @@ package org.aion.zero.impl.cli; +import static org.aion.zero.impl.cli.Cli.ReturnType.*; import static org.junit.Assert.assertEquals; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doReturn; @@ -78,58 +79,58 @@ public void shutdown() { /** Ensures that the -h and --help arguments do not fail. */ @Test public void testHelp() { - assertEquals(0, cli.call(new String[] {"-h"}, CfgAion.inst())); - assertEquals(0, cli.call(new String[] {"--help"}, CfgAion.inst())); + assertEquals(EXIT, cli.call(new String[] {"-h"}, CfgAion.inst())); + assertEquals(EXIT, cli.call(new String[] {"--help"}, CfgAion.inst())); } /** Ensures that the -v and --version arguments do not fail. */ @Test public void testVersion() { - assertEquals(0, cli.call(new String[] {"-v"}, CfgAion.inst())); - assertEquals(0, cli.call(new String[] {"--version"}, CfgAion.inst())); + assertEquals(EXIT, cli.call(new String[] {"-v"}, CfgAion.inst())); + assertEquals(EXIT, cli.call(new String[] {"--version"}, CfgAion.inst())); } /** Ensures that the -i and --info arguments do not fail. */ @Test public void testInfo() { - assertEquals(0, cli.call(new String[] {"-i"}, CfgAion.inst())); - assertEquals(0, cli.call(new String[] {"--info"}, CfgAion.inst())); + assertEquals(EXIT, cli.call(new String[] {"-i"}, CfgAion.inst())); + assertEquals(EXIT, cli.call(new String[] {"--info"}, CfgAion.inst())); } /** Ensures correct behavior for the -c and --config arguments. */ @Test public void testConfig() { // compatibility with old kernels - assertEquals(0, cli.call(new String[] {"-c"}, CfgAion.inst())); - assertEquals(0, cli.call(new String[] {"--config"}, CfgAion.inst())); + assertEquals(EXIT, cli.call(new String[] {"-c"}, CfgAion.inst())); + assertEquals(EXIT, cli.call(new String[] {"--config"}, CfgAion.inst())); // available networks for (Cli.Network net : Cli.Network.values()) { - assertEquals(0, cli.call(new String[] {"-c", net.toString()}, CfgAion.inst())); - assertEquals(0, cli.call(new String[] {"--config", net.toString()}, CfgAion.inst())); + assertEquals(EXIT, cli.call(new String[] {"-c", net.toString()}, CfgAion.inst())); + assertEquals(EXIT, cli.call(new String[] {"--config", net.toString()}, CfgAion.inst())); } // accepted alias - assertEquals(0, cli.call(new String[] {"-c", "testnet"}, CfgAion.inst())); - assertEquals(0, cli.call(new String[] {"--config", "testnet"}, CfgAion.inst())); + assertEquals(EXIT, cli.call(new String[] {"-c", "testnet"}, CfgAion.inst())); + assertEquals(EXIT, cli.call(new String[] {"--config", "testnet"}, CfgAion.inst())); // incorrect value - assertEquals(1, cli.call(new String[] {"-c", "random"}, CfgAion.inst())); - assertEquals(1, cli.call(new String[] {"--config", "random"}, CfgAion.inst())); + assertEquals(ERROR, cli.call(new String[] {"-c", "random"}, CfgAion.inst())); + assertEquals(ERROR, cli.call(new String[] {"--config", "random"}, CfgAion.inst())); } /** Tests the -a create arguments do not fail. */ @Test public void testCreateAccount() { String args[] = {"-a", "create"}; - assertEquals(0, cli.call(args, CfgAion.inst())); + assertEquals(EXIT, cli.call(args, CfgAion.inst())); } /** Tests the -a list arguments do not fail. */ @Test public void testListAccounts() { String args[] = {"-a", "list"}; - assertEquals(0, cli.call(args, CfgAion.inst())); + assertEquals(EXIT, cli.call(args, CfgAion.inst())); } /** Tests the -a export arguments do not fail on a valid account. */ @@ -138,7 +139,7 @@ public void testExportPrivateKey() { String account = Keystore.create("password"); String[] args = {"-a", "export", account}; - assertEquals(0, cli.call(args, CfgAion.inst())); + assertEquals(EXIT, cli.call(args, CfgAion.inst())); } /** @@ -151,7 +152,7 @@ public void testExportSubstringOfAccount() { String substrAcc = account.substring(1); String[] args = {"-a", "export", substrAcc}; - assertEquals(1, cli.call(args, CfgAion.inst())); + assertEquals(ERROR, cli.call(args, CfgAion.inst())); } /** Tests the -a import arguments do not fail on a fail import key. */ @@ -160,7 +161,7 @@ public void testImportPrivateKey() { ECKey key = ECKeyFac.inst().create(); String[] args = {"-a", "import", Hex.toHexString(key.getPrivKeyBytes())}; - assertEquals(0, cli.call(args, CfgAion.inst())); + assertEquals(EXIT, cli.call(args, CfgAion.inst())); } /** Tests the -a import arguments fail when a non-private key is supplied. */ @@ -169,7 +170,7 @@ public void testImportNonPrivateKey() { String account = Keystore.create("password"); String[] args = {"-a", "import", account}; - assertEquals(1, cli.call(args, CfgAion.inst())); + assertEquals(ERROR, cli.call(args, CfgAion.inst())); } /** Tests the -a import arguments do not fail when a valid private key is supplied. */ @@ -181,7 +182,7 @@ public void testImportPrivateKey2() { System.out.println("Original private key: " + Hex.toHexString(key.getPrivKeyBytes())); String[] args = {"-a", "import", Hex.toHexString(key.getPrivKeyBytes())}; - assertEquals(0, cli.call(args, CfgAion.inst())); + assertEquals(EXIT, cli.call(args, CfgAion.inst())); ECKey key2 = Keystore.getKey(Hex.toHexString(key.getAddress()), "password"); System.out.println("Imported address : " + Hex.toHexString(key2.getAddress())); @@ -193,7 +194,7 @@ public void testImportPrivateKey2() { @Test public void testImportPrivateKeyWrong() { String[] args = {"-a", "import", "hello"}; - assertEquals(1, cli.call(args, CfgAion.inst())); + assertEquals(ERROR, cli.call(args, CfgAion.inst())); } /** @@ -219,23 +220,23 @@ public void testDatadir() { System.out.println(logOG + " " + dbOG); System.out.println("Invalid Datadir:"); - assertEquals(1, cli.call(networkArgs[0], cfg)); - assertEquals(1, cli.call(networkArgs[1], cfg)); - assertEquals(1, cli.call(networkArgs[2], cfg)); + assertEquals(ERROR, cli.call(networkArgs[0], cfg)); + assertEquals(ERROR, cli.call(networkArgs[1], cfg)); + assertEquals(ERROR, cli.call(networkArgs[2], cfg)); cfg.getLog().setLogPath(logOG); cfg.getDb().setPath(dbOG); System.out.println(cfg.getLog().getLogPath() + " " + cfg.getDb().getPath()); System.out.println("\nValid Datadir 1: " + networkArgs[3][1]); - assertEquals(2, cli.call(networkArgs[3], cfg)); + assertEquals(RUN, cli.call(networkArgs[3], cfg)); assertEquals("aaaaaaaa/mainnet/database", cfg.getDb().getPath()); assertEquals("aaaaaaaa/mainnet/log", cfg.getLog().getLogPath()); assertEquals(BASE_PATH + "/aaaaaaaa/mainnet/keystore", Keystore.getKeystorePath()); printPaths(BASE_PATH, cfg, cli); System.out.println("\nValid Datadir 2: " + networkArgs[4][1]); - assertEquals(2, cli.call(networkArgs[4], cfg)); + assertEquals(RUN, cli.call(networkArgs[4], cfg)); assertEquals("abbbbbbb/mainnet/database", cfg.getDb().getPath()); assertEquals("abbbbbbb/mainnet/log", cfg.getLog().getLogPath()); assertEquals(BASE_PATH + "/abbbbbbb/mainnet/keystore", Keystore.getKeystorePath()); @@ -262,26 +263,26 @@ public void testNetwork() { Cli cli = new Cli(); System.out.println("Invalid Networks:"); - assertEquals(1, cli.call(networkArgs[0], cfg)); - assertEquals(1, cli.call(networkArgs[1], cfg)); - assertEquals(1, cli.call(networkArgs[2], cfg)); + assertEquals(ERROR, cli.call(networkArgs[0], cfg)); + assertEquals(ERROR, cli.call(networkArgs[1], cfg)); + assertEquals(ERROR, cli.call(networkArgs[2], cfg)); System.out.println("\nValid Network 1: " + networkArgs[3][1]); - assertEquals(2, cli.call(networkArgs[3], cfg)); + assertEquals(RUN, cli.call(networkArgs[3], cfg)); assertEquals("mainnet", CfgAion.getNetwork()); assertEquals(BASE_PATH + "/config/mainnet/config.xml", CfgAion.getConfFilePath()); assertEquals(BASE_PATH + "/config/mainnet/genesis.json", CfgAion.getGenesisFilePath()); printPaths(BASE_PATH, cfg, cli); System.out.println("\nValid Network 2: " + networkArgs[4][1]); - assertEquals(2, cli.call(networkArgs[4], cfg)); + assertEquals(RUN, cli.call(networkArgs[4], cfg)); assertEquals("conquest", CfgAion.getNetwork()); assertEquals(BASE_PATH + "/config/conquest/config.xml", CfgAion.getConfFilePath()); assertEquals(BASE_PATH + "/config/conquest/genesis.json", CfgAion.getGenesisFilePath()); printPaths(BASE_PATH, cfg, cli); System.out.println("\nValid Network 3: " + networkArgs[5][1]); - assertEquals(2, cli.call(networkArgs[5], cfg)); + assertEquals(RUN, cli.call(networkArgs[5], cfg)); assertEquals("conquest", CfgAion.getNetwork()); assertEquals(BASE_PATH + "/config/conquest/config.xml", CfgAion.getConfFilePath()); assertEquals(BASE_PATH + "/config/conquest/genesis.json", CfgAion.getGenesisFilePath()); @@ -308,7 +309,7 @@ public void testMultiNetworkDatadir() { Cfg cfg = CfgAion.inst(); System.out.println("\nNew 1: " + multiArgs[0][1] + " " + multiArgs[0][3]); - assertEquals(2, cli.call(multiArgs[0], cfg)); + assertEquals(RUN, cli.call(multiArgs[0], cfg)); assertEquals("mainnet", CfgAion.getNetwork()); assertEquals("aaaaaaaa/mainnet/log", cfg.getLog().getLogPath()); assertEquals("aaaaaaaa/mainnet/database", cfg.getDb().getPath()); @@ -316,7 +317,7 @@ public void testMultiNetworkDatadir() { printPaths(BASE_PATH, cfg, cli); System.out.println("\nNew 2: " + multiArgs[1][1] + " " + multiArgs[1][3]); - assertEquals(2, cli.call(multiArgs[1], cfg)); + assertEquals(RUN, cli.call(multiArgs[1], cfg)); assertEquals("conquest", CfgAion.getNetwork()); assertEquals("abbbbbbb/conquest/log", cfg.getLog().getLogPath()); assertEquals("abbbbbbb/conquest/database", cfg.getDb().getPath()); @@ -324,7 +325,7 @@ public void testMultiNetworkDatadir() { printPaths(BASE_PATH, cfg, cli); System.out.println("\nNew 3: " + multiArgs[2][1] + " " + multiArgs[2][3]); - assertEquals(2, cli.call(multiArgs[2], cfg)); + assertEquals(RUN, cli.call(multiArgs[2], cfg)); assertEquals("conquest", CfgAion.getNetwork()); assertEquals("abcccccc/conquest/log", cfg.getLog().getLogPath()); assertEquals("abcccccc/conquest/database", cfg.getDb().getPath()); @@ -332,7 +333,7 @@ public void testMultiNetworkDatadir() { printPaths(BASE_PATH, cfg, cli); System.out.println("\n Exist 1: " + multiArgs[3][1] + " " + multiArgs[3][3]); - assertEquals(2, cli.call(multiArgs[3], cfg)); + assertEquals(RUN, cli.call(multiArgs[3], cfg)); assertEquals("conquest", CfgAion.getNetwork()); assertEquals("aaaaaaaa/conquest/log", cfg.getLog().getLogPath()); assertEquals("aaaaaaaa/conquest/database", cfg.getDb().getPath()); @@ -340,7 +341,7 @@ public void testMultiNetworkDatadir() { printPaths(BASE_PATH, cfg, cli); System.out.println("\nExist 2: " + multiArgs[4][1] + " " + multiArgs[4][3]); - assertEquals(2, cli.call(multiArgs[4], cfg)); + assertEquals(RUN, cli.call(multiArgs[4], cfg)); assertEquals("conquest", CfgAion.getNetwork()); assertEquals("aaaaaaaa/conquest/log", cfg.getLog().getLogPath()); assertEquals("aaaaaaaa/conquest/database", cfg.getDb().getPath()); @@ -348,10 +349,10 @@ public void testMultiNetworkDatadir() { printPaths(BASE_PATH, cfg, cli); // Invalid input - assertEquals(1, cli.call(multiArgs[5], cfg)); - assertEquals(1, cli.call(multiArgs[6], cfg)); - assertEquals(1, cli.call(multiArgs[7], cfg)); - assertEquals(1, cli.call(multiArgs[8], cfg)); + assertEquals(ERROR, cli.call(multiArgs[5], cfg)); + assertEquals(ERROR, cli.call(multiArgs[6], cfg)); + assertEquals(ERROR, cli.call(multiArgs[7], cfg)); + assertEquals(ERROR, cli.call(multiArgs[8], cfg)); } private void printPaths(String BASE_PATH, Cfg cfg, Cli cli) { diff --git a/modBoot/src/org/aion/Aion.java b/modBoot/src/org/aion/Aion.java index bffeda2b8b..c8ac93e2f3 100644 --- a/modBoot/src/org/aion/Aion.java +++ b/modBoot/src/org/aion/Aion.java @@ -27,6 +27,7 @@ import static org.aion.crypto.ECKeyFac.ECKeyType.ED25519; import static org.aion.crypto.HashUtil.H256Type.BLAKE2B_256; import static org.aion.zero.impl.Version.KERNEL_VERSION; +import static org.aion.zero.impl.cli.Cli.ReturnType; import java.io.Console; import java.io.IOException; @@ -94,9 +95,9 @@ public static void main(String args[]) { // Reads CLI (must be after the cfg.fromXML()) if (args != null && args.length > 0) { - int ret = new Cli().call(args, cfg); - if (ret != 2) { - exit(ret); + ReturnType ret = new Cli().call(args, cfg); + if (ret != ReturnType.RUN) { + exit(ret.getValue()); } } From 11a23bacddbe5dd646730e450ef6807f46f95d84 Mon Sep 17 00:00:00 2001 From: Alexandra Roatis Date: Thu, 30 Aug 2018 11:24:24 -0400 Subject: [PATCH 07/31] account management using new parser --- .../src/org/aion/zero/impl/cli/Cli.java | 131 ++++++++++-------- 1 file changed, 70 insertions(+), 61 deletions(-) diff --git a/modAionImpl/src/org/aion/zero/impl/cli/Cli.java b/modAionImpl/src/org/aion/zero/impl/cli/Cli.java index d1ee8b0156..38f096b157 100644 --- a/modAionImpl/src/org/aion/zero/impl/cli/Cli.java +++ b/modAionImpl/src/org/aion/zero/impl/cli/Cli.java @@ -119,7 +119,8 @@ public ReturnType call(final String[] args, Cfg cfg, String path) { } try { - // the first set of options don't mix + // 1. the first set of options don't mix with -d and -n + if (options.isHelp()) { printHelp(); return EXIT; @@ -134,17 +135,20 @@ public ReturnType call(final String[] args, Cfg cfg, String path) { return EXIT; } - if (options.isInfo()) { - cfg.fromXML(); - printInfo(cfg); - return EXIT; + // 2. determine the execution folder path + + if (options.getDirectory() != null) { + setDirectory(options.getDirectory()); + // no return -> allow for other parameters combined with -d } + // 3. can be influenced by the -d argument above + if (options.getConfig() != null) { String strNet = options.getConfig().toLowerCase(); if (strNet.isEmpty()) { - // no network give; use default path + // no network given; use default path // for compatibility with old kernels CfgAion.setNetwork(strNet); File dir = new File(BASE_PATH + "/config"); @@ -166,75 +170,65 @@ public ReturnType call(final String[] args, Cfg cfg, String path) { CfgAion.setConfFilePath(BASE_PATH + "/config/" + strNet + "/config.xml"); System.out.println("\nNew config generated for " + strNet + "."); } else { - System.out.println("\nInvalid network selected!"); - System.out.println("--- Available Networks ---"); - System.out.println(" mainnet, conquest"); - System.out.println("--------------------------"); - return ERROR; + printInvalidNetwork(); + return ReturnType.ERROR; } } cfg.fromXML(); cfg.setId(UUID.randomUUID().toString()); cfg.toXML(null); - return EXIT; + return ReturnType.EXIT; } - // the following options can be combined - cfg.fromXML(); + // 4. determine the network configuration - switch (args[0].toLowerCase()) { - case "-a": + if (options.getNetwork() != null) { + setNetwork(options.getConfig().toLowerCase()); + // no return -> allow for other parameters combined with -n + } - int index = 0; - boolean multi = false; + // 5. options that can be influenced by the -d and -n arguments - if (args.length < 2) { - printHelp(); - return ERROR; - } else { - while (index < args.length) { - if(args[index].equals("-d")||args[index].equals("-n")||args[index].equals("--datadir")||args[index].equals("--network")) { - multi = true; - break; - } - index++; - } - } + if (options.isInfo()) { + cfg.fromXML(); + printInfo(cfg); + return ReturnType.EXIT; + } - // Switches datadir && network - if(multi) { - String[] newArgs = Arrays.copyOfRange(args, index, args.length); - call(newArgs, cfg); - } + if (options.isCreateAccount()) { + if (!createAccount()) { + return ERROR; + } else { + return EXIT; + } + } + if (options.isListAccounts()) { + if (!listAccounts()) { + return ERROR; + } else { + return EXIT; + } + } - switch (args[1]) { - case "create": - if (!createAccount()) { - return ERROR; - } - break; - case "list": - if (!listAccounts()) { - return ERROR; - } - break; - case "export": - if (args.length < 3 || !exportPrivateKey(args[2])) { - return ERROR; - } - break; - case "import": - if (args.length < 3 || !importPrivateKey(args[2])) { - return ERROR; - } - break; - default: - printHelp(); - return ERROR; - } - break; + if (options.getExportAccount()!= null) { + if (!exportPrivateKey(options.getExportAccount())) { + return ERROR; + } else { + return EXIT; + } + } + + if (options.getImportAccount()!= null) { + if (!importPrivateKey(options.getImportAccount())) { + return ERROR; + } else { + return EXIT; + } + } + + switch (args[0].toLowerCase()) { case "-s": if ((args.length == 2 || args.length == 4) && (args[1].equals("create"))) { createKeystoreDirIfMissing(); @@ -493,6 +487,14 @@ else if (isValid(args[3])) { return EXIT; } + private void setDirectory(String directory) { + // TODO + } + + private void setNetwork(String network) { + // TODO + } + /** Print the CLI help info. */ private void printHelp() { String usage = parser.getUsageMessage(); @@ -554,6 +556,13 @@ private Network determineNetwork(String arg) { return net; } + private void printInvalidNetwork(){ + System.out.println("\nInvalid network selected!\n"); + System.out.println("--- Available Networks ---"); + System.out.println(" mainnet, conquest"); + System.out.println("--------------------------\n"); + } + /** * Copies the config files (config && genesis) from root to [datadir]/[network] * From 9f51a1bfa0503c715b474dbea11fc58416a8ab5d Mon Sep 17 00:00:00 2001 From: Alexandra Date: Fri, 14 Sep 2018 14:35:28 -0400 Subject: [PATCH 08/31] handling all options except -n and -d --- .../src/org/aion/zero/impl/cli/Arguments.java | 38 +- .../src/org/aion/zero/impl/cli/Cli.java | 599 +++++++++--------- 2 files changed, 337 insertions(+), 300 deletions(-) diff --git a/modAionImpl/src/org/aion/zero/impl/cli/Arguments.java b/modAionImpl/src/org/aion/zero/impl/cli/Arguments.java index 287b52924f..4126ea7640 100644 --- a/modAionImpl/src/org/aion/zero/impl/cli/Arguments.java +++ b/modAionImpl/src/org/aion/zero/impl/cli/Arguments.java @@ -64,14 +64,14 @@ public class Arguments { paramLabel = "", description = "export private key of an account" ) - private String exportAccount; + private String exportAccount = null; @Option( names = {"ai", "-a import"}, paramLabel = "", description = "import private key" ) - private String importAccount; + private String importAccount = null; // config generation @Option( @@ -80,7 +80,7 @@ public class Arguments { paramLabel = "", description = "create config for the selected network\noptions: mainnet, conquest" ) - public String config; + public String config = null; // get information and version @Option( @@ -110,7 +110,7 @@ public class Arguments { "create a ssl certificate for:\n - localhost (when no parameters are given), or" + "\n - the given hostname and ip" ) - public String[] ssl; + public String[] ssl = null; // offline block management @Option( @@ -125,20 +125,20 @@ public class Arguments { paramLabel = "", description = "revert database state to given block number" ) - private long revertToBlock; + private String revertToBlock = null; // network and directory setup @Option( names = {"-n", "--network"}, description = "execute kernel with selected network\noptions: mainnet, conquest" ) - public String network; + public String network = null; @Option( names = {"-d", "--datadir"}, description = "execute kernel with selected database directory" ) - public String directory; + public String directory = null; // offline database query and update @Option( @@ -146,30 +146,32 @@ public class Arguments { paramLabel = "", description = "reorganize the state storage\noptions: FULL, TOP, SPREAD" ) - public String stateOption; + public String pruntStateOption = null; // print info from db @Option( names = {"--dump-blocks"}, - arity = "1", + arity = "0..1", paramLabel = "", description = "print top blocks from database" ) - public int dumpBlocksCount = 10; + public String dumpBlocksCount = null; @Option( names = {"--dump-state-size"}, + arity = "0..1", paramLabel = "", description = "retrieves the state size (node count) for the top blocks" ) - public int dumpStateSizeCount = 2; + public String dumpStateSizeCount = null; @Option( names = {"--dump-state"}, + arity = "0..1", paramLabel = "", description = "retrieves the state for the top main chain blocks" ) - public int dumpStateCount = 2; + public String dumpStateCount = null; @Option( names = {"--db-compact"}, @@ -241,7 +243,7 @@ public boolean isRebuildBlockInfo() { return rebuildBlockInfo; } - public long getRevertToBlock() { + public String getRevertToBlock() { return revertToBlock; } @@ -253,19 +255,19 @@ public String getDirectory() { return directory; } - public String getStateOption() { - return stateOption; + public String getPruneStateOption() { + return pruntStateOption; } - public int getDumpBlocksCount() { + public String getDumpBlocksCount() { return dumpBlocksCount; } - public int getDumpStateSizeCount() { + public String getDumpStateSizeCount() { return dumpStateSizeCount; } - public int getDumpStateCount() { + public String getDumpStateCount() { return dumpStateCount; } diff --git a/modAionImpl/src/org/aion/zero/impl/cli/Cli.java b/modAionImpl/src/org/aion/zero/impl/cli/Cli.java index 38f096b157..027e076bd1 100644 --- a/modAionImpl/src/org/aion/zero/impl/cli/Cli.java +++ b/modAionImpl/src/org/aion/zero/impl/cli/Cli.java @@ -21,12 +21,20 @@ * Aion foundation. */ - package org.aion.zero.impl.cli; +import static org.aion.zero.impl.cli.Cli.ReturnType.ERROR; +import static org.aion.zero.impl.cli.Cli.ReturnType.EXIT; +import static org.aion.zero.impl.cli.Cli.ReturnType.RUN; + import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; import com.google.common.io.Files; +import java.io.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.UUID; import org.aion.base.util.Hex; import org.aion.crypto.ECKey; import org.aion.crypto.ECKeyFac; @@ -38,16 +46,6 @@ import org.aion.zero.impl.db.RecoveryUtils; import picocli.CommandLine; -import java.io.*; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.UUID; - -import static org.aion.zero.impl.cli.Cli.ReturnType.ERROR; -import static org.aion.zero.impl.cli.Cli.ReturnType.EXIT; -import static org.aion.zero.impl.cli.Cli.ReturnType.RUN; - /** * Command line interface. * @@ -57,19 +55,22 @@ public class Cli { private final String BASE_PATH = System.getProperty("user.dir"); - private String BASE_PATH_WITH_NETWORK = BASE_PATH + "/config/" + CfgAion.getNetwork(); + private String BASE_PATH_WITH_NETWORK = BASE_PATH + "/config/" + CfgAion.getNetwork(); private String dstConfig = BASE_PATH_WITH_NETWORK + "/config.xml"; private String dstGenesis = BASE_PATH_WITH_NETWORK + "/genesis.json"; - File keystoreDir = new File(System.getProperty("user.dir") + File.separator + CfgSsl.SSL_KEYSTORE_DIR); + File keystoreDir = + new File(System.getProperty("user.dir") + File.separator + CfgSsl.SSL_KEYSTORE_DIR); private Arguments options = new Arguments(); private CommandLine parser = new CommandLine(options); public enum ReturnType { - RUN(2), EXIT(0), ERROR(1); + RUN(2), + EXIT(0), + ERROR(1); private int value; ReturnType(int _value) { @@ -82,14 +83,18 @@ public int getValue() { } enum Network { - MAINNET, CONQUEST; + MAINNET, + CONQUEST; @Override public String toString() { - switch(this) { - case MAINNET: return "mainnet"; - case CONQUEST: return "conquest"; - default: throw new IllegalArgumentException(); + switch (this) { + case MAINNET: + return "mainnet"; + case CONQUEST: + return "conquest"; + default: + throw new IllegalArgumentException(); } } } @@ -212,7 +217,7 @@ public ReturnType call(final String[] args, Cfg cfg, String path) { } } - if (options.getExportAccount()!= null) { + if (options.getExportAccount() != null) { if (!exportPrivateKey(options.getExportAccount())) { return ERROR; } else { @@ -220,7 +225,7 @@ public ReturnType call(final String[] args, Cfg cfg, String path) { } } - if (options.getImportAccount()!= null) { + if (options.getImportAccount() != null) { if (!importPrivateKey(options.getImportAccount())) { return ERROR; } else { @@ -228,271 +233,300 @@ public ReturnType call(final String[] args, Cfg cfg, String path) { } } - switch (args[0].toLowerCase()) { - case "-s": - if ((args.length == 2 || args.length == 4) && (args[1].equals("create"))) { - createKeystoreDirIfMissing(); - Console console = System.console(); - checkConsoleExists(console); - - List scriptArgs = new ArrayList<>(); - scriptArgs.add("/bin/bash"); - scriptArgs.add("script/generateSslCert.sh"); - scriptArgs.add(getCertName(console)); - scriptArgs.add(getCertPass(console)); - // add the hostname and ip optionally passed in as cli args - scriptArgs.addAll(Arrays.asList(Arrays.copyOfRange(args, 2, args.length))); - new ProcessBuilder(scriptArgs).inheritIO().start().waitFor(); - } else { - System.out.println("Incorrect usage of -s create command.\n" + - "Command must enter both hostname AND ip or else neither one."); - return ERROR; - } - break; - case "-r": - if (args.length < 2) { - System.out.println("Starting database clean-up."); - RecoveryUtils.pruneAndCorrect(); - System.out.println("Finished database clean-up."); - } else { - switch (revertTo(args[1])) { - case SUCCESS: - System.out.println( - "Blockchain successfully reverted to block number " + args[1] - + "."); - break; - case FAILURE: - System.out - .println("Unable to revert to block number " + args[1] + "."); - return ERROR; - case ILLEGAL_ARGUMENT: - default: - return ERROR; - } - } - break; - - case "-n": - case "--network": - if ( (args.length == 2 || args.length == 4) && isValid(args[1])) { - - net = determineNetwork(args[1].toLowerCase()); - - switch (net) { - case MAINNET: - case CONQUEST: - - // -n [network] - if (args.length == 2) { - - CfgAion.setNetwork(net.toString()); - BASE_PATH_WITH_NETWORK = BASE_PATH + "/config/" + CfgAion.getNetwork(); - CfgAion.setConfFilePath(BASE_PATH_WITH_NETWORK + "/config.xml"); - CfgAion.setGenesisFilePath((BASE_PATH_WITH_NETWORK + "/genesis.json")); - - copyNetwork(path, net); - cfg.getLog().setLogPath(net.toString() + "/log"); - cfg.getDb().setDatabasePath(net.toString() + "/database"); - Keystore.setKeystorePath(path + "/" + net.toString() + "/keystore"); - return RUN; - - } - - // -n [network] -d [directory] - else if ((args[2].equals("-d")||args[2].equals("--datadir")) && isValid(args[3])) { - - CfgAion.setNetwork(net.toString()); - BASE_PATH_WITH_NETWORK = BASE_PATH + "/config/" + CfgAion.getNetwork(); - CfgAion.setConfFilePath(BASE_PATH_WITH_NETWORK + "/config.xml"); - CfgAion.setGenesisFilePath((BASE_PATH_WITH_NETWORK + "/genesis.json")); - - String[] newArgs = Arrays.copyOfRange(args, 2, args.length); - call(newArgs, cfg); - return RUN; - - } else if (!(args[2].equals("-d")||args[2].equals("--datadir"))) { - System.out.println("\nInvalid multi arguments!\n"); - printHelp(); - return ERROR; - - } else { - System.out.println("\nInvalid datadir selected!"); - System.out.println("Please choose valid directory name!\n"); - return ERROR; - } - - default: - System.out.println("\nInvalid network selected!\n"); - System.out.println("--- Available Networks ---"); - System.out.println(" mainnet, conquest"); - System.out.println("--------------------------\n"); - return ERROR; - } - - } else { - System.out.println("\nInvalid network selected!"); - System.out.println("--- Available Networks ---"); - System.out.println(" mainnet , conquest"); - System.out.println("--------------------------\n"); - return ERROR; - } - - // Determines database folder path - case "-d": - case "--datadir": - if ( (args.length == 2 || args.length == 4) && isValid(args[1])) { - - // -d [directory] - if (args.length == 2) { + if (options.getSsl() != null) { + String[] paramters = options.getSsl(); + + if (paramters.length == 0 || paramters.length == 2) { + createKeystoreDirIfMissing(); + Console console = System.console(); + checkConsoleExists(console); + + List scriptArgs = new ArrayList<>(); + scriptArgs.add("/bin/bash"); + scriptArgs.add("script/generateSslCert.sh"); + scriptArgs.add(getCertName(console)); + scriptArgs.add(getCertPass(console)); + // add the hostname and ip optionally passed in as cli args + scriptArgs.addAll(Arrays.asList(paramters)); + new ProcessBuilder(scriptArgs).inheritIO().start().waitFor(); + return EXIT; + } else { + System.out.println( + "Incorrect usage of -s create command.\n" + + "Command must enter both hostname AND ip or else neither one."); + return ERROR; + } + } - copyNetwork(path + "/" + args[1], net); - cfg.getLog().setLogPath(args[1] + "/" + net + "/log"); - cfg.getDb().setDatabasePath(args[1] + "/" + net + "/database"); - Keystore.setKeystorePath(path + "/" + args[1] + "/" + net + "/keystore"); - return RUN; + if (options.isRebuildBlockInfo()) { + System.out.println("Starting database clean-up."); + RecoveryUtils.pruneAndCorrect(); + System.out.println("Finished database clean-up."); + return EXIT; + } + if (options.getRevertToBlock() != null) { + String block = options.getRevertToBlock(); + switch (revertTo(block)) { + case SUCCESS: + { + System.out.println( + "Blockchain successfully reverted to block number " + + block + + "."); + return EXIT; } - - // -d [directory] -n [network] - else if (isValid(args[3])) { - - String[] newArgs = Arrays.copyOfRange(args, 2, args.length); - call(newArgs, cfg); - - copyNetwork(path + "/" + args[1], net); - cfg.getLog().setLogPath(args[1] + "/" + net + "/log"); - cfg.getDb().setDatabasePath(args[1] + "/" + net + "/database"); - Keystore.setKeystorePath(path + "/" + args[1] + "/" + net + "/keystore"); - return RUN; - - } else if (!(args[2].equals("-n")||args[2].equals("--network"))) { - System.out.println("\nInvalid multi arguments!\n"); - printHelp(); + case FAILURE: + { + System.out.println("Unable to revert to block number " + block + "."); return ERROR; - - } else { - System.out.println("\nInvalid network selected!"); - System.out.println("--- Available Networks ---"); - System.out.println(" mainnet , conquest"); - System.out.println("--------------------------\n"); + } + case ILLEGAL_ARGUMENT: + default: + { return ERROR; } + } + } - } else { - System.out.println("\nInvalid datadir selected!"); - System.out.println("Please choose valid directory name!\n"); - return ERROR; - } + if (options.getPruneStateOption() != null) { + String pruning_type = options.getPruneStateOption(); + try { + RecoveryUtils.pruneOrRecoverState(pruning_type); + return EXIT; + } catch (Throwable t) { + System.out.println("Reorganizing the state storage FAILED due to:"); + t.printStackTrace(); + return ERROR; + } + } - case "--state": { - String pruning_type = "full"; - if (args.length >= 2) { - pruning_type = args[1]; - } + if (options.getDumpStateSizeCount() != null) { + long block_count = 2L; + String parameter = options.getDumpStateSizeCount(); + + if (parameter.isEmpty()) { + System.out.println("Retrieving state size for top " + block_count + " blocks."); + RecoveryUtils.printStateTrieSize(block_count); + return EXIT; + } else { try { - RecoveryUtils.pruneOrRecoverState(pruning_type); - } catch (Throwable t) { - System.out.println("Reorganizing the state storage FAILED due to:"); - t.printStackTrace(); + block_count = Long.parseLong(parameter); + } catch (NumberFormatException e) { + System.out.println( + "The given argument <" + + parameter + + "> cannot be converted to a number."); return ERROR; } - break; + if (block_count < 1) { + System.out.println("The given argument <" + parameter + "> is not valid."); + block_count = 2L; + } + + System.out.println("Retrieving state size for top " + block_count + " blocks."); + RecoveryUtils.printStateTrieSize(block_count); + return EXIT; } - case "--dump-state-size": - long block_count = 2L; + } - if (args.length < 2) { - System.out - .println("Retrieving state size for top " + block_count + " blocks."); - RecoveryUtils.printStateTrieSize(block_count); - } else { - try { - block_count = Long.parseLong(args[1]); - } catch (NumberFormatException e) { - System.out.println("The given argument <" + args[1] - + "> cannot be converted to a number."); - } - if (block_count < 1) { - System.out - .println("The given argument <" + args[1] + "> is not valid."); - block_count = 2L; - } + if (options.getDumpStateCount() != null) { + long level = -1L; + String parameter = options.getDumpStateCount(); - System.out - .println("Retrieving state size for top " + block_count + " blocks."); - RecoveryUtils.printStateTrieSize(block_count); + if (parameter.isEmpty()) { + System.out.println("Retrieving state for top main chain block..."); + RecoveryUtils.printStateTrieDump(level); + return EXIT; + } else { + try { + level = Long.parseLong(parameter); + } catch (NumberFormatException e) { + System.out.println( + "The given argument <" + + parameter + + "> cannot be converted to a number."); + return ERROR; } - break; - case "--dump-state": - long level = -1L; - - if (args.length < 2) { + if (level == -1L) { System.out.println("Retrieving state for top main chain block..."); - RecoveryUtils.printStateTrieDump(level); } else { - try { - level = Long.parseLong(args[1]); - } catch (NumberFormatException e) { - System.out.println("The given argument <" + args[1] - + "> cannot be converted to a number."); - } - if (level == -1L) { - System.out.println("Retrieving state for top main chain block..."); - } else { - System.out.println( + System.out.println( "Retrieving state for main chain block at level " + level + "..."); - } - RecoveryUtils.printStateTrieDump(level); } - break; - case "--db-compact": - RecoveryUtils.dbCompact(); - break; - case "--dump-blocks": - long count = 10L; - - if (args.length < 2) { - System.out.println("Printing top " + count + " blocks from database."); - RecoveryUtils.dumpBlocks(count); - } else { - try { - count = Long.parseLong(args[1]); - } catch (NumberFormatException e) { - System.out.println("The given argument <" + args[1] - + "> cannot be converted to a number."); - } - if (count < 1) { - System.out - .println("The given argument <" + args[1] + "> is not valid."); - count = 10L; - } + RecoveryUtils.printStateTrieDump(level); + return EXIT; + } + } + + if (options.getDumpBlocksCount() != null) { + long count = 10L; + String parameter = options.getDumpBlocksCount(); - System.out.println("Printing top " + count + " blocks from database."); - RecoveryUtils.dumpBlocks(count); + if (parameter.isEmpty()) { + System.out.println("Printing top " + count + " blocks from database."); + RecoveryUtils.dumpBlocks(count); + return EXIT; + } else { + try { + count = Long.parseLong(parameter); + } catch (NumberFormatException e) { + System.out.println( + "The given argument <" + + parameter + + "> cannot be converted to a number."); + return ERROR; } - break; - default: - System.out.println("Unable to parse the input arguments"); - printHelp(); - return ERROR; + if (count < 1) { + System.out.println("The given argument <" + parameter + "> is not valid."); + count = 10L; + } + + System.out.println("Printing top " + count + " blocks from database."); + RecoveryUtils.dumpBlocks(count); + return EXIT; + } } - System.out.println(""); + if (options.isDbCompact()) { + RecoveryUtils.dbCompact(); + return EXIT; + } + + // if no return happened earlier, run the kernel + return RUN; } catch (Throwable e) { + // TODO: should be moved to individual procedures System.out.println(""); e.printStackTrace(); return ERROR; } - - return EXIT; } private void setDirectory(String directory) { // TODO + // // Determines database folder path + // case "-d": + // case "--datadir": + // if ( (args.length == 2 || args.length == 4) && isValid(args[1])) { + // + // // -d [directory] + // if (args.length == 2) { + // + // copyNetwork(path + "/" + args[1], net); + // cfg.getLog().setLogPath(args[1] + "/" + net + "/log"); + // cfg.getDb().setDatabasePath(args[1] + "/" + net + "/database"); + // Keystore.setKeystorePath(path + "/" + args[1] + "/" + net + "/keystore"); + // return RUN; + // + // } + // + // // -d [directory] -n [network] + // else if (isValid(args[3])) { + // + // String[] newArgs = Arrays.copyOfRange(args, 2, args.length); + // call(newArgs, cfg); + // + // copyNetwork(path + "/" + args[1], net); + // cfg.getLog().setLogPath(args[1] + "/" + net + "/log"); + // cfg.getDb().setDatabasePath(args[1] + "/" + net + "/database"); + // Keystore.setKeystorePath(path + "/" + args[1] + "/" + net + "/keystore"); + // return RUN; + // + // } else if (!(args[2].equals("-n")||args[2].equals("--network"))) { + // System.out.println("\nInvalid multi arguments!\n"); + // printHelp(); + // return ERROR; + // + // } else { + // System.out.println("\nInvalid network selected!"); + // System.out.println("--- Available Networks ---"); + // System.out.println(" mainnet , conquest"); + // System.out.println("--------------------------\n"); + // return ERROR; + // } + // + // } else { + // System.out.println("\nInvalid datadir selected!"); + // System.out.println("Please choose valid directory name!\n"); + // return ERROR; + // } + } private void setNetwork(String network) { // TODO + // case "-n": + // case "--network": + // if ( (args.length == 2 || args.length == 4) && isValid(args[1])) { + // + // net = determineNetwork(args[1].toLowerCase()); + // + // switch (net) { + // case MAINNET: + // case CONQUEST: + // + // // -n [network] + // if (args.length == 2) { + // + // CfgAion.setNetwork(net.toString()); + // BASE_PATH_WITH_NETWORK = BASE_PATH + "/config/" + + // CfgAion.getNetwork(); + // CfgAion.setConfFilePath(BASE_PATH_WITH_NETWORK + "/config.xml"); + // CfgAion.setGenesisFilePath((BASE_PATH_WITH_NETWORK + + // "/genesis.json")); + // + // copyNetwork(path, net); + // cfg.getLog().setLogPath(net.toString() + "/log"); + // cfg.getDb().setDatabasePath(net.toString() + "/database"); + // Keystore.setKeystorePath(path + "/" + net.toString() + + // "/keystore"); + // return RUN; + // + // } + // + // // -n [network] -d [directory] + // else if ((args[2].equals("-d")||args[2].equals("--datadir")) && + // isValid(args[3])) { + // + // CfgAion.setNetwork(net.toString()); + // BASE_PATH_WITH_NETWORK = BASE_PATH + "/config/" + + // CfgAion.getNetwork(); + // CfgAion.setConfFilePath(BASE_PATH_WITH_NETWORK + "/config.xml"); + // CfgAion.setGenesisFilePath((BASE_PATH_WITH_NETWORK + + // "/genesis.json")); + // + // String[] newArgs = Arrays.copyOfRange(args, 2, args.length); + // call(newArgs, cfg); + // return RUN; + // + // } else if (!(args[2].equals("-d")||args[2].equals("--datadir"))) { + // System.out.println("\nInvalid multi arguments!\n"); + // printHelp(); + // return ERROR; + // + // } else { + // System.out.println("\nInvalid datadir selected!"); + // System.out.println("Please choose valid directory name!\n"); + // return ERROR; + // } + // + // default: + // System.out.println("\nInvalid network selected!\n"); + // System.out.println("--- Available Networks ---"); + // System.out.println(" mainnet, conquest"); + // System.out.println("--------------------------\n"); + // return ERROR; + // } + // + // } else { + // System.out.println("\nInvalid network selected!"); + // System.out.println("--- Available Networks ---"); + // System.out.println(" mainnet , conquest"); + // System.out.println("--------------------------\n"); + // return ERROR; + // } + } /** Print the CLI help info. */ @@ -540,7 +574,7 @@ private void printInfo(Cfg cfg) { */ private Network determineNetwork(String arg) { Network net; - switch(arg) { + switch (arg) { case "mainnet": net = Network.MAINNET; break; @@ -556,7 +590,7 @@ private Network determineNetwork(String arg) { return net; } - private void printInvalidNetwork(){ + private void printInvalidNetwork() { System.out.println("\nInvalid network selected!\n"); System.out.println("--- Available Networks ---"); System.out.println(" mainnet, conquest"); @@ -569,7 +603,7 @@ private void printInvalidNetwork(){ * @param path input to append base directory to copy to * @param net input to determine network to copy from */ - private void copyNetwork(String path, Network net) { + private void copyNetwork(String path, Network net) { File dir1 = new File(path + "/" + net + "/config"); File dir2 = new File(path + "/" + net + "/keystore"); @@ -678,8 +712,9 @@ private boolean importPrivateKey(String privateKey) { ECKey key = ECKeyFac.inst().fromPrivate(raw); if (key == null) { - System.out.println("Unable to recover private key." - + "Are you sure you did not import a public key?"); + System.out.println( + "Unable to recover private key." + + "Are you sure you did not import a public key?"); return false; } @@ -759,7 +794,7 @@ private RecoveryUtils.Status revertTo(String blockNumber) { block = Long.parseLong(blockNumber); } catch (NumberFormatException e) { System.out.println( - "The given argument <" + blockNumber + "> cannot be converted to a number."); + "The given argument <" + blockNumber + "> cannot be converted to a number."); return RecoveryUtils.Status.ILLEGAL_ARGUMENT; } @@ -768,6 +803,7 @@ private RecoveryUtils.Status revertTo(String blockNumber) { /** * Checks for illegal inputs (for datadir && network names) + * * @param value * @return */ @@ -778,21 +814,20 @@ public static boolean isValid(String value) { private void createKeystoreDirIfMissing() { if (!keystoreDir.isDirectory()) { if (!keystoreDir.mkdir()) { - System.out.println("Ssl keystore directory could not be created. " + - "Please check user permissions or create directory manually."); + System.out.println( + "Ssl keystore directory could not be created. " + + "Please check user permissions or create directory manually."); System.exit(1); } System.out.println(); } } - /** - * For security reasons we only want the ssl option to run in a console environment. - */ + /** For security reasons we only want the ssl option to run in a console environment. */ private void checkConsoleExists(Console console) { if (console == null) { System.out.println( - "No console found. This command can only be run interactively in a console environment."); + "No console found. This command can only be run interactively in a console environment."); System.exit(1); } } @@ -809,14 +844,20 @@ private String getCertName(Console console) { private String getCertPass(Console console) { int minPassLen = 7; - String certPass = String.valueOf(console.readPassword( - "Enter certificate password (at least " + minPassLen + " characters):\n")); + String certPass = + String.valueOf( + console.readPassword( + "Enter certificate password (at least " + + minPassLen + + " characters):\n")); if ((certPass == null) || (certPass.isEmpty())) { System.out.println("Error: no certificate password entered."); System.exit(1); } else if (certPass.length() < minPassLen) { System.out.println( - "Error: certificate password must be at least " + minPassLen + " characters long."); + "Error: certificate password must be at least " + + minPassLen + + " characters long."); System.exit(1); } return certPass; @@ -831,24 +872,20 @@ public String getDstGenesis() { } // Methods below taken from FileUtils class - private static boolean copyRecursively(File src, File target) - { + private static boolean copyRecursively(File src, File target) { if (src.isDirectory()) { return copyDirectoryContents(src, target); - } - else { + } else { try { Files.copy(src, target); return true; - } - catch (IOException e) { + } catch (IOException e) { return false; } } } - private static boolean copyDirectoryContents(File src, File target) - { + private static boolean copyDirectoryContents(File src, File target) { Preconditions.checkArgument(src.isDirectory(), "Source dir is not a directory: %s", src); // Don't delete symbolic link directories @@ -866,31 +903,29 @@ private static boolean copyDirectoryContents(File src, File target) return success; } - private static boolean isSymbolicLink(File file) - { + private static boolean isSymbolicLink(File file) { try { File canonicalFile = file.getCanonicalFile(); File absoluteFile = file.getAbsoluteFile(); File parentFile = file.getParentFile(); // a symbolic link has a different name between the canonical and absolute path - return !canonicalFile.getName().equals(absoluteFile.getName()) || + return !canonicalFile.getName().equals(absoluteFile.getName()) + || // or the canonical parent path is not the same as the file's parent path, // provided the file has a parent path - parentFile != null && !parentFile.getCanonicalPath().equals(canonicalFile.getParent()); - } - catch (IOException e) { + parentFile != null + && !parentFile.getCanonicalPath().equals(canonicalFile.getParent()); + } catch (IOException e) { // error on the side of caution return true; } } - private static ImmutableList listFiles(File dir) - { + private static ImmutableList listFiles(File dir) { File[] files = dir.listFiles(); if (files == null) { return ImmutableList.of(); } return ImmutableList.copyOf(files); } - } From 98570479c35cdfb302e8e2393c2f95d801e51e6e Mon Sep 17 00:00:00 2001 From: Alexandra Date: Mon, 17 Sep 2018 11:45:05 -0400 Subject: [PATCH 09/31] move network enum to separate file; added mastery networks; added unit tests --- .../org/aion/zero/impl/config/Network.java | 65 +++++++++++++++++++ .../aion/zero/impl/config/NetworkTest.java | 65 +++++++++++++++++++ 2 files changed, 130 insertions(+) create mode 100644 modAionImpl/src/org/aion/zero/impl/config/Network.java create mode 100644 modAionImpl/test/org/aion/zero/impl/config/NetworkTest.java diff --git a/modAionImpl/src/org/aion/zero/impl/config/Network.java b/modAionImpl/src/org/aion/zero/impl/config/Network.java new file mode 100644 index 0000000000..7225b455b3 --- /dev/null +++ b/modAionImpl/src/org/aion/zero/impl/config/Network.java @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2017-2018 Aion foundation. + * + * This file is part of the aion network project. + * + * The aion network project is free software: you can redistribute it + * and/or modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or any later version. + * + * The aion network project is distributed in the hope that it will + * be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with the aion network project source files. + * If not, see . + * + * Contributors: + * Aion foundation. + */ +package org.aion.zero.impl.config; + +public enum Network { + MAINNET("mainnet"), + CONQUEST("conquest"), + MASTERY("mastery"); + + private String name; + + Network(String _name) { + this.name = _name; + } + + @Override + public String toString() { + return this.name; + } + + /** + * Utility method that determines the correct network based on the given argument. + * + * @param network a string value representing the network name + * @return the network object corresponding to the string value or null when the value is not + * mapped to an object. + * @implNote There may be several test networks active. The term {@code testnet} is defined to + * correspond to the current default test network. + */ + public static Network determineNetwork(String network) { + String netStr = network.toLowerCase(); + + if (netStr.equals("testnet")) { + return Network.MASTERY; + } + + for (Network net : Network.values()) { + if (netStr.equals(net.toString())) { + return net; + } + } + + return null; + } +} diff --git a/modAionImpl/test/org/aion/zero/impl/config/NetworkTest.java b/modAionImpl/test/org/aion/zero/impl/config/NetworkTest.java new file mode 100644 index 0000000000..297187f60f --- /dev/null +++ b/modAionImpl/test/org/aion/zero/impl/config/NetworkTest.java @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2017-2018 Aion foundation. + * + * This file is part of the aion network project. + * + * The aion network project is free software: you can redistribute it + * and/or modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or any later version. + * + * The aion network project is distributed in the hope that it will + * be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with the aion network project source files. + * If not, see . + * + * Contributors: + * Aion foundation. + */ +package org.aion.zero.impl.config; + +import static com.google.common.truth.Truth.assertThat; + +import java.util.ArrayList; +import java.util.List; +import junitparams.JUnitParamsRunner; +import junitparams.Parameters; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Test functionality from {@link Network}. + * + * @author Alexandra Roatis + */ +@RunWith(JUnitParamsRunner.class) +public class NetworkTest { + + /** Parameters for testing {@link #testDetermineNetwork(String, Network)}. */ + @SuppressWarnings("unused") + private Object networkMappings() { + List parameters = new ArrayList<>(); + + for (Network net : Network.values()) { + parameters.add(new Object[] {net.toString(), net}); + parameters.add(new Object[] {net.toString().toUpperCase(), net}); + } + + parameters.add(new Object[] {"testnet", Network.MASTERY}); + parameters.add(new Object[] {"TESTNET", Network.MASTERY}); + + parameters.add(new Object[] {"undefined", null}); + + return parameters.toArray(); + } + + @Test + @Parameters(method = "networkMappings") + public void testDetermineNetwork(String input, Network expected) { + assertThat(Network.determineNetwork(input)).isEqualTo(expected); + } +} From fcfb727d7b7a9b3f06f397e8ec7655e3f0b8f389 Mon Sep 17 00:00:00 2001 From: Alexandra Date: Mon, 17 Sep 2018 14:05:51 -0400 Subject: [PATCH 10/31] added identifier to network enum --- .../org/aion/zero/impl/config/Network.java | 44 ++++++++++++++++--- .../aion/zero/impl/config/NetworkTest.java | 24 +++++++++- 2 files changed, 61 insertions(+), 7 deletions(-) diff --git a/modAionImpl/src/org/aion/zero/impl/config/Network.java b/modAionImpl/src/org/aion/zero/impl/config/Network.java index 7225b455b3..7374199ba3 100644 --- a/modAionImpl/src/org/aion/zero/impl/config/Network.java +++ b/modAionImpl/src/org/aion/zero/impl/config/Network.java @@ -22,15 +22,28 @@ */ package org.aion.zero.impl.config; +/** + * Defines different Aion networks. + * + * @author Alexandra Roatis + */ public enum Network { - MAINNET("mainnet"), - CONQUEST("conquest"), - MASTERY("mastery"); + MAINNET("mainnet", 256), + CONQUEST("conquest", 128), + MASTERY("mastery", 32); private String name; + private int identifier; - Network(String _name) { + /** + * Constructor. + * + * @param _name network name + * @param _identifier network identifier + */ + Network(String _name, int _identifier) { this.name = _name; + this.identifier = _identifier; } @Override @@ -38,8 +51,12 @@ public String toString() { return this.name; } + public int getIdentifier() { + return identifier; + } + /** - * Utility method that determines the correct network based on the given argument. + * Utility method that determines the correct network based on the given name. * * @param network a string value representing the network name * @return the network object corresponding to the string value or null when the value is not @@ -62,4 +79,21 @@ public static Network determineNetwork(String network) { return null; } + + /** + * Utility method that determines the correct network based on the given identifier. + * + * @param identifier an integer value representing the network identifier + * @return the network object corresponding to the int value or null when the value is not + * mapped to an object. + */ + public static Network determineNetwork(int identifier) { + for (Network net : Network.values()) { + if (identifier == net.getIdentifier()) { + return net; + } + } + + return null; + } } diff --git a/modAionImpl/test/org/aion/zero/impl/config/NetworkTest.java b/modAionImpl/test/org/aion/zero/impl/config/NetworkTest.java index 297187f60f..134767f3e7 100644 --- a/modAionImpl/test/org/aion/zero/impl/config/NetworkTest.java +++ b/modAionImpl/test/org/aion/zero/impl/config/NetworkTest.java @@ -41,7 +41,7 @@ public class NetworkTest { /** Parameters for testing {@link #testDetermineNetwork(String, Network)}. */ @SuppressWarnings("unused") - private Object networkMappings() { + private Object stringToNetworkMappings() { List parameters = new ArrayList<>(); for (Network net : Network.values()) { @@ -58,8 +58,28 @@ private Object networkMappings() { } @Test - @Parameters(method = "networkMappings") + @Parameters(method = "stringToNetworkMappings") public void testDetermineNetwork(String input, Network expected) { assertThat(Network.determineNetwork(input)).isEqualTo(expected); } + + /** Parameters for testing {@link #testDetermineNetwork(int, Network)}. */ + @SuppressWarnings("unused") + private Object intToNetworkMappings() { + List parameters = new ArrayList<>(); + + for (Network net : Network.values()) { + parameters.add(new Object[] {net.getIdentifier(), net}); + } + + parameters.add(new Object[] {-1, null}); + + return parameters.toArray(); + } + + @Test + @Parameters(method = "intToNetworkMappings") + public void testDetermineNetwork(int input, Network expected) { + assertThat(Network.determineNetwork(input)).isEqualTo(expected); + } } From 6566f5b6e7ca1f3548f0f0cbfe07d876ff04845b Mon Sep 17 00:00:00 2001 From: Alexandra Date: Tue, 18 Sep 2018 11:59:45 -0400 Subject: [PATCH 11/31] updated the assignment of data directory and network --- .../src/org/aion/zero/impl/cli/Cli.java | 269 ++++-------------- .../org/aion/zero/impl/config/CfgAion.java | 30 +- .../aion/zero/impl/db/AionRepositoryImpl.java | 3 +- .../test/org/aion/zero/impl/cli/CliTest.java | 252 ++++++++-------- modBoot/src/org/aion/Aion.java | 11 +- modMcf/src/org/aion/mcf/config/Cfg.java | 210 +++++++++++--- 6 files changed, 370 insertions(+), 405 deletions(-) diff --git a/modAionImpl/src/org/aion/zero/impl/cli/Cli.java b/modAionImpl/src/org/aion/zero/impl/cli/Cli.java index 027e076bd1..7da949a668 100644 --- a/modAionImpl/src/org/aion/zero/impl/cli/Cli.java +++ b/modAionImpl/src/org/aion/zero/impl/cli/Cli.java @@ -26,11 +26,16 @@ import static org.aion.zero.impl.cli.Cli.ReturnType.ERROR; import static org.aion.zero.impl.cli.Cli.ReturnType.EXIT; import static org.aion.zero.impl.cli.Cli.ReturnType.RUN; +import static org.aion.zero.impl.config.Network.determineNetwork; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; import com.google.common.io.Files; -import java.io.*; +import java.io.BufferedReader; +import java.io.Console; +import java.io.File; +import java.io.IOException; +import java.io.InputStreamReader; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -42,7 +47,7 @@ import org.aion.mcf.config.Cfg; import org.aion.mcf.config.CfgSsl; import org.aion.zero.impl.Version; -import org.aion.zero.impl.config.CfgAion; +import org.aion.zero.impl.config.Network; import org.aion.zero.impl.db.RecoveryUtils; import picocli.CommandLine; @@ -53,14 +58,9 @@ */ public class Cli { + // TODO-Ale: consider using initial path from cfg private final String BASE_PATH = System.getProperty("user.dir"); - private String BASE_PATH_WITH_NETWORK = BASE_PATH + "/config/" + CfgAion.getNetwork(); - - private String dstConfig = BASE_PATH_WITH_NETWORK + "/config.xml"; - - private String dstGenesis = BASE_PATH_WITH_NETWORK + "/genesis.json"; - File keystoreDir = new File(System.getProperty("user.dir") + File.separator + CfgSsl.SSL_KEYSTORE_DIR); @@ -82,30 +82,7 @@ public int getValue() { } } - enum Network { - MAINNET, - CONQUEST; - - @Override - public String toString() { - switch (this) { - case MAINNET: - return "mainnet"; - case CONQUEST: - return "conquest"; - default: - throw new IllegalArgumentException(); - } - } - } - - private Network net = Network.MAINNET; - public ReturnType call(final String[] args, Cfg cfg) { - return call(args, cfg, BASE_PATH); - } - - public ReturnType call(final String[] args, Cfg cfg, String path) { try { // the preprocess method handles arguments that are separated by space // parsing populates the options object @@ -143,53 +120,37 @@ public ReturnType call(final String[] args, Cfg cfg, String path) { // 2. determine the execution folder path if (options.getDirectory() != null) { - setDirectory(options.getDirectory()); + setDirectory(options.getDirectory(), cfg); // no return -> allow for other parameters combined with -d } // 3. can be influenced by the -d argument above if (options.getConfig() != null) { - String strNet = options.getConfig().toLowerCase(); - - if (strNet.isEmpty()) { - // no network given; use default path - // for compatibility with old kernels - CfgAion.setNetwork(strNet); - File dir = new File(BASE_PATH + "/config"); - if (!dir.exists()) { - dir.mkdirs(); - } - CfgAion.setConfFilePath(BASE_PATH + "/config/config.xml"); - System.out.println("\nNew config generated for default (release) network."); - } else { - net = determineNetwork(strNet); - - if (net != null) { - strNet = net.toString(); - CfgAion.setNetwork(strNet); - File dir = new File(BASE_PATH + "/config/" + net); - if (!dir.exists()) { - dir.mkdirs(); - } - CfgAion.setConfFilePath(BASE_PATH + "/config/" + strNet + "/config.xml"); - System.out.println("\nNew config generated for " + strNet + "."); - } else { - printInvalidNetwork(); - return ReturnType.ERROR; - } + // TODO-Ale test + String strNet = options.getConfig(); + + if (!strNet.isEmpty()) { + setNetwork(strNet, cfg); + } + + File dir = cfg.getExecDirectory(); + if (!dir.exists()) { + dir.mkdirs(); } cfg.fromXML(); cfg.setId(UUID.randomUUID().toString()); cfg.toXML(null); + + System.out.println("\nNew config generated at " + cfg.getExecConfigPath() + "."); return ReturnType.EXIT; } // 4. determine the network configuration if (options.getNetwork() != null) { - setNetwork(options.getConfig().toLowerCase()); + setNetwork(options.getConfig(), cfg); // no return -> allow for other parameters combined with -n } @@ -234,9 +195,9 @@ public ReturnType call(final String[] args, Cfg cfg, String path) { } if (options.getSsl() != null) { - String[] paramters = options.getSsl(); + String[] parameters = options.getSsl(); - if (paramters.length == 0 || paramters.length == 2) { + if (parameters.length == 0 || parameters.length == 2) { createKeystoreDirIfMissing(); Console console = System.console(); checkConsoleExists(console); @@ -247,7 +208,7 @@ public ReturnType call(final String[] args, Cfg cfg, String path) { scriptArgs.add(getCertName(console)); scriptArgs.add(getCertPass(console)); // add the hostname and ip optionally passed in as cli args - scriptArgs.addAll(Arrays.asList(paramters)); + scriptArgs.addAll(Arrays.asList(parameters)); new ProcessBuilder(scriptArgs).inheritIO().start().waitFor(); return EXIT; } else { @@ -403,132 +364,6 @@ public ReturnType call(final String[] args, Cfg cfg, String path) { } } - private void setDirectory(String directory) { - // TODO - // // Determines database folder path - // case "-d": - // case "--datadir": - // if ( (args.length == 2 || args.length == 4) && isValid(args[1])) { - // - // // -d [directory] - // if (args.length == 2) { - // - // copyNetwork(path + "/" + args[1], net); - // cfg.getLog().setLogPath(args[1] + "/" + net + "/log"); - // cfg.getDb().setDatabasePath(args[1] + "/" + net + "/database"); - // Keystore.setKeystorePath(path + "/" + args[1] + "/" + net + "/keystore"); - // return RUN; - // - // } - // - // // -d [directory] -n [network] - // else if (isValid(args[3])) { - // - // String[] newArgs = Arrays.copyOfRange(args, 2, args.length); - // call(newArgs, cfg); - // - // copyNetwork(path + "/" + args[1], net); - // cfg.getLog().setLogPath(args[1] + "/" + net + "/log"); - // cfg.getDb().setDatabasePath(args[1] + "/" + net + "/database"); - // Keystore.setKeystorePath(path + "/" + args[1] + "/" + net + "/keystore"); - // return RUN; - // - // } else if (!(args[2].equals("-n")||args[2].equals("--network"))) { - // System.out.println("\nInvalid multi arguments!\n"); - // printHelp(); - // return ERROR; - // - // } else { - // System.out.println("\nInvalid network selected!"); - // System.out.println("--- Available Networks ---"); - // System.out.println(" mainnet , conquest"); - // System.out.println("--------------------------\n"); - // return ERROR; - // } - // - // } else { - // System.out.println("\nInvalid datadir selected!"); - // System.out.println("Please choose valid directory name!\n"); - // return ERROR; - // } - - } - - private void setNetwork(String network) { - // TODO - // case "-n": - // case "--network": - // if ( (args.length == 2 || args.length == 4) && isValid(args[1])) { - // - // net = determineNetwork(args[1].toLowerCase()); - // - // switch (net) { - // case MAINNET: - // case CONQUEST: - // - // // -n [network] - // if (args.length == 2) { - // - // CfgAion.setNetwork(net.toString()); - // BASE_PATH_WITH_NETWORK = BASE_PATH + "/config/" + - // CfgAion.getNetwork(); - // CfgAion.setConfFilePath(BASE_PATH_WITH_NETWORK + "/config.xml"); - // CfgAion.setGenesisFilePath((BASE_PATH_WITH_NETWORK + - // "/genesis.json")); - // - // copyNetwork(path, net); - // cfg.getLog().setLogPath(net.toString() + "/log"); - // cfg.getDb().setDatabasePath(net.toString() + "/database"); - // Keystore.setKeystorePath(path + "/" + net.toString() + - // "/keystore"); - // return RUN; - // - // } - // - // // -n [network] -d [directory] - // else if ((args[2].equals("-d")||args[2].equals("--datadir")) && - // isValid(args[3])) { - // - // CfgAion.setNetwork(net.toString()); - // BASE_PATH_WITH_NETWORK = BASE_PATH + "/config/" + - // CfgAion.getNetwork(); - // CfgAion.setConfFilePath(BASE_PATH_WITH_NETWORK + "/config.xml"); - // CfgAion.setGenesisFilePath((BASE_PATH_WITH_NETWORK + - // "/genesis.json")); - // - // String[] newArgs = Arrays.copyOfRange(args, 2, args.length); - // call(newArgs, cfg); - // return RUN; - // - // } else if (!(args[2].equals("-d")||args[2].equals("--datadir"))) { - // System.out.println("\nInvalid multi arguments!\n"); - // printHelp(); - // return ERROR; - // - // } else { - // System.out.println("\nInvalid datadir selected!"); - // System.out.println("Please choose valid directory name!\n"); - // return ERROR; - // } - // - // default: - // System.out.println("\nInvalid network selected!\n"); - // System.out.println("--- Available Networks ---"); - // System.out.println(" mainnet, conquest"); - // System.out.println("--------------------------\n"); - // return ERROR; - // } - // - // } else { - // System.out.println("\nInvalid network selected!"); - // System.out.println("--- Available Networks ---"); - // System.out.println(" mainnet , conquest"); - // System.out.println("--------------------------\n"); - // return ERROR; - // } - - } - /** Print the CLI help info. */ private void printHelp() { String usage = parser.getUsageMessage(); @@ -566,35 +401,31 @@ private void printInfo(Cfg cfg) { "p2p: " + cfg.getNet().getP2p().getIp() + ":" + cfg.getNet().getP2p().getPort()); } - /** - * Determines the correct network (mainnet / conquest) enum based on argument - * - * @param arg CLI input of -n [network] - * @return Network - */ - private Network determineNetwork(String arg) { - Network net; - switch (arg) { - case "mainnet": - net = Network.MAINNET; - break; - case "testnet": - net = Network.CONQUEST; - break; - case "conquest": - net = Network.CONQUEST; - break; - default: - net = null; - } - return net; + private void setDirectory(String directory, Cfg cfg) { + // use the path ignoring the current base path + File file = new File(directory); + if (!file.isAbsolute()) { + // add the directory to the base path + file = new File(BASE_PATH, directory); + } + cfg.setExecDirectory(file); + } + + private void setNetwork(String network, Cfg cfg) { + Network net = determineNetwork(network.toLowerCase()); + if (net == null) { + // print error message and set default value + printInvalidNetwork(); + net = Network.MAINNET; + } + cfg.setNetwork(net.toString()); } private void printInvalidNetwork() { System.out.println("\nInvalid network selected!\n"); - System.out.println("--- Available Networks ---"); - System.out.println(" mainnet, conquest"); - System.out.println("--------------------------\n"); + System.out.println("------ Available Networks ------"); + System.out.println(Arrays.toString(Network.values())); + System.out.println("--------------------------------\n"); } /** @@ -617,8 +448,8 @@ private void copyNetwork(String path, Network net) { copyRecursively(src1, dst1); copyRecursively(src2, dst2); - dstConfig = dst1.toString(); - dstGenesis = dst2.toString(); + // dstConfig = dst1.toString(); + // dstGenesis = dst2.toString(); } /** @@ -864,11 +695,11 @@ private String getCertPass(Console console) { } public String getDstConfig() { - return dstConfig; + return ""; // dstConfig; } public String getDstGenesis() { - return dstGenesis; + return ""; // dstGenesis; } // Methods below taken from FileUtils class diff --git a/modAionImpl/src/org/aion/zero/impl/config/CfgAion.java b/modAionImpl/src/org/aion/zero/impl/config/CfgAion.java index 2202ecbebc..ea29e0ef1f 100644 --- a/modAionImpl/src/org/aion/zero/impl/config/CfgAion.java +++ b/modAionImpl/src/org/aion/zero/impl/config/CfgAion.java @@ -44,10 +44,7 @@ public final class CfgAion extends Cfg { private static String NETWORK = "mainnet"; - - private static String CONF_FILE_PATH = BASE_PATH + "/config/" + NETWORK + "/config.xml"; - - private static String GENESIS_FILE_PATH = BASE_PATH + "/config/" + NETWORK + "/genesis.json"; + private Network network = Network.MAINNET; protected AionGenesis genesis; @@ -57,6 +54,7 @@ public final class CfgAion extends Cfg { private static final String NODE_ID_PLACEHOLDER = "[NODE-ID-PLACEHOLDER]"; + public CfgAion() { this.mode = "aion"; this.id = UUID.randomUUID().toString(); @@ -86,7 +84,7 @@ public static void setInst(CfgAion cfgAion) { @Override public void setGenesis() { try { - this.genesis = GenesisBlockLoader.loadJSON(GENESIS_FILE_PATH); + this.genesis = GenesisBlockLoader.loadJSON(getInitialGenesisPath()); } catch (IOException | HeaderStructureException e) { System.out.println(String.format("Genesis load exception %s", e.getMessage())); System.out.println("defaulting to default AionGenesis configuration"); @@ -134,7 +132,7 @@ private void closeFileInputStream(final FileInputStream fis){ } public void dbFromXML() { - File cfgFile = new File(CONF_FILE_PATH); + File cfgFile = new File(getInitialConfigPath()); XMLInputFactory input = XMLInputFactory.newInstance(); FileInputStream fis = null; try { @@ -234,7 +232,7 @@ public boolean fromXML(final XMLStreamReader sr) throws XMLStreamException { @Override public boolean fromXML() { boolean shouldWriteBackToFile = false; - File cfgFile = new File(CONF_FILE_PATH); + File cfgFile = new File(getInitialConfigPath()); if(!cfgFile.exists()) return false; XMLInputFactory input = XMLInputFactory.newInstance(); @@ -310,7 +308,7 @@ public void toXML(final String[] args) { try { - sw = output.createXMLStreamWriter(new FileWriter(CONF_FILE_PATH)); + sw = output.createXMLStreamWriter(new FileWriter(getExecConfigPath())); sw.writeStartDocument("utf-8", "1.0"); sw.writeCharacters("\r\n"); sw.writeStartElement("aion"); @@ -355,22 +353,6 @@ public void toXML(final String[] args) { } } - public static String getNetwork() { - return NETWORK; - } - public static String getConfFilePath() { return CONF_FILE_PATH; } - public static String getGenesisFilePath() { return GENESIS_FILE_PATH; } - - public static void setNetwork(String value) { - NETWORK = value; - } - public static void setConfFilePath (String value) { - CONF_FILE_PATH = value; - } - public static void setGenesisFilePath (String value) { - GENESIS_FILE_PATH = value; - } - @Override public boolean equals(Object o) { if (this == o) return true; diff --git a/modAionImpl/src/org/aion/zero/impl/db/AionRepositoryImpl.java b/modAionImpl/src/org/aion/zero/impl/db/AionRepositoryImpl.java index 7407c2041a..eda7467332 100644 --- a/modAionImpl/src/org/aion/zero/impl/db/AionRepositoryImpl.java +++ b/modAionImpl/src/org/aion/zero/impl/db/AionRepositoryImpl.java @@ -86,8 +86,7 @@ private static class AionRepositoryImplHolder { private static final AionRepositoryImpl inst = new AionRepositoryImpl( new RepositoryConfig( - new File(config.getBasePath(), config.getDb().getPath()) - .getAbsolutePath(), + config.getDatabasePath(), ContractDetailsAion.getInstance(), config.getDb())); } diff --git a/modAionImpl/test/org/aion/zero/impl/cli/CliTest.java b/modAionImpl/test/org/aion/zero/impl/cli/CliTest.java index d7e1349db6..36f4883d35 100644 --- a/modAionImpl/test/org/aion/zero/impl/cli/CliTest.java +++ b/modAionImpl/test/org/aion/zero/impl/cli/CliTest.java @@ -1,3 +1,25 @@ +/* + * Copyright (c) 2017-2018 Aion foundation. + * + * This file is part of the aion network project. + * + * The aion network project is free software: you can redistribute it + * and/or modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or any later version. + * + * The aion network project is distributed in the hope that it will + * be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with the aion network project source files. + * If not, see . + * + * Contributors: + * Aion foundation. + */ package org.aion.zero.impl.cli; import static org.aion.zero.impl.cli.Cli.ReturnType.*; @@ -33,48 +55,48 @@ public class CliTest { String BASE_PATH = cfg.getBasePath(); - /** - * Sets up a spy Cli class that returns the String "password" when the cli.readPassword() is - * called using any two params. - */ - @Before - public void setup() throws IOException { - doReturn("password").when(cli).readPassword(any(), any()); - - // Copies config folder recursively - File src = new File(BASE_PATH + "/../modBoot/resource"); - File dst = new File(BASE_PATH + "/config"); - copyRecursively(src, dst); - - CfgAion.setConfFilePath(BASE_PATH + "/config/mainnet/config.xml"); - CfgAion.setGenesisFilePath(BASE_PATH + "/config/mainnet/genesis.json"); - Keystore.setKeystorePath(BASE_PATH + "/keystore"); - } - - @After - public void shutdown() { - // Deletes created folders recursively - File path1 = new File(BASE_PATH + "/aaaaaaaa"); - File path2 = new File(BASE_PATH + "/abbbbbbb"); - File path3 = new File(BASE_PATH + "/abcccccc"); - File path4 = new File(BASE_PATH + "/keystore"); - File path5 = new File(BASE_PATH + "/config"); - if (path1.exists() - || path2.exists() - || path3.exists() - || path4.exists() - || path5.exists()) { - deleteRecursively(path1); - deleteRecursively(path2); - deleteRecursively(path3); - deleteRecursively(path4); - deleteRecursively(path5); - } - - CfgAion.setConfFilePath(BASE_PATH + "/config/mainnet/config.xml"); - CfgAion.setGenesisFilePath(BASE_PATH + "/config/mainnet/genesis.json"); - Keystore.setKeystorePath(BASE_PATH + "/keystore"); - } +// /** +// * Sets up a spy Cli class that returns the String "password" when the cli.readPassword() is +// * called using any two params. +// */ +// @Before +// public void setup() throws IOException { +// doReturn("password").when(cli).readPassword(any(), any()); +// +// // Copies config folder recursively +// File src = new File(BASE_PATH + "/../modBoot/resource"); +// File dst = new File(BASE_PATH + "/config"); +// copyRecursively(src, dst); +// +// CfgAion.setConfFilePath(BASE_PATH + "/config/mainnet/config.xml"); +// CfgAion.setGenesisFilePath(BASE_PATH + "/config/mainnet/genesis.json"); +// Keystore.setKeystorePath(BASE_PATH + "/keystore"); +// } +// +// @After +// public void shutdown() { +// // Deletes created folders recursively +// File path1 = new File(BASE_PATH + "/aaaaaaaa"); +// File path2 = new File(BASE_PATH + "/abbbbbbb"); +// File path3 = new File(BASE_PATH + "/abcccccc"); +// File path4 = new File(BASE_PATH + "/keystore"); +// File path5 = new File(BASE_PATH + "/config"); +// if (path1.exists() +// || path2.exists() +// || path3.exists() +// || path4.exists() +// || path5.exists()) { +// deleteRecursively(path1); +// deleteRecursively(path2); +// deleteRecursively(path3); +// deleteRecursively(path4); +// deleteRecursively(path5); +// } +// +// CfgAion.setConfFilePath(BASE_PATH + "/config/mainnet/config.xml"); +// CfgAion.setGenesisFilePath(BASE_PATH + "/config/mainnet/genesis.json"); +// Keystore.setKeystorePath(BASE_PATH + "/keystore"); +// } /** Ensures that the -h and --help arguments do not fail. */ @Test @@ -97,27 +119,27 @@ public void testInfo() { assertEquals(EXIT, cli.call(new String[] {"--info"}, CfgAion.inst())); } - /** Ensures correct behavior for the -c and --config arguments. */ - @Test - public void testConfig() { - // compatibility with old kernels - assertEquals(EXIT, cli.call(new String[] {"-c"}, CfgAion.inst())); - assertEquals(EXIT, cli.call(new String[] {"--config"}, CfgAion.inst())); - - // available networks - for (Cli.Network net : Cli.Network.values()) { - assertEquals(EXIT, cli.call(new String[] {"-c", net.toString()}, CfgAion.inst())); - assertEquals(EXIT, cli.call(new String[] {"--config", net.toString()}, CfgAion.inst())); - } - - // accepted alias - assertEquals(EXIT, cli.call(new String[] {"-c", "testnet"}, CfgAion.inst())); - assertEquals(EXIT, cli.call(new String[] {"--config", "testnet"}, CfgAion.inst())); - - // incorrect value - assertEquals(ERROR, cli.call(new String[] {"-c", "random"}, CfgAion.inst())); - assertEquals(ERROR, cli.call(new String[] {"--config", "random"}, CfgAion.inst())); - } +// /** Ensures correct behavior for the -c and --config arguments. */ +// @Test +// public void testConfig() { +// // compatibility with old kernels +// assertEquals(EXIT, cli.call(new String[] {"-c"}, CfgAion.inst())); +// assertEquals(EXIT, cli.call(new String[] {"--config"}, CfgAion.inst())); +// +// // available networks +// for (Cli.Network net : Cli.Network.values()) { +// assertEquals(EXIT, cli.call(new String[] {"-c", net.toString()}, CfgAion.inst())); +// assertEquals(EXIT, cli.call(new String[] {"--config", net.toString()}, CfgAion.inst())); +// } +// +// // accepted alias +// assertEquals(EXIT, cli.call(new String[] {"-c", "testnet"}, CfgAion.inst())); +// assertEquals(EXIT, cli.call(new String[] {"--config", "testnet"}, CfgAion.inst())); +// +// // incorrect value +// assertEquals(ERROR, cli.call(new String[] {"-c", "random"}, CfgAion.inst())); +// assertEquals(ERROR, cli.call(new String[] {"--config", "random"}, CfgAion.inst())); +// } /** Tests the -a create arguments do not fail. */ @Test @@ -243,51 +265,51 @@ public void testDatadir() { printPaths(BASE_PATH, cfg, cli); } - /** - * Test the -n | --network option to see if; 1. Access the correct CONF_FILE_PATH 2. Access the - * correct GENESIS_FILE_PATH 3. Defaults correctly to "mainnet" - */ - @Test - public void testNetwork() { - - final String[][] networkArgs = - new String[][] { - {"-n", ""}, // Unspecified - {"-n", "{@.@}"}, // Invalid (illegal characters) - {"-n", "testnet", "test"}, // Invalid (number of arguments) - {"-n", "mainnet"}, // Mainnet - {"-n", "testnet"}, // Testnet - {"-n", "conquest"}, // Conquest - }; - - Cli cli = new Cli(); - - System.out.println("Invalid Networks:"); - assertEquals(ERROR, cli.call(networkArgs[0], cfg)); - assertEquals(ERROR, cli.call(networkArgs[1], cfg)); - assertEquals(ERROR, cli.call(networkArgs[2], cfg)); - - System.out.println("\nValid Network 1: " + networkArgs[3][1]); - assertEquals(RUN, cli.call(networkArgs[3], cfg)); - assertEquals("mainnet", CfgAion.getNetwork()); - assertEquals(BASE_PATH + "/config/mainnet/config.xml", CfgAion.getConfFilePath()); - assertEquals(BASE_PATH + "/config/mainnet/genesis.json", CfgAion.getGenesisFilePath()); - printPaths(BASE_PATH, cfg, cli); - - System.out.println("\nValid Network 2: " + networkArgs[4][1]); - assertEquals(RUN, cli.call(networkArgs[4], cfg)); - assertEquals("conquest", CfgAion.getNetwork()); - assertEquals(BASE_PATH + "/config/conquest/config.xml", CfgAion.getConfFilePath()); - assertEquals(BASE_PATH + "/config/conquest/genesis.json", CfgAion.getGenesisFilePath()); - printPaths(BASE_PATH, cfg, cli); - - System.out.println("\nValid Network 3: " + networkArgs[5][1]); - assertEquals(RUN, cli.call(networkArgs[5], cfg)); - assertEquals("conquest", CfgAion.getNetwork()); - assertEquals(BASE_PATH + "/config/conquest/config.xml", CfgAion.getConfFilePath()); - assertEquals(BASE_PATH + "/config/conquest/genesis.json", CfgAion.getGenesisFilePath()); - printPaths(BASE_PATH, cfg, cli); - } +// /** +// * Test the -n | --network option to see if; 1. Access the correct CONF_FILE_PATH 2. Access the +// * correct GENESIS_FILE_PATH 3. Defaults correctly to "mainnet" +// */ +// @Test +// public void testNetwork() { +// +// final String[][] networkArgs = +// new String[][] { +// {"-n", ""}, // Unspecified +// {"-n", "{@.@}"}, // Invalid (illegal characters) +// {"-n", "testnet", "test"}, // Invalid (number of arguments) +// {"-n", "mainnet"}, // Mainnet +// {"-n", "testnet"}, // Testnet +// {"-n", "conquest"}, // Conquest +// }; +// +// Cli cli = new Cli(); +// +// System.out.println("Invalid Networks:"); +// assertEquals(ERROR, cli.call(networkArgs[0], cfg)); +// assertEquals(ERROR, cli.call(networkArgs[1], cfg)); +// assertEquals(ERROR, cli.call(networkArgs[2], cfg)); +// +// System.out.println("\nValid Network 1: " + networkArgs[3][1]); +// assertEquals(RUN, cli.call(networkArgs[3], cfg)); +// assertEquals("mainnet", CfgAion.getNetwork()); +// assertEquals(BASE_PATH + "/config/mainnet/config.xml", CfgAion.getConfFilePath()); +// assertEquals(BASE_PATH + "/config/mainnet/genesis.json", CfgAion.getGenesisFilePath()); +// printPaths(BASE_PATH, cfg, cli); +// +// System.out.println("\nValid Network 2: " + networkArgs[4][1]); +// assertEquals(RUN, cli.call(networkArgs[4], cfg)); +// assertEquals("conquest", CfgAion.getNetwork()); +// assertEquals(BASE_PATH + "/config/conquest/config.xml", CfgAion.getConfFilePath()); +// assertEquals(BASE_PATH + "/config/conquest/genesis.json", CfgAion.getGenesisFilePath()); +// printPaths(BASE_PATH, cfg, cli); +// +// System.out.println("\nValid Network 3: " + networkArgs[5][1]); +// assertEquals(RUN, cli.call(networkArgs[5], cfg)); +// assertEquals("conquest", CfgAion.getNetwork()); +// assertEquals(BASE_PATH + "/config/conquest/config.xml", CfgAion.getConfFilePath()); +// assertEquals(BASE_PATH + "/config/conquest/genesis.json", CfgAion.getGenesisFilePath()); +// printPaths(BASE_PATH, cfg, cli); +// } @Test public void testMultiNetworkDatadir() { @@ -310,7 +332,7 @@ public void testMultiNetworkDatadir() { System.out.println("\nNew 1: " + multiArgs[0][1] + " " + multiArgs[0][3]); assertEquals(RUN, cli.call(multiArgs[0], cfg)); - assertEquals("mainnet", CfgAion.getNetwork()); + assertEquals("mainnet", cfg.getNetwork()); assertEquals("aaaaaaaa/mainnet/log", cfg.getLog().getLogPath()); assertEquals("aaaaaaaa/mainnet/database", cfg.getDb().getPath()); assertEquals(BASE_PATH + "/aaaaaaaa/mainnet/keystore", Keystore.getKeystorePath()); @@ -318,7 +340,7 @@ public void testMultiNetworkDatadir() { System.out.println("\nNew 2: " + multiArgs[1][1] + " " + multiArgs[1][3]); assertEquals(RUN, cli.call(multiArgs[1], cfg)); - assertEquals("conquest", CfgAion.getNetwork()); + assertEquals("conquest", cfg.getNetwork()); assertEquals("abbbbbbb/conquest/log", cfg.getLog().getLogPath()); assertEquals("abbbbbbb/conquest/database", cfg.getDb().getPath()); assertEquals(BASE_PATH + "/abbbbbbb/conquest/keystore", Keystore.getKeystorePath()); @@ -326,7 +348,7 @@ public void testMultiNetworkDatadir() { System.out.println("\nNew 3: " + multiArgs[2][1] + " " + multiArgs[2][3]); assertEquals(RUN, cli.call(multiArgs[2], cfg)); - assertEquals("conquest", CfgAion.getNetwork()); + assertEquals("conquest", cfg.getNetwork()); assertEquals("abcccccc/conquest/log", cfg.getLog().getLogPath()); assertEquals("abcccccc/conquest/database", cfg.getDb().getPath()); assertEquals(BASE_PATH + "/abcccccc/conquest/keystore", Keystore.getKeystorePath()); @@ -334,7 +356,7 @@ public void testMultiNetworkDatadir() { System.out.println("\n Exist 1: " + multiArgs[3][1] + " " + multiArgs[3][3]); assertEquals(RUN, cli.call(multiArgs[3], cfg)); - assertEquals("conquest", CfgAion.getNetwork()); + assertEquals("conquest", cfg.getNetwork()); assertEquals("aaaaaaaa/conquest/log", cfg.getLog().getLogPath()); assertEquals("aaaaaaaa/conquest/database", cfg.getDb().getPath()); assertEquals(BASE_PATH + "/aaaaaaaa/conquest/keystore", Keystore.getKeystorePath()); @@ -342,7 +364,7 @@ public void testMultiNetworkDatadir() { System.out.println("\nExist 2: " + multiArgs[4][1] + " " + multiArgs[4][3]); assertEquals(RUN, cli.call(multiArgs[4], cfg)); - assertEquals("conquest", CfgAion.getNetwork()); + assertEquals("conquest", cfg.getNetwork()); assertEquals("aaaaaaaa/conquest/log", cfg.getLog().getLogPath()); assertEquals("aaaaaaaa/conquest/database", cfg.getDb().getPath()); assertEquals(BASE_PATH + "/aaaaaaaa/conquest/keystore", Keystore.getKeystorePath()); @@ -374,9 +396,9 @@ private void printPaths(String BASE_PATH, Cfg cfg, Cli cli) { + cli.getDstGenesis() + "\n----------------------------------------------------------------------------" + "\n> Config read: " - + CfgAion.getConfFilePath() + + cfg.getInitialConfigPath() + "\n> Genesis read: " - + CfgAion.getGenesisFilePath() + + cfg.getInitialGenesisPath() + "\n----------------------------------------------------------------------------\n\n"); } diff --git a/modBoot/src/org/aion/Aion.java b/modBoot/src/org/aion/Aion.java index c8ac93e2f3..3a62ad19f1 100644 --- a/modBoot/src/org/aion/Aion.java +++ b/modBoot/src/org/aion/Aion.java @@ -129,6 +129,7 @@ public static void main(String args[]) { .init(cfg.getLog().getModules(), cfg.getLog().getLogFile(), cfg.getLog().getLogPath()); Logger genLog = AionLoggerFactory.getLogger(LogEnum.GEN.name()); + // TODO-Ale: fix String[] filePath = new String[7]; // Log/Database path if (!cfg.getLog().getLogFile()) { @@ -141,10 +142,10 @@ public static void main(String args[]) { } filePath[1] = cfg.getBasePath() + "/" + cfg.getDb().getPath(); filePath[2] = Keystore.getKeystorePath(); - filePath[3] = new Cli().getDstConfig(); - filePath[4] = new Cli().getDstGenesis(); - filePath[5] = CfgAion.getConfFilePath(); - filePath[6] = CfgAion.getGenesisFilePath(); + filePath[3] = cfg.getExecConfigPath(); + filePath[4] = cfg.getExecGenesisPath(); + filePath[5] = cfg.getInitialConfigPath(); + filePath[6] = cfg.getInitialGenesisPath(); String path = "\n-------------------------------- USED PATHS --------------------------------" + @@ -167,7 +168,7 @@ public static void main(String args[]) { // always print the version string in the center of the Aion logo String versionStr = "v"+KERNEL_VERSION; - String networkStr = CfgAion.getNetwork(); + String networkStr = cfg.getNetwork().toString(); logo = appendLogo(logo, versionStr); logo = appendLogo(logo, networkStr); diff --git a/modMcf/src/org/aion/mcf/config/Cfg.java b/modMcf/src/org/aion/mcf/config/Cfg.java index 449db98757..f29ef8eadc 100644 --- a/modMcf/src/org/aion/mcf/config/Cfg.java +++ b/modMcf/src/org/aion/mcf/config/Cfg.java @@ -1,41 +1,51 @@ /* * Copyright (c) 2017-2018 Aion foundation. * - * This file is part of the aion network project. + * This file is part of the aion network project. * - * The aion network project is free software: you can redistribute it - * and/or modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation, either version 3 of - * the License, or any later version. + * The aion network project is free software: you can redistribute it + * and/or modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or any later version. * - * The aion network project is distributed in the hope that it will - * be useful, but WITHOUT ANY WARRANTY; without even the implied - * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * See the GNU General Public License for more details. + * The aion network project is distributed in the hope that it will + * be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. * - * You should have received a copy of the GNU General Public License - * along with the aion network project source files. - * If not, see . - * - * Contributors to the aion source files in decreasing order of code volume: - * - * Aion foundation. + * You should have received a copy of the GNU General Public License + * along with the aion network project source files. + * If not, see . * + * Contributors: + * Aion foundation. */ - package org.aion.mcf.config; -import org.aion.mcf.types.AbstractBlock; - +import java.io.File; import javax.xml.stream.XMLStreamException; import javax.xml.stream.XMLStreamReader; +import org.aion.mcf.types.AbstractBlock; -/** - * @author chris - */ +/** @author chris */ public abstract class Cfg { - protected static final String BASE_PATH = System.getProperty("user.dir"); + private final String configFile = "config.xml"; + private final String genesisFile = "genesis.json"; + private final String keystoreDirectory = "keystore"; + private final String configDirectory = "config"; + + private File logDirectory = null; + private File databaseDirectory = null; + private File execDirectory = null; + + private String network = null; + + protected String BASE_PATH = System.getProperty("user.dir"); + protected final String INITIAL_PATH = System.getProperty("user.dir"); + + private boolean useOldSetup = false; + private boolean ignoreOldSetup = false; protected String mode; @@ -59,7 +69,7 @@ public abstract class Cfg { protected CfgGui gui; - public void setId(final String _id){ + public void setId(final String _id) { this.id = _id; } @@ -127,7 +137,6 @@ public String[] getNodes() { return this.net.getNodes(); } - public CfgConsensus getConsensus() { return this.consensus; } @@ -136,11 +145,134 @@ public void setConsensus(CfgConsensus _consensus) { this.consensus = _consensus; } + /** @return the base dir where all configuration + persistence is managed */ + public String getBasePath() { + return getExecDirectory().getAbsolutePath(); + } + + /** Returns the directory location where the kernel configuration and persistence is managed. */ + public File getExecDirectory() { + if (!ignoreOldSetup && useOldSetup) { + return new File(INITIAL_PATH); + } else { + return new File(execDirectory, network.toString()); + } + } + /** - * @return the base dir where all configuration + persistance is managed + * Sets the directory where the kernel data containing setup and execution information will be + * stored. + * + * @param _execDirectory the directory chosen for execution + * @implNote Using this method overwrites the use of old kernel setup. */ - public String getBasePath() { - return BASE_PATH; + public void setExecDirectory(File _execDirectory) { + this.execDirectory = _execDirectory; + this.ignoreOldSetup = true; + } + + /** + * Sets the network to be used by the kernel. + * + * @param _network the network chosen for execution + * @implNote Using this method overwrites the use of old kernel setup. + */ + public void setNetwork(String _network) { + this.network = _network; + this.ignoreOldSetup = true; + } + + public String getNetwork() { + return network; + } + + public String getLogPath() { + if (logDirectory != null) { + // set when using absolute paths + return logDirectory.getAbsolutePath(); + } else { + return new File(getExecDirectory(), getLog().getLogPath()).getAbsolutePath(); + } + } + + public void setLogDirectory(File _logDirectory) { + this.logDirectory = _logDirectory; + } + + public String getDatabasePath() { + if (databaseDirectory != null) { + // set when using absolute paths + return databaseDirectory.getAbsolutePath(); + } else { + return new File(getExecDirectory(), getDb().getPath()).getAbsolutePath(); + } + } + + public void setDatabaseDirectory(File _databaseDirectory) { + this.databaseDirectory = _databaseDirectory; + } + + public String getKeystorePath() { + // todo-Ale + return new File(getBasePath(), keystoreDirectory).getAbsolutePath(); + } + + /** Returns the configuration directory location for the kernel execution. */ + public File getExecConfigDirectory() { + // TODO check different input + return new File(getExecDirectory(), configDirectory); + } + + /** Returns the location where the config file is saved for kernel execution. */ + public String getExecConfigPath() { + return new File(getExecConfigDirectory(), configFile).getAbsolutePath(); + } + + /** Returns the location where the genesis file is saved for kernel execution. */ + public String getExecGenesisPath() { + return new File(getExecConfigDirectory(), genesisFile).getAbsolutePath(); + } + + /** + * Returns the configuration directory location before the multi-config changes made to + * the kernel. + */ + private File getOldConfigDirectory() { + return new File(INITIAL_PATH, configDirectory); + } + + /** + * Returns the configuration directory location after the multi-config changes made to + * the kernel. + */ + private File getNewConfigDirectory() { + return new File(getOldConfigDirectory(), network.toString()); + } + + /** @implNote Maintains the old setup if the config file is present in the old location. */ + public String getInitialConfigPath() { + // TODO-Ale: may want to consider exec path as well + // use old config location for compatibility with old kernels + File config = new File(getOldConfigDirectory(), configFile); + + if (!config.exists()) { + config = new File(getNewConfigDirectory(), configFile); + } else { + useOldSetup = true; + } + + return config.getAbsolutePath(); + } + + public String getInitialGenesisPath() { + // use old genesis location for compatibility with old kernels + File genesis = new File(getOldConfigDirectory(), genesisFile); + + if (!genesis.exists()) { + genesis = new File(getNewConfigDirectory(), genesisFile); + } + + return genesis.getAbsolutePath(); } public static String readValue(final XMLStreamReader sr) throws XMLStreamException { @@ -149,11 +281,11 @@ public static String readValue(final XMLStreamReader sr) throws XMLStreamExcepti while (sr.hasNext()) { int eventType = sr.next(); switch (eventType) { - case XMLStreamReader.CHARACTERS: - str.append(sr.getText()); - break; - case XMLStreamReader.END_ELEMENT: - break readLoop; + case XMLStreamReader.CHARACTERS: + str.append(sr.getText()); + break; + case XMLStreamReader.END_ELEMENT: + break readLoop; } } return str.toString(); @@ -164,16 +296,15 @@ public static void skipElement(final XMLStreamReader sr) throws XMLStreamExcepti while (sr.hasNext()) { int eventType = sr.next(); switch (eventType) { - case XMLStreamReader.END_ELEMENT: - break skipLoop; + case XMLStreamReader.END_ELEMENT: + break skipLoop; } } } /** - * @return boolean - * use return to determine if also need to write back - * to file with current config + * @return boolean value used return to also determine if we need to write back to file with + * current config */ public abstract boolean fromXML(); @@ -182,5 +313,4 @@ public static void skipElement(final XMLStreamReader sr) throws XMLStreamExcepti public abstract void setGenesis(); public abstract AbstractBlock getGenesis(); - -} \ No newline at end of file +} From cfbd771246607ee05758545b161a40d361dd472e Mon Sep 17 00:00:00 2001 From: Alexandra Date: Wed, 19 Sep 2018 11:44:33 -0400 Subject: [PATCH 12/31] debugging and unit tests for help, version and datadir; functionality to load config from given file --- .../src/org/aion/zero/impl/cli/Cli.java | 25 +- .../org/aion/zero/impl/config/CfgAion.java | 14 +- .../test/org/aion/zero/impl/cli/CliTest.java | 393 ++++++++++-------- modMcf/src/org/aion/mcf/config/Cfg.java | 18 +- 4 files changed, 271 insertions(+), 179 deletions(-) diff --git a/modAionImpl/src/org/aion/zero/impl/cli/Cli.java b/modAionImpl/src/org/aion/zero/impl/cli/Cli.java index 7da949a668..f63efc5b90 100644 --- a/modAionImpl/src/org/aion/zero/impl/cli/Cli.java +++ b/modAionImpl/src/org/aion/zero/impl/cli/Cli.java @@ -120,7 +120,9 @@ public ReturnType call(final String[] args, Cfg cfg) { // 2. determine the execution folder path if (options.getDirectory() != null) { - setDirectory(options.getDirectory(), cfg); + if (!setDirectory(options.getDirectory(), cfg)) { + return ERROR; + } // no return -> allow for other parameters combined with -d } @@ -401,14 +403,31 @@ private void printInfo(Cfg cfg) { "p2p: " + cfg.getNet().getP2p().getIp() + ":" + cfg.getNet().getP2p().getPort()); } - private void setDirectory(String directory, Cfg cfg) { + /** + * Sets the directory where the kernel will be executed. + * + * @param directory the directory to be used + * @param cfg the configuration file containing the information + * @return {@code true} when the given directory is valid, {@code false} otherwise. + */ + private boolean setDirectory(String directory, Cfg cfg) { // use the path ignoring the current base path File file = new File(directory); if (!file.isAbsolute()) { // add the directory to the base path file = new File(BASE_PATH, directory); } - cfg.setExecDirectory(file); + + if (!file.exists()) { + file.mkdirs(); + } + + if (file != null && file.isDirectory() && file.canWrite()) { + cfg.setExecDirectory(file); + return true; + } else { + return false; + } } private void setNetwork(String network, Cfg cfg) { diff --git a/modAionImpl/src/org/aion/zero/impl/config/CfgAion.java b/modAionImpl/src/org/aion/zero/impl/config/CfgAion.java index ea29e0ef1f..23973758f5 100644 --- a/modAionImpl/src/org/aion/zero/impl/config/CfgAion.java +++ b/modAionImpl/src/org/aion/zero/impl/config/CfgAion.java @@ -26,12 +26,10 @@ package org.aion.zero.impl.config; import java.io.*; - import java.util.ArrayList; import java.util.List; import java.util.UUID; import javax.xml.stream.*; - import com.google.common.base.Objects; import org.aion.mcf.config.*; import org.aion.zero.exceptions.HeaderStructureException; @@ -231,10 +229,18 @@ public boolean fromXML(final XMLStreamReader sr) throws XMLStreamException { @Override public boolean fromXML() { + return fromXML(new File(getInitialConfigPath())); + } + + @Override + public boolean fromXML(File cfgFile) { + // resets internal data containing network and path + this.resetInternal(); + boolean shouldWriteBackToFile = false; - File cfgFile = new File(getInitialConfigPath()); - if(!cfgFile.exists()) + if (!cfgFile.exists()) { return false; + } XMLInputFactory input = XMLInputFactory.newInstance(); FileInputStream fis; try { diff --git a/modAionImpl/test/org/aion/zero/impl/cli/CliTest.java b/modAionImpl/test/org/aion/zero/impl/cli/CliTest.java index 36f4883d35..48475c467e 100644 --- a/modAionImpl/test/org/aion/zero/impl/cli/CliTest.java +++ b/modAionImpl/test/org/aion/zero/impl/cli/CliTest.java @@ -22,10 +22,11 @@ */ package org.aion.zero.impl.cli; -import static org.aion.zero.impl.cli.Cli.ReturnType.*; +import static com.google.common.truth.Truth.assertThat; +import static org.aion.zero.impl.cli.Cli.ReturnType.ERROR; +import static org.aion.zero.impl.cli.Cli.ReturnType.EXIT; +import static org.aion.zero.impl.cli.Cli.ReturnType.RUN; import static org.junit.Assert.assertEquals; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.doReturn; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; @@ -36,113 +37,195 @@ import java.nio.file.Path; import java.nio.file.SimpleFileVisitor; import java.nio.file.attribute.BasicFileAttributes; +import java.util.ArrayList; +import java.util.List; +import junitparams.JUnitParamsRunner; +import junitparams.Parameters; import org.aion.base.util.Hex; import org.aion.crypto.ECKey; import org.aion.crypto.ECKeyFac; import org.aion.mcf.account.Keystore; import org.aion.mcf.config.Cfg; +import org.aion.zero.impl.cli.Cli.ReturnType; import org.aion.zero.impl.config.CfgAion; import org.junit.After; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; -import org.mockito.Mockito; +import org.junit.runner.RunWith; +/** CliTest for new version with use of different networks. */ +@RunWith(JUnitParamsRunner.class) public class CliTest { - private static final Cli cli = Mockito.spy(new Cli()); - - CfgAion cfg = CfgAion.inst(); - - String BASE_PATH = cfg.getBasePath(); - -// /** -// * Sets up a spy Cli class that returns the String "password" when the cli.readPassword() is -// * called using any two params. -// */ -// @Before -// public void setup() throws IOException { -// doReturn("password").when(cli).readPassword(any(), any()); -// -// // Copies config folder recursively -// File src = new File(BASE_PATH + "/../modBoot/resource"); -// File dst = new File(BASE_PATH + "/config"); -// copyRecursively(src, dst); -// -// CfgAion.setConfFilePath(BASE_PATH + "/config/mainnet/config.xml"); -// CfgAion.setGenesisFilePath(BASE_PATH + "/config/mainnet/genesis.json"); -// Keystore.setKeystorePath(BASE_PATH + "/keystore"); -// } -// -// @After -// public void shutdown() { -// // Deletes created folders recursively -// File path1 = new File(BASE_PATH + "/aaaaaaaa"); -// File path2 = new File(BASE_PATH + "/abbbbbbb"); -// File path3 = new File(BASE_PATH + "/abcccccc"); -// File path4 = new File(BASE_PATH + "/keystore"); -// File path5 = new File(BASE_PATH + "/config"); -// if (path1.exists() -// || path2.exists() -// || path3.exists() -// || path4.exists() -// || path5.exists()) { -// deleteRecursively(path1); -// deleteRecursively(path2); -// deleteRecursively(path3); -// deleteRecursively(path4); -// deleteRecursively(path5); -// } -// -// CfgAion.setConfFilePath(BASE_PATH + "/config/mainnet/config.xml"); -// CfgAion.setGenesisFilePath(BASE_PATH + "/config/mainnet/genesis.json"); -// Keystore.setKeystorePath(BASE_PATH + "/keystore"); -// } + private Cli cli = new Cli(); + private CfgAion cfg = CfgAion.inst(); + + private static final String BASE_PATH = System.getProperty("user.dir"); + private static final String module = "modAionImpl"; + private static final String initialConfigFile = "test_resources/config.xml"; + private static final String dataDirectory = "datadir"; + private static final File path = new File(BASE_PATH, dataDirectory); + + private static File config = + BASE_PATH.contains(module) + ? new File(BASE_PATH, initialConfigFile) + : new File(BASE_PATH, module + "/" + initialConfigFile); + + @Before + public void setup() { + // reset config values + cfg.fromXML(config); + } + + @After + public void shutdown() { + deleteRecursively(path); + } /** Ensures that the -h and --help arguments do not fail. */ @Test public void testHelp() { - assertEquals(EXIT, cli.call(new String[] {"-h"}, CfgAion.inst())); - assertEquals(EXIT, cli.call(new String[] {"--help"}, CfgAion.inst())); + assertThat(cli.call(new String[] {"-h"}, cfg)).isEqualTo(EXIT); + assertThat(cli.call(new String[] {"--help"}, cfg)).isEqualTo(EXIT); } /** Ensures that the -v and --version arguments do not fail. */ @Test public void testVersion() { - assertEquals(EXIT, cli.call(new String[] {"-v"}, CfgAion.inst())); - assertEquals(EXIT, cli.call(new String[] {"--version"}, CfgAion.inst())); + assertThat(cli.call(new String[] {"-v"}, cfg)).isEqualTo(EXIT); + assertThat(cli.call(new String[] {"--version"}, cfg)).isEqualTo(EXIT); + } + + /** Parameters for testing {@link #testDirectory(String[], ReturnType, String)}. */ + @SuppressWarnings("unused") + private Object parametersWithDatadir() { + List parameters = new ArrayList<>(); + + String[] options = new String[] {"-d", "--datadir"}; + String expected = new File(path, "mainnet").getAbsolutePath(); + + for (String op : options) { + // with relative path + parameters.add(new Object[] {new String[] {op, dataDirectory}, RUN, expected}); + // with absolute path + parameters.add(new Object[] {new String[] {op, path.getAbsolutePath()}, RUN, expected}); + // without value + parameters.add(new Object[] {new String[] {op}, ERROR, BASE_PATH}); + // with invalid characters (Linux & Win) + parameters.add(new Object[] {new String[] {op, "/\\<>:\"|?*"}, ERROR, BASE_PATH}); + } + + // setup for subdirectories + String dir = dataDirectory + "/subfolder"; + File path = new File(BASE_PATH, dir); + expected = new File(path, "mainnet").getAbsolutePath(); + + for (String op : options) { + // with relative path with subdirectories + parameters.add(new Object[] {new String[] {op, dir}, RUN, expected}); + // with multiple values + parameters.add(new Object[] {new String[] {op, dataDirectory, dir}, ERROR, BASE_PATH}); + } + + return parameters.toArray(); + } + + /** Ensures that the -d and --datadir arguments do not fail. */ + @Test + @Parameters(method = "parametersWithDatadir") + public void testDirectory(String[] input, ReturnType expectedReturn, String expectedPath) { + assertThat(cli.call(input, cfg)).isEqualTo(expectedReturn); + assertThat(cfg.getBasePath()).isEqualTo(expectedPath); + assertThat(cfg.getExecConfigPath()) + .isEqualTo(new File(expectedPath, "config/config.xml").getAbsolutePath()); + assertThat(cfg.getExecGenesisPath()) + .isEqualTo(new File(expectedPath, "config/genesis.json").getAbsolutePath()); + assertThat(cfg.getDatabasePath()) + .isEqualTo(new File(expectedPath, "database").getAbsolutePath()); + assertThat(cfg.getLogPath()).isEqualTo(new File(expectedPath, "log").getAbsolutePath()); + assertThat(cfg.getKeystorePath()) + .isEqualTo(new File(expectedPath, "keystore").getAbsolutePath()); } /** Ensures that the -i and --info arguments do not fail. */ @Test + @Ignore public void testInfo() { - assertEquals(EXIT, cli.call(new String[] {"-i"}, CfgAion.inst())); - assertEquals(EXIT, cli.call(new String[] {"--info"}, CfgAion.inst())); + assertThat(cli.call(new String[] {"-i"}, cfg)).isEqualTo(EXIT); + assertThat(cli.call(new String[] {"--info"}, cfg)).isEqualTo(EXIT); } -// /** Ensures correct behavior for the -c and --config arguments. */ -// @Test -// public void testConfig() { -// // compatibility with old kernels -// assertEquals(EXIT, cli.call(new String[] {"-c"}, CfgAion.inst())); -// assertEquals(EXIT, cli.call(new String[] {"--config"}, CfgAion.inst())); -// -// // available networks -// for (Cli.Network net : Cli.Network.values()) { -// assertEquals(EXIT, cli.call(new String[] {"-c", net.toString()}, CfgAion.inst())); -// assertEquals(EXIT, cli.call(new String[] {"--config", net.toString()}, CfgAion.inst())); -// } -// -// // accepted alias -// assertEquals(EXIT, cli.call(new String[] {"-c", "testnet"}, CfgAion.inst())); -// assertEquals(EXIT, cli.call(new String[] {"--config", "testnet"}, CfgAion.inst())); -// -// // incorrect value -// assertEquals(ERROR, cli.call(new String[] {"-c", "random"}, CfgAion.inst())); -// assertEquals(ERROR, cli.call(new String[] {"--config", "random"}, CfgAion.inst())); -// } + // /** + // * Sets up a spy Cli class that returns the String "password" when the cli.readPassword() + // is + // * called using any two params. + // */ + // @Before + // public void setup() throws IOException { + // doReturn("password").when(cli).readPassword(any(), any()); + // + // // Copies config folder recursively + // File src = new File(BASE_PATH + "/../modBoot/resource"); + // File dst = new File(BASE_PATH + "/config"); + // copyRecursively(src, dst); + // + // CfgAion.setConfFilePath(BASE_PATH + "/config/mainnet/config.xml"); + // CfgAion.setGenesisFilePath(BASE_PATH + "/config/mainnet/genesis.json"); + // Keystore.setKeystorePath(BASE_PATH + "/keystore"); + // } + // + // @After + // public void shutdown() { + // // Deletes created folders recursively + // File path1 = new File(BASE_PATH + "/aaaaaaaa"); + // File path2 = new File(BASE_PATH + "/abbbbbbb"); + // File path3 = new File(BASE_PATH + "/abcccccc"); + // File path4 = new File(BASE_PATH + "/keystore"); + // File path5 = new File(BASE_PATH + "/config"); + // if (path1.exists() + // || path2.exists() + // || path3.exists() + // || path4.exists() + // || path5.exists()) { + // deleteRecursively(path1); + // deleteRecursively(path2); + // deleteRecursively(path3); + // deleteRecursively(path4); + // deleteRecursively(path5); + // } + // + // CfgAion.setConfFilePath(BASE_PATH + "/config/mainnet/config.xml"); + // CfgAion.setGenesisFilePath(BASE_PATH + "/config/mainnet/genesis.json"); + // Keystore.setKeystorePath(BASE_PATH + "/keystore"); + // } + + // /** Ensures correct behavior for the -c and --config arguments. */ + // @Test + // public void testConfig() { + // // compatibility with old kernels + // assertEquals(EXIT, cli.call(new String[] {"-c"}, CfgAion.inst())); + // assertEquals(EXIT, cli.call(new String[] {"--config"}, CfgAion.inst())); + // + // // available networks + // for (Cli.Network net : Cli.Network.values()) { + // assertEquals(EXIT, cli.call(new String[] {"-c", net.toString()}, CfgAion.inst())); + // assertEquals(EXIT, cli.call(new String[] {"--config", net.toString()}, + // CfgAion.inst())); + // } + // + // // accepted alias + // assertEquals(EXIT, cli.call(new String[] {"-c", "testnet"}, CfgAion.inst())); + // assertEquals(EXIT, cli.call(new String[] {"--config", "testnet"}, CfgAion.inst())); + // + // // incorrect value + // assertEquals(ERROR, cli.call(new String[] {"-c", "random"}, CfgAion.inst())); + // assertEquals(ERROR, cli.call(new String[] {"--config", "random"}, CfgAion.inst())); + // } /** Tests the -a create arguments do not fail. */ @Test + @Ignore public void testCreateAccount() { String args[] = {"-a", "create"}; assertEquals(EXIT, cli.call(args, CfgAion.inst())); @@ -150,6 +233,7 @@ public void testCreateAccount() { /** Tests the -a list arguments do not fail. */ @Test + @Ignore public void testListAccounts() { String args[] = {"-a", "list"}; assertEquals(EXIT, cli.call(args, CfgAion.inst())); @@ -157,6 +241,7 @@ public void testListAccounts() { /** Tests the -a export arguments do not fail on a valid account. */ @Test + @Ignore public void testExportPrivateKey() { String account = Keystore.create("password"); @@ -169,6 +254,7 @@ public void testExportPrivateKey() { * valid account. */ @Test + @Ignore public void testExportSubstringOfAccount() { String account = Keystore.create("password"); String substrAcc = account.substring(1); @@ -179,6 +265,7 @@ public void testExportSubstringOfAccount() { /** Tests the -a import arguments do not fail on a fail import key. */ @Test + @Ignore public void testImportPrivateKey() { ECKey key = ECKeyFac.inst().create(); @@ -188,6 +275,7 @@ public void testImportPrivateKey() { /** Tests the -a import arguments fail when a non-private key is supplied. */ @Test + @Ignore public void testImportNonPrivateKey() { String account = Keystore.create("password"); @@ -197,6 +285,7 @@ public void testImportNonPrivateKey() { /** Tests the -a import arguments do not fail when a valid private key is supplied. */ @Test + @Ignore public void testImportPrivateKey2() { ECKey key = ECKeyFac.inst().create(); System.out.println("Original address : " + Hex.toHexString(key.getAddress())); @@ -214,104 +303,64 @@ public void testImportPrivateKey2() { /** Tests the -a import arguments fail given an invalid private key. */ @Test + @Ignore public void testImportPrivateKeyWrong() { String[] args = {"-a", "import", "hello"}; assertEquals(ERROR, cli.call(args, CfgAion.inst())); } - /** - * Test the -d | --datadir option to see if; 1. Access the correct dbPath 2. Defaults correctly - * to "database" - */ - @Test - public void testDatadir() { - - final String[][] networkArgs = - new String[][] { - {"-d", ""}, // Unspecified - {"-d", "{@.@}"}, // Invalid (illegal characters) - {"-d", "test_db", "test"}, // Invalid (number of arguments) - {"-d", "aaaaaaaa"}, // Valid - {"-d", "abbbbbbb"}, // Valid - }; - - Cli cli = new Cli(); - - String logOG = cfg.getLog().getLogPath(); - String dbOG = cfg.getDb().getPath(); - System.out.println(logOG + " " + dbOG); - - System.out.println("Invalid Datadir:"); - assertEquals(ERROR, cli.call(networkArgs[0], cfg)); - assertEquals(ERROR, cli.call(networkArgs[1], cfg)); - assertEquals(ERROR, cli.call(networkArgs[2], cfg)); - - cfg.getLog().setLogPath(logOG); - cfg.getDb().setPath(dbOG); - System.out.println(cfg.getLog().getLogPath() + " " + cfg.getDb().getPath()); - - System.out.println("\nValid Datadir 1: " + networkArgs[3][1]); - assertEquals(RUN, cli.call(networkArgs[3], cfg)); - assertEquals("aaaaaaaa/mainnet/database", cfg.getDb().getPath()); - assertEquals("aaaaaaaa/mainnet/log", cfg.getLog().getLogPath()); - assertEquals(BASE_PATH + "/aaaaaaaa/mainnet/keystore", Keystore.getKeystorePath()); - printPaths(BASE_PATH, cfg, cli); - - System.out.println("\nValid Datadir 2: " + networkArgs[4][1]); - assertEquals(RUN, cli.call(networkArgs[4], cfg)); - assertEquals("abbbbbbb/mainnet/database", cfg.getDb().getPath()); - assertEquals("abbbbbbb/mainnet/log", cfg.getLog().getLogPath()); - assertEquals(BASE_PATH + "/abbbbbbb/mainnet/keystore", Keystore.getKeystorePath()); - printPaths(BASE_PATH, cfg, cli); - } - -// /** -// * Test the -n | --network option to see if; 1. Access the correct CONF_FILE_PATH 2. Access the -// * correct GENESIS_FILE_PATH 3. Defaults correctly to "mainnet" -// */ -// @Test -// public void testNetwork() { -// -// final String[][] networkArgs = -// new String[][] { -// {"-n", ""}, // Unspecified -// {"-n", "{@.@}"}, // Invalid (illegal characters) -// {"-n", "testnet", "test"}, // Invalid (number of arguments) -// {"-n", "mainnet"}, // Mainnet -// {"-n", "testnet"}, // Testnet -// {"-n", "conquest"}, // Conquest -// }; -// -// Cli cli = new Cli(); -// -// System.out.println("Invalid Networks:"); -// assertEquals(ERROR, cli.call(networkArgs[0], cfg)); -// assertEquals(ERROR, cli.call(networkArgs[1], cfg)); -// assertEquals(ERROR, cli.call(networkArgs[2], cfg)); -// -// System.out.println("\nValid Network 1: " + networkArgs[3][1]); -// assertEquals(RUN, cli.call(networkArgs[3], cfg)); -// assertEquals("mainnet", CfgAion.getNetwork()); -// assertEquals(BASE_PATH + "/config/mainnet/config.xml", CfgAion.getConfFilePath()); -// assertEquals(BASE_PATH + "/config/mainnet/genesis.json", CfgAion.getGenesisFilePath()); -// printPaths(BASE_PATH, cfg, cli); -// -// System.out.println("\nValid Network 2: " + networkArgs[4][1]); -// assertEquals(RUN, cli.call(networkArgs[4], cfg)); -// assertEquals("conquest", CfgAion.getNetwork()); -// assertEquals(BASE_PATH + "/config/conquest/config.xml", CfgAion.getConfFilePath()); -// assertEquals(BASE_PATH + "/config/conquest/genesis.json", CfgAion.getGenesisFilePath()); -// printPaths(BASE_PATH, cfg, cli); -// -// System.out.println("\nValid Network 3: " + networkArgs[5][1]); -// assertEquals(RUN, cli.call(networkArgs[5], cfg)); -// assertEquals("conquest", CfgAion.getNetwork()); -// assertEquals(BASE_PATH + "/config/conquest/config.xml", CfgAion.getConfFilePath()); -// assertEquals(BASE_PATH + "/config/conquest/genesis.json", CfgAion.getGenesisFilePath()); -// printPaths(BASE_PATH, cfg, cli); -// } + // /** + // * Test the -n | --network option to see if; 1. Access the correct CONF_FILE_PATH 2. + // Access the + // * correct GENESIS_FILE_PATH 3. Defaults correctly to "mainnet" + // */ + // @Test + // public void testNetwork() { + // + // final String[][] networkArgs = + // new String[][] { + // {"-n", ""}, // Unspecified + // {"-n", "{@.@}"}, // Invalid (illegal characters) + // {"-n", "testnet", "test"}, // Invalid (number of arguments) + // {"-n", "mainnet"}, // Mainnet + // {"-n", "testnet"}, // Testnet + // {"-n", "conquest"}, // Conquest + // }; + // + // Cli cli = new Cli(); + // + // System.out.println("Invalid Networks:"); + // assertEquals(ERROR, cli.call(networkArgs[0], cfg)); + // assertEquals(ERROR, cli.call(networkArgs[1], cfg)); + // assertEquals(ERROR, cli.call(networkArgs[2], cfg)); + // + // System.out.println("\nValid Network 1: " + networkArgs[3][1]); + // assertEquals(RUN, cli.call(networkArgs[3], cfg)); + // assertEquals("mainnet", CfgAion.getNetwork()); + // assertEquals(BASE_PATH + "/config/mainnet/config.xml", CfgAion.getConfFilePath()); + // assertEquals(BASE_PATH + "/config/mainnet/genesis.json", + // CfgAion.getGenesisFilePath()); + // printPaths(BASE_PATH, cfg, cli); + // + // System.out.println("\nValid Network 2: " + networkArgs[4][1]); + // assertEquals(RUN, cli.call(networkArgs[4], cfg)); + // assertEquals("conquest", CfgAion.getNetwork()); + // assertEquals(BASE_PATH + "/config/conquest/config.xml", CfgAion.getConfFilePath()); + // assertEquals(BASE_PATH + "/config/conquest/genesis.json", + // CfgAion.getGenesisFilePath()); + // printPaths(BASE_PATH, cfg, cli); + // + // System.out.println("\nValid Network 3: " + networkArgs[5][1]); + // assertEquals(RUN, cli.call(networkArgs[5], cfg)); + // assertEquals("conquest", CfgAion.getNetwork()); + // assertEquals(BASE_PATH + "/config/conquest/config.xml", CfgAion.getConfFilePath()); + // assertEquals(BASE_PATH + "/config/conquest/genesis.json", + // CfgAion.getGenesisFilePath()); + // printPaths(BASE_PATH, cfg, cli); + // } @Test + @Ignore public void testMultiNetworkDatadir() { final String[][] multiArgs = @@ -488,7 +537,9 @@ private FileVisitResult handleException(final IOException e) { @Override public FileVisitResult postVisitDirectory( final Path dir, final IOException e) throws IOException { - if (e != null) return handleException(e); + if (e != null) { + return handleException(e); + } java.nio.file.Files.delete(dir); return FileVisitResult.CONTINUE; } diff --git a/modMcf/src/org/aion/mcf/config/Cfg.java b/modMcf/src/org/aion/mcf/config/Cfg.java index f29ef8eadc..4e2b8107f6 100644 --- a/modMcf/src/org/aion/mcf/config/Cfg.java +++ b/modMcf/src/org/aion/mcf/config/Cfg.java @@ -150,9 +150,21 @@ public String getBasePath() { return getExecDirectory().getAbsolutePath(); } + protected void resetInternal() { + logDirectory = null; + databaseDirectory = null; + execDirectory = null; + network = null; + + BASE_PATH = INITIAL_PATH; + + useOldSetup = false; + ignoreOldSetup = false; + } + /** Returns the directory location where the kernel configuration and persistence is managed. */ public File getExecDirectory() { - if (!ignoreOldSetup && useOldSetup) { + if ((!ignoreOldSetup && useOldSetup) || execDirectory == null) { return new File(INITIAL_PATH); } else { return new File(execDirectory, network.toString()); @@ -168,6 +180,8 @@ public File getExecDirectory() { */ public void setExecDirectory(File _execDirectory) { this.execDirectory = _execDirectory; + // default network when only datadir is set + this.network = "mainnet"; this.ignoreOldSetup = true; } @@ -308,6 +322,8 @@ public static void skipElement(final XMLStreamReader sr) throws XMLStreamExcepti */ public abstract boolean fromXML(); + public abstract boolean fromXML(File configFile); + public abstract void toXML(final String[] args); public abstract void setGenesis(); From c6921ecc62bd0ac0149e4bd984596480457875f7 Mon Sep 17 00:00:00 2001 From: Alexandra Date: Wed, 19 Sep 2018 11:45:37 -0400 Subject: [PATCH 13/31] debugging and unit tests for config option; functionality to save config to given file --- .../src/org/aion/zero/impl/cli/Cli.java | 16 ++- .../org/aion/zero/impl/config/CfgAion.java | 14 +- .../test/org/aion/zero/impl/cli/CliTest.java | 122 ++++++++++++++++++ modMcf/src/org/aion/mcf/config/Cfg.java | 70 ++++++---- 4 files changed, 183 insertions(+), 39 deletions(-) diff --git a/modAionImpl/src/org/aion/zero/impl/cli/Cli.java b/modAionImpl/src/org/aion/zero/impl/cli/Cli.java index f63efc5b90..fd5655fdba 100644 --- a/modAionImpl/src/org/aion/zero/impl/cli/Cli.java +++ b/modAionImpl/src/org/aion/zero/impl/cli/Cli.java @@ -129,21 +129,27 @@ public ReturnType call(final String[] args, Cfg cfg) { // 3. can be influenced by the -d argument above if (options.getConfig() != null) { - // TODO-Ale test String strNet = options.getConfig(); if (!strNet.isEmpty()) { setNetwork(strNet, cfg); } - File dir = cfg.getExecDirectory(); + // TODO-Ale: handle case where cannot find initial file + // read from initial config + if (cfg.fromXML(cfg.getInitialConfigFile())) { + // set user id when not present + cfg.setId(UUID.randomUUID().toString()); + } + + // ensure path exists + File dir = cfg.getExecConfigDirectory(); if (!dir.exists()) { dir.mkdirs(); } - cfg.fromXML(); - cfg.setId(UUID.randomUUID().toString()); - cfg.toXML(null); + // save to disk + cfg.toXML(null, cfg.getExecConfigFile()); System.out.println("\nNew config generated at " + cfg.getExecConfigPath() + "."); return ReturnType.EXIT; diff --git a/modAionImpl/src/org/aion/zero/impl/config/CfgAion.java b/modAionImpl/src/org/aion/zero/impl/config/CfgAion.java index 23973758f5..0280e6d33e 100644 --- a/modAionImpl/src/org/aion/zero/impl/config/CfgAion.java +++ b/modAionImpl/src/org/aion/zero/impl/config/CfgAion.java @@ -130,7 +130,7 @@ private void closeFileInputStream(final FileInputStream fis){ } public void dbFromXML() { - File cfgFile = new File(getInitialConfigPath()); + File cfgFile = getInitialConfigFile(); XMLInputFactory input = XMLInputFactory.newInstance(); FileInputStream fis = null; try { @@ -229,14 +229,11 @@ public boolean fromXML(final XMLStreamReader sr) throws XMLStreamException { @Override public boolean fromXML() { - return fromXML(new File(getInitialConfigPath())); + return fromXML(getInitialConfigFile()); } @Override public boolean fromXML(File cfgFile) { - // resets internal data containing network and path - this.resetInternal(); - boolean shouldWriteBackToFile = false; if (!cfgFile.exists()) { return false; @@ -257,6 +254,11 @@ public boolean fromXML(File cfgFile) { @Override public void toXML(final String[] args) { + toXML(args, getExecConfigFile()); + } + + @Override + public void toXML(final String[] args, File file) { if (args != null) { boolean override = false; for (String arg : args) { @@ -314,7 +316,7 @@ public void toXML(final String[] args) { try { - sw = output.createXMLStreamWriter(new FileWriter(getExecConfigPath())); + sw = output.createXMLStreamWriter(new FileWriter(file)); sw.writeStartDocument("utf-8", "1.0"); sw.writeCharacters("\r\n"); sw.writeStartElement("aion"); diff --git a/modAionImpl/test/org/aion/zero/impl/cli/CliTest.java b/modAionImpl/test/org/aion/zero/impl/cli/CliTest.java index 48475c467e..d28f626246 100644 --- a/modAionImpl/test/org/aion/zero/impl/cli/CliTest.java +++ b/modAionImpl/test/org/aion/zero/impl/cli/CliTest.java @@ -71,16 +71,47 @@ public class CliTest { BASE_PATH.contains(module) ? new File(BASE_PATH, initialConfigFile) : new File(BASE_PATH, module + "/" + initialConfigFile); + private static File oldConfig = new File(BASE_PATH, "config/config.xml"); + private static File mainnetConfig = new File(BASE_PATH, "config/mainnet/config.xml"); + private static File testnetConfig = new File(BASE_PATH, "config/mastery/config.xml"); @Before public void setup() { // reset config values cfg.fromXML(config); + + if (BASE_PATH.contains(module) && !mainnetConfig.exists()) { + // save config to disk at expected location for new kernel + File configPath = new File(BASE_PATH, "config/mainnet"); + if (!configPath.exists()) { + configPath.mkdirs(); + } + cfg.toXML(null, mainnetConfig); + } + + if (BASE_PATH.contains(module) && !testnetConfig.exists()) { + // save config to disk at expected location for new kernel + File configPath = new File(BASE_PATH, "config/mastery"); + if (!configPath.exists()) { + configPath.mkdirs(); + } + cfg.toXML(null, testnetConfig); + } + + cfg.resetInternal(); } @After public void shutdown() { deleteRecursively(path); + + // to avoid deleting config for all tests + if (BASE_PATH.contains(module)) { + deleteRecursively(new File(BASE_PATH, "config")); + } + + deleteRecursively(new File(BASE_PATH, "mainnet")); + deleteRecursively(new File(BASE_PATH, "mastery")); } /** Ensures that the -h and --help arguments do not fail. */ @@ -148,6 +179,97 @@ public void testDirectory(String[] input, ReturnType expectedReturn, String expe .isEqualTo(new File(expectedPath, "keystore").getAbsolutePath()); } + /** Parameters for testing {@link #testConfig(String[], ReturnType, String)}. */ + @SuppressWarnings("unused") + private Object parametersWithConfig() { + List parameters = new ArrayList<>(); + + String[] options = new String[] {"-c", "--config"}; + String expected = new File(BASE_PATH, "mainnet").getAbsolutePath(); + + for (String op : options) { + // without parameter + parameters.add(new Object[] {new String[] {op}, EXIT, expected}); + // invalid parameter + parameters.add(new Object[] {new String[] {op, "invalid"}, EXIT, expected}); + // mainnet as parameter + parameters.add(new Object[] {new String[] {op, "mainnet"}, EXIT, expected}); + } + + expected = new File(BASE_PATH, "mastery").getAbsolutePath(); + + for (String op : options) { + // mastery as parameter + parameters.add(new Object[] {new String[] {op, "mastery"}, EXIT, expected}); + // testnet as parameter + parameters.add(new Object[] {new String[] {op, "testnet"}, EXIT, expected}); + } + + return parameters.toArray(); + } + + /** Ensures that the -c and --config arguments do not fail. */ + @Test + @Parameters(method = "parametersWithConfig") + public void testConfig(String[] input, ReturnType expectedReturn, String expectedPath) { + assertThat(cli.call(input, cfg)).isEqualTo(expectedReturn); + assertThat(cfg.getBasePath()).isEqualTo(expectedPath); + assertThat(cfg.getExecConfigPath()) + .isEqualTo(new File(expectedPath, "config/config.xml").getAbsolutePath()); + } + + /** Parameters for testing {@link #testConfig_oldLocation(String[], ReturnType, String)}. */ + @SuppressWarnings("unused") + private Object parametersWithConfigForOldLocation() { + List parameters = new ArrayList<>(); + + String[] options = new String[] {"-c", "--config"}; + String expected = new File(BASE_PATH, "mainnet").getAbsolutePath(); + + for (String op : options) { + // without parameter + parameters.add(new Object[] {new String[] {op}, EXIT, BASE_PATH}); + // invalid parameter + parameters.add(new Object[] {new String[] {op, "invalid"}, EXIT, expected}); + // mainnet as parameter + parameters.add(new Object[] {new String[] {op, "mainnet"}, EXIT, expected}); + } + + expected = new File(BASE_PATH, "mastery").getAbsolutePath(); + + for (String op : options) { + // mastery as parameter + parameters.add(new Object[] {new String[] {op, "mastery"}, EXIT, expected}); + // testnet as parameter + parameters.add(new Object[] {new String[] {op, "testnet"}, EXIT, expected}); + } + + return parameters.toArray(); + } + + /** + * Ensures that the -c and --config arguments do not fail when input uses old + * config location. + */ + @Test + @Parameters(method = "parametersWithConfigForOldLocation") + public void testConfig_oldLocation( + String[] input, ReturnType expectedReturn, String expectedPath) { + // ensure config exists on disk at expected location for old kernel + if (!oldConfig.exists()) { + File configPath = new File(BASE_PATH, "config"); + if (!configPath.exists()) { + configPath.mkdirs(); + } + cfg.toXML(null, new File(configPath, "config.xml")); + } + + assertThat(cli.call(input, cfg)).isEqualTo(expectedReturn); + assertThat(cfg.getBasePath()).isEqualTo(expectedPath); + assertThat(cfg.getExecConfigPath()) + .isEqualTo(new File(expectedPath, "config/config.xml").getAbsolutePath()); + } + /** Ensures that the -i and --info arguments do not fail. */ @Test @Ignore diff --git a/modMcf/src/org/aion/mcf/config/Cfg.java b/modMcf/src/org/aion/mcf/config/Cfg.java index 4e2b8107f6..7afed84b2f 100644 --- a/modMcf/src/org/aion/mcf/config/Cfg.java +++ b/modMcf/src/org/aion/mcf/config/Cfg.java @@ -39,10 +39,14 @@ public abstract class Cfg { private File databaseDirectory = null; private File execDirectory = null; - private String network = null; + private String network = "mainnet"; protected String BASE_PATH = System.getProperty("user.dir"); protected final String INITIAL_PATH = System.getProperty("user.dir"); + protected final File oldConfigDir = new File(INITIAL_PATH, configDirectory); + + /** @implNote modified only from {@link #setNetwork(String)} */ + protected File newConfigDir = new File(oldConfigDir, network); private boolean useOldSetup = false; private boolean ignoreOldSetup = false; @@ -150,11 +154,12 @@ public String getBasePath() { return getExecDirectory().getAbsolutePath(); } - protected void resetInternal() { + /** Resets internal data containing network and path. */ + public void resetInternal() { logDirectory = null; databaseDirectory = null; execDirectory = null; - network = null; + network = "mainnet"; BASE_PATH = INITIAL_PATH; @@ -181,7 +186,6 @@ public File getExecDirectory() { public void setExecDirectory(File _execDirectory) { this.execDirectory = _execDirectory; // default network when only datadir is set - this.network = "mainnet"; this.ignoreOldSetup = true; } @@ -193,6 +197,8 @@ public void setExecDirectory(File _execDirectory) { */ public void setNetwork(String _network) { this.network = _network; + this.newConfigDir = new File(oldConfigDir, network); + this.execDirectory = new File(INITIAL_PATH); this.ignoreOldSetup = true; } @@ -236,10 +242,14 @@ public File getExecConfigDirectory() { // TODO check different input return new File(getExecDirectory(), configDirectory); } + /** Returns the location where the config file is saved for kernel execution. */ + public File getExecConfigFile() { + return new File(getExecConfigDirectory(), configFile); + } /** Returns the location where the config file is saved for kernel execution. */ public String getExecConfigPath() { - return new File(getExecConfigDirectory(), configFile).getAbsolutePath(); + return getExecConfigFile().getAbsolutePath(); } /** Returns the location where the genesis file is saved for kernel execution. */ @@ -247,43 +257,39 @@ public String getExecGenesisPath() { return new File(getExecConfigDirectory(), genesisFile).getAbsolutePath(); } - /** - * Returns the configuration directory location before the multi-config changes made to - * the kernel. - */ - private File getOldConfigDirectory() { - return new File(INITIAL_PATH, configDirectory); - } - - /** - * Returns the configuration directory location after the multi-config changes made to - * the kernel. - */ - private File getNewConfigDirectory() { - return new File(getOldConfigDirectory(), network.toString()); - } - /** @implNote Maintains the old setup if the config file is present in the old location. */ - public String getInitialConfigPath() { + public File getInitialConfigFile() { // TODO-Ale: may want to consider exec path as well // use old config location for compatibility with old kernels - File config = new File(getOldConfigDirectory(), configFile); + File config = new File(oldConfigDir, configFile); - if (!config.exists()) { - config = new File(getNewConfigDirectory(), configFile); + // TODO: read mainnet config when ignore set + if (ignoreOldSetup || !config.exists()) { + config = new File(newConfigDir, configFile); + if (execDirectory == null) { + execDirectory = new File(INITIAL_PATH); + } } else { useOldSetup = true; } - return config.getAbsolutePath(); + return config; + } + + /** @implNote Maintains the old setup if the config file is present in the old location. */ + public String getInitialConfigPath() { + return getInitialConfigFile().getAbsolutePath(); } public String getInitialGenesisPath() { // use old genesis location for compatibility with old kernels - File genesis = new File(getOldConfigDirectory(), genesisFile); + File genesis = new File(oldConfigDir, genesisFile); if (!genesis.exists()) { - genesis = new File(getNewConfigDirectory(), genesisFile); + genesis = new File(newConfigDir, genesisFile); + if (execDirectory == null) { + execDirectory = new File(INITIAL_PATH); + } } return genesis.getAbsolutePath(); @@ -322,10 +328,18 @@ public static void skipElement(final XMLStreamReader sr) throws XMLStreamExcepti */ public abstract boolean fromXML(); + /** + * @return boolean value used return to also determine if we need to write back to file with + * current config * return true which means should save back to xml config return true which + * means should save back to xml config if in the config.xml id is set as default + * [NODE-ID-PLACEHOLDER] return true which means should save back to xml config + */ public abstract boolean fromXML(File configFile); public abstract void toXML(final String[] args); + public abstract void toXML(final String[] args, File file); + public abstract void setGenesis(); public abstract AbstractBlock getGenesis(); From 4b3f554829190ce6351078ce2331ceb070d7ac81 Mon Sep 17 00:00:00 2001 From: Alexandra Date: Wed, 19 Sep 2018 13:56:08 -0400 Subject: [PATCH 14/31] debugging and unit tests for network option --- .../src/org/aion/zero/impl/cli/Arguments.java | 133 ++++++++---------- .../src/org/aion/zero/impl/cli/Cli.java | 47 ++++--- .../org/aion/zero/impl/config/Network.java | 7 + .../test/org/aion/zero/impl/cli/CliTest.java | 46 ++++++ modMcf/src/org/aion/mcf/config/Cfg.java | 17 ++- 5 files changed, 151 insertions(+), 99 deletions(-) diff --git a/modAionImpl/src/org/aion/zero/impl/cli/Arguments.java b/modAionImpl/src/org/aion/zero/impl/cli/Arguments.java index 4126ea7640..0c9baa02db 100644 --- a/modAionImpl/src/org/aion/zero/impl/cli/Arguments.java +++ b/modAionImpl/src/org/aion/zero/impl/cli/Arguments.java @@ -40,143 +40,126 @@ public class Arguments { // usage information @Option( - names = {"-h", "--help"}, - usageHelp = true, - description = "display help information" - ) + names = {"-h", "--help"}, + usageHelp = true, + description = "display help information") private boolean help = false; // account management @Option( - names = {"ac", "-a create"}, - description = "create a new account" - ) + names = {"ac", "-a create"}, + description = "create a new account") private boolean createAccount = false; @Option( - names = {"al", "-a list"}, - description = "list all existing accounts" - ) + names = {"al", "-a list"}, + description = "list all existing accounts") private boolean listAccounts = false; @Option( - names = {"ae", "-a export"}, - paramLabel = "", - description = "export private key of an account" - ) + names = {"ae", "-a export"}, + paramLabel = "", + description = "export private key of an account") private String exportAccount = null; @Option( - names = {"ai", "-a import"}, - paramLabel = "", - description = "import private key" - ) + names = {"ai", "-a import"}, + paramLabel = "", + description = "import private key") private String importAccount = null; // config generation @Option( - names = {"-c", "--config"}, - arity = "0..1", - paramLabel = "", - description = "create config for the selected network\noptions: mainnet, conquest" - ) + names = {"-c", "--config"}, + arity = "0..1", + paramLabel = "", + description = + "create config for the selected network\noptions: mainnet, conquest, mastery") public String config = null; // get information and version @Option( - names = {"-i", "--info"}, - description = "display information" - ) + names = {"-i", "--info"}, + description = "display information") public boolean info = false; @Option( - names = {"-v"}, - description = "display version" - ) + names = {"-v"}, + description = "display version") public boolean version = false; @Option( - names = {"--version"}, - description = "display version tag" - ) + names = {"--version"}, + description = "display version tag") public boolean versionTag = false; // create ssl certificate @Option( - names = {"sc", "-s create"}, - arity = "0..2", - paramLabel = " ", - description = - "create a ssl certificate for:\n - localhost (when no parameters are given), or" - + "\n - the given hostname and ip" - ) + names = {"sc", "-s create"}, + arity = "0..2", + paramLabel = " ", + description = + "create a ssl certificate for:\n - localhost (when no parameters are given), or" + + "\n - the given hostname and ip") public String[] ssl = null; // offline block management @Option( - names = {"pb", "--prune-blocks"}, - description = "remove blocks on side chains and update block info" - ) + names = {"pb", "--prune-blocks"}, + description = "remove blocks on side chains and update block info") public boolean rebuildBlockInfo = false; @Option( - names = {"-r", "--revert"}, - arity = "1", - paramLabel = "", - description = "revert database state to given block number" - ) + names = {"-r", "--revert"}, + arity = "1", + paramLabel = "", + description = "revert database state to given block number") private String revertToBlock = null; // network and directory setup @Option( - names = {"-n", "--network"}, - description = "execute kernel with selected network\noptions: mainnet, conquest" - ) + names = {"-n", "--network"}, + description = + "execute kernel with selected network\noptions: mainnet, conquest, mastery") public String network = null; @Option( - names = {"-d", "--datadir"}, - description = "execute kernel with selected database directory" - ) + names = {"-d", "--datadir"}, + description = "execute kernel with selected database directory") public String directory = null; // offline database query and update @Option( - names = {"ps", "--state"}, - paramLabel = "", - description = "reorganize the state storage\noptions: FULL, TOP, SPREAD" - ) + names = {"ps", "--state"}, + paramLabel = "", + description = "reorganize the state storage\noptions: FULL, TOP, SPREAD") public String pruntStateOption = null; // print info from db @Option( - names = {"--dump-blocks"}, - arity = "0..1", - paramLabel = "", - description = "print top blocks from database" - ) + names = {"--dump-blocks"}, + arity = "0..1", + paramLabel = "", + description = "print top blocks from database") public String dumpBlocksCount = null; @Option( - names = {"--dump-state-size"}, - arity = "0..1", - paramLabel = "", - description = "retrieves the state size (node count) for the top blocks" - ) + names = {"--dump-state-size"}, + arity = "0..1", + paramLabel = "", + description = "retrieves the state size (node count) for the top blocks") public String dumpStateSizeCount = null; @Option( - names = {"--dump-state"}, - arity = "0..1", - paramLabel = "", - description = "retrieves the state for the top main chain blocks" - ) + names = {"--dump-state"}, + arity = "0..1", + paramLabel = "", + description = "retrieves the state for the top main chain blocks") public String dumpStateCount = null; @Option( - names = {"--db-compact"}, - description = "if using leveldb, it triggers its database compaction processes" - ) + names = {"--db-compact"}, + description = "if using leveldb, it triggers its database compaction processes") public boolean dbCompact; /** Compacts the account options into specific commands. */ diff --git a/modAionImpl/src/org/aion/zero/impl/cli/Cli.java b/modAionImpl/src/org/aion/zero/impl/cli/Cli.java index fd5655fdba..cf0d818828 100644 --- a/modAionImpl/src/org/aion/zero/impl/cli/Cli.java +++ b/modAionImpl/src/org/aion/zero/impl/cli/Cli.java @@ -158,7 +158,7 @@ public ReturnType call(final String[] args, Cfg cfg) { // 4. determine the network configuration if (options.getNetwork() != null) { - setNetwork(options.getConfig(), cfg); + setNetwork(options.getNetwork(), cfg); // no return -> allow for other parameters combined with -n } @@ -362,6 +362,8 @@ public ReturnType call(final String[] args, Cfg cfg) { return EXIT; } + // make directories for kernel execution + makeDirs(cfg); // if no return happened earlier, run the kernel return RUN; } catch (Throwable e) { @@ -449,32 +451,35 @@ private void setNetwork(String network, Cfg cfg) { private void printInvalidNetwork() { System.out.println("\nInvalid network selected!\n"); System.out.println("------ Available Networks ------"); - System.out.println(Arrays.toString(Network.values())); + System.out.println(Network.valueString()); System.out.println("--------------------------------\n"); } /** - * Copies the config files (config && genesis) from root to [datadir]/[network] + * Creates the directories for persistence of the kernel data. Copies the config and genesis + * files from the initial path for the execution directory. * - * @param path input to append base directory to copy to - * @param net input to determine network to copy from + * @param cfg the configuration for the runtime kernel environment */ - private void copyNetwork(String path, Network net) { - - File dir1 = new File(path + "/" + net + "/config"); - File dir2 = new File(path + "/" + net + "/keystore"); - dir1.mkdirs(); - dir2.mkdirs(); - - File src1 = new File(BASE_PATH + "/config/" + net + "/config.xml"); - File src2 = new File(BASE_PATH + "/config/" + net + "/genesis.json"); - File dst1 = new File(path + "/" + net + "/config/config.xml"); - File dst2 = new File(path + "/" + net + "/config/genesis.json"); - - copyRecursively(src1, dst1); - copyRecursively(src2, dst2); - // dstConfig = dst1.toString(); - // dstGenesis = dst2.toString(); + private void makeDirs(Cfg cfg) { + File file = cfg.getExecDirectory(); + if (!file.exists()) { + file.mkdirs(); + } + + // create target config directory + file = cfg.getExecConfigDirectory(); + if (!file.exists()) { + file.mkdirs(); + } + + // copy config & genesis + copyRecursively(cfg.getInitialConfigFile(), cfg.getExecConfigFile()); + copyRecursively(cfg.getInitialGenesisFile(), cfg.getExecGenesisFile()); + + // TODO-Ale: create target log directory + // TODO-Ale: create target database directory + // TODO-Ale: create target keystore directory } /** diff --git a/modAionImpl/src/org/aion/zero/impl/config/Network.java b/modAionImpl/src/org/aion/zero/impl/config/Network.java index 7374199ba3..0d6db1a13d 100644 --- a/modAionImpl/src/org/aion/zero/impl/config/Network.java +++ b/modAionImpl/src/org/aion/zero/impl/config/Network.java @@ -22,6 +22,8 @@ */ package org.aion.zero.impl.config; +import java.util.Arrays; + /** * Defines different Aion networks. * @@ -96,4 +98,9 @@ public static Network determineNetwork(int identifier) { return null; } + + public static String valueString() { + String output = Arrays.toString(Network.values()); + return output.substring(1, output.length() - 1); + } } diff --git a/modAionImpl/test/org/aion/zero/impl/cli/CliTest.java b/modAionImpl/test/org/aion/zero/impl/cli/CliTest.java index d28f626246..98ca9cc3dd 100644 --- a/modAionImpl/test/org/aion/zero/impl/cli/CliTest.java +++ b/modAionImpl/test/org/aion/zero/impl/cli/CliTest.java @@ -270,6 +270,52 @@ public void testConfig_oldLocation( .isEqualTo(new File(expectedPath, "config/config.xml").getAbsolutePath()); } + /** Parameters for testing {@link #testNetwork(String[], ReturnType, String)}. */ + @SuppressWarnings("unused") + private Object parametersWithNetwork() { + List parameters = new ArrayList<>(); + + String[] options = new String[] {"-n", "--network"}; + String expected = new File(BASE_PATH, "mainnet").getAbsolutePath(); + + for (String op : options) { + // without parameter + parameters.add(new Object[] {new String[] {op}, ERROR, BASE_PATH}); + // invalid parameter + parameters.add(new Object[] {new String[] {op, "invalid"}, RUN, expected}); + // mainnet as parameter + parameters.add(new Object[] {new String[] {op, "mainnet"}, RUN, expected}); + } + + expected = new File(BASE_PATH, "mastery").getAbsolutePath(); + + for (String op : options) { + // mastery as parameter + parameters.add(new Object[] {new String[] {op, "mastery"}, RUN, expected}); + // testnet as parameter + parameters.add(new Object[] {new String[] {op, "testnet"}, RUN, expected}); + } + + return parameters.toArray(); + } + + /** Ensures that the -n and --network arguments do not fail. */ + @Test + @Parameters(method = "parametersWithNetwork") + public void testNetwork(String[] input, ReturnType expectedReturn, String expectedPath) { + assertThat(cli.call(input, cfg)).isEqualTo(expectedReturn); + assertThat(cfg.getBasePath()).isEqualTo(expectedPath); + assertThat(cfg.getExecConfigPath()) + .isEqualTo(new File(expectedPath, "config/config.xml").getAbsolutePath()); + assertThat(cfg.getExecGenesisPath()) + .isEqualTo(new File(expectedPath, "config/genesis.json").getAbsolutePath()); + assertThat(cfg.getDatabasePath()) + .isEqualTo(new File(expectedPath, "database").getAbsolutePath()); + assertThat(cfg.getLogPath()).isEqualTo(new File(expectedPath, "log").getAbsolutePath()); + assertThat(cfg.getKeystorePath()) + .isEqualTo(new File(expectedPath, "keystore").getAbsolutePath()); + } + /** Ensures that the -i and --info arguments do not fail. */ @Test @Ignore diff --git a/modMcf/src/org/aion/mcf/config/Cfg.java b/modMcf/src/org/aion/mcf/config/Cfg.java index 7afed84b2f..77307149fb 100644 --- a/modMcf/src/org/aion/mcf/config/Cfg.java +++ b/modMcf/src/org/aion/mcf/config/Cfg.java @@ -242,6 +242,7 @@ public File getExecConfigDirectory() { // TODO check different input return new File(getExecDirectory(), configDirectory); } + /** Returns the location where the config file is saved for kernel execution. */ public File getExecConfigFile() { return new File(getExecConfigDirectory(), configFile); @@ -252,9 +253,14 @@ public String getExecConfigPath() { return getExecConfigFile().getAbsolutePath(); } + /** Returns the location where the genesis file is saved for kernel execution. */ + public File getExecGenesisFile() { + return new File(getExecConfigDirectory(), genesisFile); + } + /** Returns the location where the genesis file is saved for kernel execution. */ public String getExecGenesisPath() { - return new File(getExecConfigDirectory(), genesisFile).getAbsolutePath(); + return getExecGenesisFile().getAbsolutePath(); } /** @implNote Maintains the old setup if the config file is present in the old location. */ @@ -281,7 +287,7 @@ public String getInitialConfigPath() { return getInitialConfigFile().getAbsolutePath(); } - public String getInitialGenesisPath() { + public File getInitialGenesisFile() { // use old genesis location for compatibility with old kernels File genesis = new File(oldConfigDir, genesisFile); @@ -292,7 +298,12 @@ public String getInitialGenesisPath() { } } - return genesis.getAbsolutePath(); + return genesis; + } + + /** @implNote Maintains the old setup if the config file is present in the old location. */ + public String getInitialGenesisPath() { + return getInitialGenesisFile().getAbsolutePath(); } public static String readValue(final XMLStreamReader sr) throws XMLStreamException { From 79e8ead426c3206374ed0429a0a82c93ae69cf0a Mon Sep 17 00:00:00 2001 From: Alexandra Date: Wed, 19 Sep 2018 14:23:06 -0400 Subject: [PATCH 15/31] reordered -n and -d options; added missing test resources; refactoring and unit tests --- .../src/org/aion/zero/impl/cli/Cli.java | 40 +- .../test/org/aion/zero/impl/cli/CliTest.java | 415 +++++++++--------- modAionImpl/test_resources/config.xml | 100 +++++ modAionImpl/test_resources/genesis.json | 19 + modMcf/src/org/aion/mcf/config/Cfg.java | 2 +- 5 files changed, 353 insertions(+), 223 deletions(-) create mode 100644 modAionImpl/test_resources/config.xml create mode 100644 modAionImpl/test_resources/genesis.json diff --git a/modAionImpl/src/org/aion/zero/impl/cli/Cli.java b/modAionImpl/src/org/aion/zero/impl/cli/Cli.java index cf0d818828..92b774ffb7 100644 --- a/modAionImpl/src/org/aion/zero/impl/cli/Cli.java +++ b/modAionImpl/src/org/aion/zero/impl/cli/Cli.java @@ -117,7 +117,20 @@ public ReturnType call(final String[] args, Cfg cfg) { return EXIT; } - // 2. determine the execution folder path + // 2. determine the network configuration + + if (options.getNetwork() != null + || (options.getConfig() != null && !options.getConfig().isEmpty())) { + String strNet = options.getNetwork(); + // the network given in config overwrites the -n option + if (options.getConfig() != null && !options.getConfig().isEmpty()) { + strNet = options.getConfig(); + } + setNetwork(strNet, cfg); + // no return -> allow for other parameters combined with -n + } + + // 3. determine the execution folder path; influenced by --network if (options.getDirectory() != null) { if (!setDirectory(options.getDirectory(), cfg)) { @@ -126,14 +139,10 @@ public ReturnType call(final String[] args, Cfg cfg) { // no return -> allow for other parameters combined with -d } - // 3. can be influenced by the -d argument above + // 4. can be influenced by the -d argument above if (options.getConfig() != null) { - String strNet = options.getConfig(); - - if (!strNet.isEmpty()) { - setNetwork(strNet, cfg); - } + // network was already set above // TODO-Ale: handle case where cannot find initial file // read from initial config @@ -155,13 +164,6 @@ public ReturnType call(final String[] args, Cfg cfg) { return ReturnType.EXIT; } - // 4. determine the network configuration - - if (options.getNetwork() != null) { - setNetwork(options.getNetwork(), cfg); - // no return -> allow for other parameters combined with -n - } - // 5. options that can be influenced by the -d and -n arguments if (options.isInfo()) { @@ -724,16 +726,8 @@ private String getCertPass(Console console) { return certPass; } - public String getDstConfig() { - return ""; // dstConfig; - } - - public String getDstGenesis() { - return ""; // dstGenesis; - } - // Methods below taken from FileUtils class - private static boolean copyRecursively(File src, File target) { + public static boolean copyRecursively(File src, File target) { if (src.isDirectory()) { return copyDirectoryContents(src, target); } else { diff --git a/modAionImpl/test/org/aion/zero/impl/cli/CliTest.java b/modAionImpl/test/org/aion/zero/impl/cli/CliTest.java index 98ca9cc3dd..4792826dcc 100644 --- a/modAionImpl/test/org/aion/zero/impl/cli/CliTest.java +++ b/modAionImpl/test/org/aion/zero/impl/cli/CliTest.java @@ -64,6 +64,7 @@ public class CliTest { private static final String BASE_PATH = System.getProperty("user.dir"); private static final String module = "modAionImpl"; private static final String initialConfigFile = "test_resources/config.xml"; + private static final String initialGenesisFile = "test_resources/genesis.json"; private static final String dataDirectory = "datadir"; private static final File path = new File(BASE_PATH, dataDirectory); @@ -75,6 +76,17 @@ public class CliTest { private static File mainnetConfig = new File(BASE_PATH, "config/mainnet/config.xml"); private static File testnetConfig = new File(BASE_PATH, "config/mastery/config.xml"); + private static File genesis = + BASE_PATH.contains(module) + ? new File(BASE_PATH, initialGenesisFile) + : new File(BASE_PATH, module + "/" + initialGenesisFile); + private static File oldGenesis = new File(BASE_PATH, "config/genesis.json"); + private static File mainnetGenesis = new File(BASE_PATH, "config/mainnet/genesis.json"); + private static File testnetGenesis = new File(BASE_PATH, "config/mastery/genesis.json"); + + /** @implNote set this to true to enable printing */ + private static boolean verbose = false; + @Before public void setup() { // reset config values @@ -87,6 +99,7 @@ public void setup() { configPath.mkdirs(); } cfg.toXML(null, mainnetConfig); + Cli.copyRecursively(genesis, mainnetGenesis); } if (BASE_PATH.contains(module) && !testnetConfig.exists()) { @@ -96,6 +109,7 @@ public void setup() { configPath.mkdirs(); } cfg.toXML(null, testnetConfig); + Cli.copyRecursively(genesis, testnetGenesis); } cfg.resetInternal(); @@ -128,15 +142,17 @@ public void testVersion() { assertThat(cli.call(new String[] {"--version"}, cfg)).isEqualTo(EXIT); } - /** Parameters for testing {@link #testDirectory(String[], ReturnType, String)}. */ + /** Parameters for testing {@link #testDirectoryAndNetwork(String[], ReturnType, String)}. */ @SuppressWarnings("unused") - private Object parametersWithDatadir() { + private Object parametersWithDirectoryAndNetwork() { List parameters = new ArrayList<>(); - String[] options = new String[] {"-d", "--datadir"}; + String[] dir_options = new String[] {"-d", "--datadir"}; + String[] net_options = new String[] {"-n", "--network"}; String expected = new File(path, "mainnet").getAbsolutePath(); - for (String op : options) { + // data directory alone + for (String op : dir_options) { // with relative path parameters.add(new Object[] {new String[] {op, dataDirectory}, RUN, expected}); // with absolute path @@ -147,12 +163,97 @@ private Object parametersWithDatadir() { parameters.add(new Object[] {new String[] {op, "/\\<>:\"|?*"}, ERROR, BASE_PATH}); } - // setup for subdirectories + // network and directory + String[] net_values = new String[] {"mainnet", "invalid"}; + for (String opDir : dir_options) { + for (String opNet : net_options) { + for (String valNet : net_values) { + // with relative path + parameters.add( + new Object[] { + new String[] {opDir, dataDirectory, opNet, valNet}, RUN, expected + }); + parameters.add( + new Object[] { + new String[] {opNet, valNet, opDir, dataDirectory}, RUN, expected + }); + // with absolute path + parameters.add( + new Object[] { + new String[] {opDir, path.getAbsolutePath(), opNet, valNet}, + RUN, + expected + }); + parameters.add( + new Object[] { + new String[] {opNet, valNet, opDir, path.getAbsolutePath()}, + RUN, + expected + }); + } + } + } + + // network alone + expected = new File(BASE_PATH, "mainnet").getAbsolutePath(); + for (String op : net_options) { + // without parameter + parameters.add(new Object[] {new String[] {op}, ERROR, BASE_PATH}); + // with two parameters + parameters.add( + new Object[] {new String[] {op, "testnet", "mastery"}, ERROR, BASE_PATH}); + // invalid parameter + parameters.add(new Object[] {new String[] {op, "invalid"}, RUN, expected}); + // mainnet as parameter + parameters.add(new Object[] {new String[] {op, "mainnet"}, RUN, expected}); + } + + // network alone with testnet + net_values = new String[] {"mastery", "testnet"}; + expected = new File(BASE_PATH, "mastery").getAbsolutePath(); + for (String op : net_options) { + for (String netVal : net_values) { + // mastery as parameter + parameters.add(new Object[] {new String[] {op, netVal}, RUN, expected}); + } + } + + // network and directory with testnet + expected = new File(path, "mastery").getAbsolutePath(); + for (String opDir : dir_options) { + for (String opNet : net_options) { + for (String netVal : net_values) { + // with relative path + parameters.add( + new Object[] { + new String[] {opDir, dataDirectory, opNet, netVal}, RUN, expected + }); + parameters.add( + new Object[] { + new String[] {opNet, netVal, opDir, dataDirectory}, RUN, expected + }); + // with absolute path + parameters.add( + new Object[] { + new String[] {opDir, path.getAbsolutePath(), opNet, netVal}, + RUN, + expected + }); + parameters.add( + new Object[] { + new String[] {opNet, netVal, opDir, path.getAbsolutePath()}, + RUN, + expected + }); + } + } + } + + // with subdirectories String dir = dataDirectory + "/subfolder"; File path = new File(BASE_PATH, dir); expected = new File(path, "mainnet").getAbsolutePath(); - - for (String op : options) { + for (String op : dir_options) { // with relative path with subdirectories parameters.add(new Object[] {new String[] {op, dir}, RUN, expected}); // with multiple values @@ -161,11 +262,14 @@ private Object parametersWithDatadir() { return parameters.toArray(); } - - /** Ensures that the -d and --datadir arguments do not fail. */ + /** + * Ensures that the -d, --datadir, -n and --network arguments do not + * fail. + */ @Test - @Parameters(method = "parametersWithDatadir") - public void testDirectory(String[] input, ReturnType expectedReturn, String expectedPath) { + @Parameters(method = "parametersWithDirectoryAndNetwork") + public void testDirectoryAndNetwork( + String[] input, ReturnType expectedReturn, String expectedPath) { assertThat(cli.call(input, cfg)).isEqualTo(expectedReturn); assertThat(cfg.getBasePath()).isEqualTo(expectedPath); assertThat(cfg.getExecConfigPath()) @@ -177,6 +281,30 @@ public void testDirectory(String[] input, ReturnType expectedReturn, String expe assertThat(cfg.getLogPath()).isEqualTo(new File(expectedPath, "log").getAbsolutePath()); assertThat(cfg.getKeystorePath()) .isEqualTo(new File(expectedPath, "keystore").getAbsolutePath()); + if (verbose) { + printPaths(cfg); + } + } + + private void printPaths(Cfg cfg) { + System.out.println( + "\n-------------------------------- USED PATHS --------------------------------" + + "\n> Logger path: " + + cfg.getLogPath() + + "\n> Database path: " + + cfg.getDatabasePath() + + "\n> Keystore path: " + + cfg.getKeystorePath() + + "\n> Config write: " + + cfg.getExecConfigPath() + + "\n> Genesis write: " + + cfg.getExecGenesisPath() + + "\n----------------------------------------------------------------------------" + + "\n> Config read: " + + cfg.getInitialConfigPath() + + "\n> Genesis read: " + + cfg.getInitialGenesisPath() + + "\n----------------------------------------------------------------------------\n\n"); } /** Parameters for testing {@link #testConfig(String[], ReturnType, String)}. */ @@ -205,6 +333,72 @@ private Object parametersWithConfig() { parameters.add(new Object[] {new String[] {op, "testnet"}, EXIT, expected}); } + // config and directory + String[] dir_options = new String[] {"-d", "--datadir"}; + expected = new File(path, "mainnet").getAbsolutePath(); + + String[] net_values = new String[] {"mainnet", "invalid"}; + for (String opDir : dir_options) { + for (String opCfg : options) { + for (String valNet : net_values) { + // with relative path + parameters.add( + new Object[] { + new String[] {opDir, dataDirectory, opCfg, valNet}, EXIT, expected + }); + parameters.add( + new Object[] { + new String[] {opCfg, valNet, opDir, dataDirectory}, EXIT, expected + }); + // with absolute path + parameters.add( + new Object[] { + new String[] {opDir, path.getAbsolutePath(), opCfg, valNet}, + EXIT, + expected + }); + parameters.add( + new Object[] { + new String[] {opCfg, valNet, opDir, path.getAbsolutePath()}, + EXIT, + expected + }); + } + } + } + + // config and directory with testnet + net_values = new String[] {"mastery", "testnet"}; + expected = new File(path, "mastery").getAbsolutePath(); + for (String opDir : dir_options) { + for (String opCfg : options) { + for (String netVal : net_values) { + // with relative path + parameters.add( + new Object[] { + new String[] {opDir, dataDirectory, opCfg, netVal}, EXIT, expected + }); + parameters.add( + new Object[] { + new String[] {opCfg, netVal, opDir, dataDirectory}, EXIT, expected + }); + // with absolute path + parameters.add( + new Object[] { + new String[] {opDir, path.getAbsolutePath(), opCfg, netVal}, + EXIT, + expected + }); + parameters.add( + new Object[] { + new String[] {opCfg, netVal, opDir, path.getAbsolutePath()}, + EXIT, + expected + }); + } + } + } + return parameters.toArray(); } @@ -216,6 +410,11 @@ public void testConfig(String[] input, ReturnType expectedReturn, String expecte assertThat(cfg.getBasePath()).isEqualTo(expectedPath); assertThat(cfg.getExecConfigPath()) .isEqualTo(new File(expectedPath, "config/config.xml").getAbsolutePath()); + assertThat(cfg.getExecGenesisPath()) + .isEqualTo(new File(expectedPath, "config/genesis.json").getAbsolutePath()); + if (verbose) { + printPaths(cfg); + } } /** Parameters for testing {@link #testConfig_oldLocation(String[], ReturnType, String)}. */ @@ -261,59 +460,19 @@ public void testConfig_oldLocation( if (!configPath.exists()) { configPath.mkdirs(); } - cfg.toXML(null, new File(configPath, "config.xml")); - } - - assertThat(cli.call(input, cfg)).isEqualTo(expectedReturn); - assertThat(cfg.getBasePath()).isEqualTo(expectedPath); - assertThat(cfg.getExecConfigPath()) - .isEqualTo(new File(expectedPath, "config/config.xml").getAbsolutePath()); - } - - /** Parameters for testing {@link #testNetwork(String[], ReturnType, String)}. */ - @SuppressWarnings("unused") - private Object parametersWithNetwork() { - List parameters = new ArrayList<>(); - - String[] options = new String[] {"-n", "--network"}; - String expected = new File(BASE_PATH, "mainnet").getAbsolutePath(); - - for (String op : options) { - // without parameter - parameters.add(new Object[] {new String[] {op}, ERROR, BASE_PATH}); - // invalid parameter - parameters.add(new Object[] {new String[] {op, "invalid"}, RUN, expected}); - // mainnet as parameter - parameters.add(new Object[] {new String[] {op, "mainnet"}, RUN, expected}); + cfg.toXML(null, oldConfig); + Cli.copyRecursively(genesis, oldGenesis); } - expected = new File(BASE_PATH, "mastery").getAbsolutePath(); - - for (String op : options) { - // mastery as parameter - parameters.add(new Object[] {new String[] {op, "mastery"}, RUN, expected}); - // testnet as parameter - parameters.add(new Object[] {new String[] {op, "testnet"}, RUN, expected}); - } - - return parameters.toArray(); - } - - /** Ensures that the -n and --network arguments do not fail. */ - @Test - @Parameters(method = "parametersWithNetwork") - public void testNetwork(String[] input, ReturnType expectedReturn, String expectedPath) { assertThat(cli.call(input, cfg)).isEqualTo(expectedReturn); assertThat(cfg.getBasePath()).isEqualTo(expectedPath); assertThat(cfg.getExecConfigPath()) .isEqualTo(new File(expectedPath, "config/config.xml").getAbsolutePath()); assertThat(cfg.getExecGenesisPath()) .isEqualTo(new File(expectedPath, "config/genesis.json").getAbsolutePath()); - assertThat(cfg.getDatabasePath()) - .isEqualTo(new File(expectedPath, "database").getAbsolutePath()); - assertThat(cfg.getLogPath()).isEqualTo(new File(expectedPath, "log").getAbsolutePath()); - assertThat(cfg.getKeystorePath()) - .isEqualTo(new File(expectedPath, "keystore").getAbsolutePath()); + if (verbose) { + printPaths(cfg); + } } /** Ensures that the -i and --info arguments do not fail. */ @@ -477,148 +636,6 @@ public void testImportPrivateKeyWrong() { assertEquals(ERROR, cli.call(args, CfgAion.inst())); } - // /** - // * Test the -n | --network option to see if; 1. Access the correct CONF_FILE_PATH 2. - // Access the - // * correct GENESIS_FILE_PATH 3. Defaults correctly to "mainnet" - // */ - // @Test - // public void testNetwork() { - // - // final String[][] networkArgs = - // new String[][] { - // {"-n", ""}, // Unspecified - // {"-n", "{@.@}"}, // Invalid (illegal characters) - // {"-n", "testnet", "test"}, // Invalid (number of arguments) - // {"-n", "mainnet"}, // Mainnet - // {"-n", "testnet"}, // Testnet - // {"-n", "conquest"}, // Conquest - // }; - // - // Cli cli = new Cli(); - // - // System.out.println("Invalid Networks:"); - // assertEquals(ERROR, cli.call(networkArgs[0], cfg)); - // assertEquals(ERROR, cli.call(networkArgs[1], cfg)); - // assertEquals(ERROR, cli.call(networkArgs[2], cfg)); - // - // System.out.println("\nValid Network 1: " + networkArgs[3][1]); - // assertEquals(RUN, cli.call(networkArgs[3], cfg)); - // assertEquals("mainnet", CfgAion.getNetwork()); - // assertEquals(BASE_PATH + "/config/mainnet/config.xml", CfgAion.getConfFilePath()); - // assertEquals(BASE_PATH + "/config/mainnet/genesis.json", - // CfgAion.getGenesisFilePath()); - // printPaths(BASE_PATH, cfg, cli); - // - // System.out.println("\nValid Network 2: " + networkArgs[4][1]); - // assertEquals(RUN, cli.call(networkArgs[4], cfg)); - // assertEquals("conquest", CfgAion.getNetwork()); - // assertEquals(BASE_PATH + "/config/conquest/config.xml", CfgAion.getConfFilePath()); - // assertEquals(BASE_PATH + "/config/conquest/genesis.json", - // CfgAion.getGenesisFilePath()); - // printPaths(BASE_PATH, cfg, cli); - // - // System.out.println("\nValid Network 3: " + networkArgs[5][1]); - // assertEquals(RUN, cli.call(networkArgs[5], cfg)); - // assertEquals("conquest", CfgAion.getNetwork()); - // assertEquals(BASE_PATH + "/config/conquest/config.xml", CfgAion.getConfFilePath()); - // assertEquals(BASE_PATH + "/config/conquest/genesis.json", - // CfgAion.getGenesisFilePath()); - // printPaths(BASE_PATH, cfg, cli); - // } - - @Test - @Ignore - public void testMultiNetworkDatadir() { - - final String[][] multiArgs = - new String[][] { - {"-d", "aaaaaaaa", "-n", "mainnet"}, // New network - {"-d", "abbbbbbb", "-n", "conquest"}, // New dir - {"-n", "conquest", "-d", "abcccccc"}, // New dir - {"-d", "aaaaaaaa", "-n", "conquest"}, // Existing dir / network - {"-n", "conquest", "-d", "aaaaaaaa"}, // Exisitng network / dir - {"-d", "{@.@}", "-n", "conquest"}, // Invalid dir - {"-n", "conquest", "-d", "{@.@}"}, // Invalid dir - {"-d", "aaaaaaaa", "-n", "{@.@}"}, // Invalid network - {"-n", "{@.@}", "-d", "aaaaaaaa"}, // Invalid network - }; - - Cli cli = new Cli(); - Cfg cfg = CfgAion.inst(); - - System.out.println("\nNew 1: " + multiArgs[0][1] + " " + multiArgs[0][3]); - assertEquals(RUN, cli.call(multiArgs[0], cfg)); - assertEquals("mainnet", cfg.getNetwork()); - assertEquals("aaaaaaaa/mainnet/log", cfg.getLog().getLogPath()); - assertEquals("aaaaaaaa/mainnet/database", cfg.getDb().getPath()); - assertEquals(BASE_PATH + "/aaaaaaaa/mainnet/keystore", Keystore.getKeystorePath()); - printPaths(BASE_PATH, cfg, cli); - - System.out.println("\nNew 2: " + multiArgs[1][1] + " " + multiArgs[1][3]); - assertEquals(RUN, cli.call(multiArgs[1], cfg)); - assertEquals("conquest", cfg.getNetwork()); - assertEquals("abbbbbbb/conquest/log", cfg.getLog().getLogPath()); - assertEquals("abbbbbbb/conquest/database", cfg.getDb().getPath()); - assertEquals(BASE_PATH + "/abbbbbbb/conquest/keystore", Keystore.getKeystorePath()); - printPaths(BASE_PATH, cfg, cli); - - System.out.println("\nNew 3: " + multiArgs[2][1] + " " + multiArgs[2][3]); - assertEquals(RUN, cli.call(multiArgs[2], cfg)); - assertEquals("conquest", cfg.getNetwork()); - assertEquals("abcccccc/conquest/log", cfg.getLog().getLogPath()); - assertEquals("abcccccc/conquest/database", cfg.getDb().getPath()); - assertEquals(BASE_PATH + "/abcccccc/conquest/keystore", Keystore.getKeystorePath()); - printPaths(BASE_PATH, cfg, cli); - - System.out.println("\n Exist 1: " + multiArgs[3][1] + " " + multiArgs[3][3]); - assertEquals(RUN, cli.call(multiArgs[3], cfg)); - assertEquals("conquest", cfg.getNetwork()); - assertEquals("aaaaaaaa/conquest/log", cfg.getLog().getLogPath()); - assertEquals("aaaaaaaa/conquest/database", cfg.getDb().getPath()); - assertEquals(BASE_PATH + "/aaaaaaaa/conquest/keystore", Keystore.getKeystorePath()); - printPaths(BASE_PATH, cfg, cli); - - System.out.println("\nExist 2: " + multiArgs[4][1] + " " + multiArgs[4][3]); - assertEquals(RUN, cli.call(multiArgs[4], cfg)); - assertEquals("conquest", cfg.getNetwork()); - assertEquals("aaaaaaaa/conquest/log", cfg.getLog().getLogPath()); - assertEquals("aaaaaaaa/conquest/database", cfg.getDb().getPath()); - assertEquals(BASE_PATH + "/aaaaaaaa/conquest/keystore", Keystore.getKeystorePath()); - printPaths(BASE_PATH, cfg, cli); - - // Invalid input - assertEquals(ERROR, cli.call(multiArgs[5], cfg)); - assertEquals(ERROR, cli.call(multiArgs[6], cfg)); - assertEquals(ERROR, cli.call(multiArgs[7], cfg)); - assertEquals(ERROR, cli.call(multiArgs[8], cfg)); - } - - private void printPaths(String BASE_PATH, Cfg cfg, Cli cli) { - System.out.println( - "\n-------------------------------- USED PATHS --------------------------------" - + "\n> Logger path: " - + BASE_PATH - + "/" - + cfg.getLog().getLogPath() - + "\n> Database path: " - + BASE_PATH - + "/" - + cfg.getDb().getPath() - + "\n> Keystore path: " - + Keystore.getKeystorePath() - + "\n> Config write: " - + cli.getDstConfig() - + "\n> Genesis write: " - + cli.getDstGenesis() - + "\n----------------------------------------------------------------------------" - + "\n> Config read: " - + cfg.getInitialConfigPath() - + "\n> Genesis read: " - + cfg.getInitialGenesisPath() - + "\n----------------------------------------------------------------------------\n\n"); - } - // Methods below taken from FileUtils class private static boolean copyRecursively(File src, File target) { if (src.isDirectory()) { @@ -682,7 +699,7 @@ private static boolean deleteRecursively(File file) { try { java.nio.file.Files.walkFileTree( path, - new SimpleFileVisitor() { + new SimpleFileVisitor<>() { @Override public FileVisitResult visitFile( final Path file, final BasicFileAttributes attrs) diff --git a/modAionImpl/test_resources/config.xml b/modAionImpl/test_resources/config.xml new file mode 100644 index 0000000000..bedb9e6e89 --- /dev/null +++ b/modAionImpl/test_resources/config.xml @@ -0,0 +1,100 @@ + + + aion + [NODE-ID-PLACEHOLDER] + + + + false + + web3,eth,personal,stratum,ops + + + + + 10E9 + + 100E9 + + false + + + + 256 + + p2p://c33d1066-8c7e-496c-9c4e-c89318280274@13.92.155.115:30303 + p2p://c33d2207-729a-4584-86f1-e19ab97cf9ce@51.144.42.220:30303 + p2p://c33d302f-216b-47d4-ac44-5d8181b56e7e@52.231.187.227:30303 + p2p://c33d4c07-6a29-4ca6-8b06-b2781ba7f9bf@191.232.164.119:30303 + p2p://c33d5a94-20d8-49d9-97d6-284f88da5c21@13.89.244.125:30303 + p2p://741b979e-6a06-493a-a1f2-693cafd37083@66.207.217.190:30303 + + + 0.0.0.0 + 30303 + false + 128 + + + + + 32 + + false + + + false + 0000000000000000000000000000000000000000000000000000000000000000 + 2 + AION + + + + + + + + + + database + + true + + + + + FULL + + + leveldb + + false + + + + true + + log + WARN + INFO + ERROR + INFO + INFO + ERROR + INFO + INFO + INFO + + + + + true + + aion.sh + + /placeholder/for/aion_root_dir + + + + + diff --git a/modAionImpl/test_resources/genesis.json b/modAionImpl/test_resources/genesis.json new file mode 100644 index 0000000000..7e25773645 --- /dev/null +++ b/modAionImpl/test_resources/genesis.json @@ -0,0 +1,19 @@ +{ + "alloc": { + "0xa0eeaeabdbc92953b072afbd21f3e3fd8a4a4f5e6a6e22200db746ab75e9a99a": { + "balance": "465934586660000000000000000" + } + }, + "networkBalanceAlloc": { + "0": { + "balance": "465934586660000000000000000" + } + }, + "energyLimit": "15000000", + "nonce": "0x00", + "difficulty": "0x4000", + "coinbase": "0x0000000000000000000000000000000000000000000000000000000000000000", + "timestamp": "1524528000", + "parentHash": "0x6a6d99a2ef14ab3b835dfc92fb918d76c37f6578a69825fbe19cd366485604b1", + "chainId": "256" +} \ No newline at end of file diff --git a/modMcf/src/org/aion/mcf/config/Cfg.java b/modMcf/src/org/aion/mcf/config/Cfg.java index 77307149fb..e57b47cbb6 100644 --- a/modMcf/src/org/aion/mcf/config/Cfg.java +++ b/modMcf/src/org/aion/mcf/config/Cfg.java @@ -291,7 +291,7 @@ public File getInitialGenesisFile() { // use old genesis location for compatibility with old kernels File genesis = new File(oldConfigDir, genesisFile); - if (!genesis.exists()) { + if (ignoreOldSetup || !genesis.exists()) { genesis = new File(newConfigDir, genesisFile); if (execDirectory == null) { execDirectory = new File(INITIAL_PATH); From 203a3feccae15ee3969ac034adab02767fadc604 Mon Sep 17 00:00:00 2001 From: Alexandra Date: Wed, 19 Sep 2018 16:30:01 -0400 Subject: [PATCH 16/31] debugging and unit tests for info option --- .../src/org/aion/zero/impl/cli/Cli.java | 8 +- .../test/org/aion/zero/impl/cli/CliTest.java | 223 ++++++++++++++---- 2 files changed, 178 insertions(+), 53 deletions(-) diff --git a/modAionImpl/src/org/aion/zero/impl/cli/Cli.java b/modAionImpl/src/org/aion/zero/impl/cli/Cli.java index 92b774ffb7..e76770e523 100644 --- a/modAionImpl/src/org/aion/zero/impl/cli/Cli.java +++ b/modAionImpl/src/org/aion/zero/impl/cli/Cli.java @@ -167,7 +167,13 @@ public ReturnType call(final String[] args, Cfg cfg) { // 5. options that can be influenced by the -d and -n arguments if (options.isInfo()) { - cfg.fromXML(); + File configFile = cfg.getExecConfigFile(); + if (!configFile.exists()) { + configFile = cfg.getInitialConfigFile(); + } + + System.out.println("Reading config file at " + configFile.getAbsolutePath() + "."); + cfg.fromXML(configFile); printInfo(cfg); return ReturnType.EXIT; } diff --git a/modAionImpl/test/org/aion/zero/impl/cli/CliTest.java b/modAionImpl/test/org/aion/zero/impl/cli/CliTest.java index 4792826dcc..4e9a037d76 100644 --- a/modAionImpl/test/org/aion/zero/impl/cli/CliTest.java +++ b/modAionImpl/test/org/aion/zero/impl/cli/CliTest.java @@ -85,7 +85,7 @@ public class CliTest { private static File testnetGenesis = new File(BASE_PATH, "config/mastery/genesis.json"); /** @implNote set this to true to enable printing */ - private static boolean verbose = false; + private static boolean verbose = true; @Before public void setup() { @@ -128,18 +128,13 @@ public void shutdown() { deleteRecursively(new File(BASE_PATH, "mastery")); } - /** Ensures that the -h and --help arguments do not fail. */ - @Test - public void testHelp() { - assertThat(cli.call(new String[] {"-h"}, cfg)).isEqualTo(EXIT); - assertThat(cli.call(new String[] {"--help"}, cfg)).isEqualTo(EXIT); - } - - /** Ensures that the -v and --version arguments do not fail. */ + /** + * Ensures that the { -h, --help, -v, --version } arguments work. + */ @Test - public void testVersion() { - assertThat(cli.call(new String[] {"-v"}, cfg)).isEqualTo(EXIT); - assertThat(cli.call(new String[] {"--version"}, cfg)).isEqualTo(EXIT); + @Parameters({"-h", "--help", "-v", "--version"}) + public void testHelpAndVersion(String option) { + assertThat(cli.call(new String[] {option}, cfg)).isEqualTo(EXIT); } /** Parameters for testing {@link #testDirectoryAndNetwork(String[], ReturnType, String)}. */ @@ -262,9 +257,9 @@ private Object parametersWithDirectoryAndNetwork() { return parameters.toArray(); } + /** - * Ensures that the -d, --datadir, -n and --network arguments do not - * fail. + * Ensures that the { -d, --datadir, -n, --network } arguments work. */ @Test @Parameters(method = "parametersWithDirectoryAndNetwork") @@ -281,6 +276,7 @@ public void testDirectoryAndNetwork( assertThat(cfg.getLogPath()).isEqualTo(new File(expectedPath, "log").getAbsolutePath()); assertThat(cfg.getKeystorePath()) .isEqualTo(new File(expectedPath, "keystore").getAbsolutePath()); + if (verbose) { printPaths(cfg); } @@ -307,7 +303,7 @@ private void printPaths(Cfg cfg) { + "\n----------------------------------------------------------------------------\n\n"); } - /** Parameters for testing {@link #testConfig(String[], ReturnType, String)}. */ + /** Parameters for testing {@link #testConfig(String[], String)}. */ @SuppressWarnings("unused") private Object parametersWithConfig() { List parameters = new ArrayList<>(); @@ -317,20 +313,20 @@ private Object parametersWithConfig() { for (String op : options) { // without parameter - parameters.add(new Object[] {new String[] {op}, EXIT, expected}); + parameters.add(new Object[] {new String[] {op}, expected}); // invalid parameter - parameters.add(new Object[] {new String[] {op, "invalid"}, EXIT, expected}); + parameters.add(new Object[] {new String[] {op, "invalid"}, expected}); // mainnet as parameter - parameters.add(new Object[] {new String[] {op, "mainnet"}, EXIT, expected}); + parameters.add(new Object[] {new String[] {op, "mainnet"}, expected}); } expected = new File(BASE_PATH, "mastery").getAbsolutePath(); for (String op : options) { // mastery as parameter - parameters.add(new Object[] {new String[] {op, "mastery"}, EXIT, expected}); + parameters.add(new Object[] {new String[] {op, "mastery"}, expected}); // testnet as parameter - parameters.add(new Object[] {new String[] {op, "testnet"}, EXIT, expected}); + parameters.add(new Object[] {new String[] {op, "testnet"}, expected}); } // config and directory @@ -344,23 +340,21 @@ private Object parametersWithConfig() { // with relative path parameters.add( new Object[] { - new String[] {opDir, dataDirectory, opCfg, valNet}, EXIT, expected + new String[] {opDir, dataDirectory, opCfg, valNet}, expected }); parameters.add( new Object[] { - new String[] {opCfg, valNet, opDir, dataDirectory}, EXIT, expected + new String[] {opCfg, valNet, opDir, dataDirectory}, expected }); // with absolute path parameters.add( new Object[] { new String[] {opDir, path.getAbsolutePath(), opCfg, valNet}, - EXIT, expected }); parameters.add( new Object[] { new String[] {opCfg, valNet, opDir, path.getAbsolutePath()}, - EXIT, expected }); } @@ -376,23 +370,21 @@ private Object parametersWithConfig() { // with relative path parameters.add( new Object[] { - new String[] {opDir, dataDirectory, opCfg, netVal}, EXIT, expected + new String[] {opDir, dataDirectory, opCfg, netVal}, expected }); parameters.add( new Object[] { - new String[] {opCfg, netVal, opDir, dataDirectory}, EXIT, expected + new String[] {opCfg, netVal, opDir, dataDirectory}, expected }); // with absolute path parameters.add( new Object[] { new String[] {opDir, path.getAbsolutePath(), opCfg, netVal}, - EXIT, expected }); parameters.add( new Object[] { new String[] {opCfg, netVal, opDir, path.getAbsolutePath()}, - EXIT, expected }); } @@ -402,22 +394,23 @@ private Object parametersWithConfig() { return parameters.toArray(); } - /** Ensures that the -c and --config arguments do not fail. */ + /** Ensures that the { -c, --config } arguments work. */ @Test @Parameters(method = "parametersWithConfig") - public void testConfig(String[] input, ReturnType expectedReturn, String expectedPath) { - assertThat(cli.call(input, cfg)).isEqualTo(expectedReturn); + public void testConfig(String[] input, String expectedPath) { + assertThat(cli.call(input, cfg)).isEqualTo(EXIT); assertThat(cfg.getBasePath()).isEqualTo(expectedPath); assertThat(cfg.getExecConfigPath()) .isEqualTo(new File(expectedPath, "config/config.xml").getAbsolutePath()); assertThat(cfg.getExecGenesisPath()) .isEqualTo(new File(expectedPath, "config/genesis.json").getAbsolutePath()); + if (verbose) { printPaths(cfg); } } - /** Parameters for testing {@link #testConfig_oldLocation(String[], ReturnType, String)}. */ + /** Parameters for testing {@link #testConfig_oldLocation(String[], String)}. */ @SuppressWarnings("unused") private Object parametersWithConfigForOldLocation() { List parameters = new ArrayList<>(); @@ -427,33 +420,32 @@ private Object parametersWithConfigForOldLocation() { for (String op : options) { // without parameter - parameters.add(new Object[] {new String[] {op}, EXIT, BASE_PATH}); + parameters.add(new Object[] {new String[] {op}, BASE_PATH}); // invalid parameter - parameters.add(new Object[] {new String[] {op, "invalid"}, EXIT, expected}); + parameters.add(new Object[] {new String[] {op, "invalid"}, expected}); // mainnet as parameter - parameters.add(new Object[] {new String[] {op, "mainnet"}, EXIT, expected}); + parameters.add(new Object[] {new String[] {op, "mainnet"}, expected}); } expected = new File(BASE_PATH, "mastery").getAbsolutePath(); for (String op : options) { // mastery as parameter - parameters.add(new Object[] {new String[] {op, "mastery"}, EXIT, expected}); + parameters.add(new Object[] {new String[] {op, "mastery"}, expected}); // testnet as parameter - parameters.add(new Object[] {new String[] {op, "testnet"}, EXIT, expected}); + parameters.add(new Object[] {new String[] {op, "testnet"}, expected}); } return parameters.toArray(); } /** - * Ensures that the -c and --config arguments do not fail when input uses old - * config location. + * Ensures that the { -c, --config } arguments work when using old config + * location. */ @Test @Parameters(method = "parametersWithConfigForOldLocation") - public void testConfig_oldLocation( - String[] input, ReturnType expectedReturn, String expectedPath) { + public void testConfig_oldLocation(String[] input, String expectedPath) { // ensure config exists on disk at expected location for old kernel if (!oldConfig.exists()) { File configPath = new File(BASE_PATH, "config"); @@ -464,23 +456,150 @@ public void testConfig_oldLocation( Cli.copyRecursively(genesis, oldGenesis); } - assertThat(cli.call(input, cfg)).isEqualTo(expectedReturn); + assertThat(cli.call(input, cfg)).isEqualTo(EXIT); assertThat(cfg.getBasePath()).isEqualTo(expectedPath); assertThat(cfg.getExecConfigPath()) .isEqualTo(new File(expectedPath, "config/config.xml").getAbsolutePath()); assertThat(cfg.getExecGenesisPath()) .isEqualTo(new File(expectedPath, "config/genesis.json").getAbsolutePath()); + if (verbose) { printPaths(cfg); } } - /** Ensures that the -i and --info arguments do not fail. */ + /** Parameters for testing {@link #testInfo(String[], ReturnType, String)}. */ + @SuppressWarnings("unused") + private Object parametersWithInfo() { + List parameters = new ArrayList<>(); + + String[] options = new String[] {"-i", "--info"}; + String expected = new File(BASE_PATH, "mainnet").getAbsolutePath(); + + // only info + for (String op : options) { + // without parameter + parameters.add(new Object[] {new String[] {op}, EXIT, expected}); + // invalid parameter + parameters.add(new Object[] {new String[] {op, "value"}, ERROR, BASE_PATH}); + } + + // with network + expected = new File(BASE_PATH, "mastery").getAbsolutePath(); + for (String op : options) { + // mastery as parameter + parameters.add(new Object[] {new String[] {op, "-n", "mastery"}, EXIT, expected}); + parameters.add(new Object[] {new String[] {"-n", "mastery", op}, EXIT, expected}); + // invalid parameter + parameters.add( + new Object[] {new String[] {op, "value", "-n", "mastery"}, ERROR, BASE_PATH}); + } + + // with directory + expected = new File(path, "mainnet").getAbsolutePath(); + for (String op : options) { + // with relative path + parameters.add(new Object[] {new String[] {op, "-d", dataDirectory}, EXIT, expected}); + parameters.add(new Object[] {new String[] {"-d", dataDirectory, op}, EXIT, expected}); + // + invalid parameter + parameters.add( + new Object[] { + new String[] {op, "value", "-d", dataDirectory}, ERROR, BASE_PATH + }); + // with absolute path + parameters.add( + new Object[] {new String[] {op, "-d", path.getAbsolutePath()}, EXIT, expected}); + parameters.add( + new Object[] {new String[] {"-d", path.getAbsolutePath(), op}, EXIT, expected}); + // + invalid parameter + parameters.add( + new Object[] { + new String[] {op, "value", "-d", path.getAbsolutePath()}, ERROR, BASE_PATH + }); + } + + // with network and directory + expected = new File(path, "mastery").getAbsolutePath(); + for (String op : options) { + // with relative path + parameters.add( + new Object[] { + new String[] {op, "-d", dataDirectory, "-n", "mastery"}, EXIT, expected + }); + parameters.add( + new Object[] { + new String[] {"-n", "mastery", op, "-d", dataDirectory}, EXIT, expected + }); + parameters.add( + new Object[] { + new String[] {"-n", "mastery", "-d", dataDirectory, op}, EXIT, expected + }); + // with absolute path + parameters.add( + new Object[] { + new String[] {op, "-n", "mastery", "-d", path.getAbsolutePath()}, + EXIT, + expected + }); + parameters.add( + new Object[] { + new String[] {"-d", path.getAbsolutePath(), op, "-n", "mastery"}, + EXIT, + expected + }); + parameters.add( + new Object[] { + new String[] {"-d", path.getAbsolutePath(), "-n", "mastery", op}, + EXIT, + expected + }); + } + + return parameters.toArray(); + } + + /** Ensures that the { -i, --info } arguments work. */ @Test - @Ignore - public void testInfo() { - assertThat(cli.call(new String[] {"-i"}, cfg)).isEqualTo(EXIT); - assertThat(cli.call(new String[] {"--info"}, cfg)).isEqualTo(EXIT); + @Parameters(method = "parametersWithInfo") + public void testInfo(String[] input, ReturnType expectedReturn, String expectedPath) { + assertThat(cli.call(input, cfg)).isEqualTo(expectedReturn); + assertThat(cfg.getBasePath()).isEqualTo(expectedPath); + } + + /** + * Ensures that the { -i, --info } arguments work when using old config location. + */ + @Test + @Parameters({"-i", "--info"}) + public void testInfo_oldLocation(String option) { + // ensure config exists on disk at expected location for old kernel + if (!oldConfig.exists()) { + File configPath = new File(BASE_PATH, "config"); + if (!configPath.exists()) { + configPath.mkdirs(); + } + cfg.toXML(null, oldConfig); + Cli.copyRecursively(genesis, oldGenesis); + } + + assertThat(cli.call(new String[] {option}, cfg)).isEqualTo(EXIT); + assertThat(cfg.getBasePath()).isEqualTo(BASE_PATH); + } + + /** + * Ensures that the { -i, --info } arguments work when using the config location + * copied to the execution directory. + */ + @Test + @Parameters({"-i", "--info"}) + public void testInfo_execLocation(String option) { + // generates the config at the required destination + cli.call(new String[] {"-c", "-d", dataDirectory}, cfg); + + // ensure config exists on disk at expected location for old kernel + assertThat(cfg.getExecConfigFile().exists()).isTrue(); + + assertThat(cli.call(new String[] {option, "-d", dataDirectory}, cfg)).isEqualTo(EXIT); } // /** @@ -550,7 +669,7 @@ public void testInfo() { // assertEquals(ERROR, cli.call(new String[] {"--config", "random"}, CfgAion.inst())); // } - /** Tests the -a create arguments do not fail. */ + /** Tests the -a create arguments work. */ @Test @Ignore public void testCreateAccount() { @@ -558,7 +677,7 @@ public void testCreateAccount() { assertEquals(EXIT, cli.call(args, CfgAion.inst())); } - /** Tests the -a list arguments do not fail. */ + /** Tests the -a list arguments work. */ @Test @Ignore public void testListAccounts() { @@ -566,7 +685,7 @@ public void testListAccounts() { assertEquals(EXIT, cli.call(args, CfgAion.inst())); } - /** Tests the -a export arguments do not fail on a valid account. */ + /** Tests the -a export arguments work on a valid account. */ @Test @Ignore public void testExportPrivateKey() { @@ -590,7 +709,7 @@ public void testExportSubstringOfAccount() { assertEquals(ERROR, cli.call(args, CfgAion.inst())); } - /** Tests the -a import arguments do not fail on a fail import key. */ + /** Tests the -a import arguments work on a fail import key. */ @Test @Ignore public void testImportPrivateKey() { @@ -610,7 +729,7 @@ public void testImportNonPrivateKey() { assertEquals(ERROR, cli.call(args, CfgAion.inst())); } - /** Tests the -a import arguments do not fail when a valid private key is supplied. */ + /** Tests the -a import arguments work when a valid private key is supplied. */ @Test @Ignore public void testImportPrivateKey2() { From 7d90274fc9bb0442ffff00ca2b589981931aea52 Mon Sep 17 00:00:00 2001 From: Alexandra Date: Wed, 19 Sep 2018 17:11:46 -0400 Subject: [PATCH 17/31] support for absolute paths for the log and database directories #627 --- .../org/aion/zero/impl/config/CfgAion.java | 176 ++++++++++-------- .../test/org/aion/zero/impl/cli/CliTest.java | 104 ++++------- 2 files changed, 135 insertions(+), 145 deletions(-) diff --git a/modAionImpl/src/org/aion/zero/impl/config/CfgAion.java b/modAionImpl/src/org/aion/zero/impl/config/CfgAion.java index 0280e6d33e..b08bee85a3 100644 --- a/modAionImpl/src/org/aion/zero/impl/config/CfgAion.java +++ b/modAionImpl/src/org/aion/zero/impl/config/CfgAion.java @@ -25,20 +25,33 @@ package org.aion.zero.impl.config; -import java.io.*; +import com.google.common.base.Objects; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileWriter; +import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.UUID; -import javax.xml.stream.*; -import com.google.common.base.Objects; -import org.aion.mcf.config.*; +import javax.xml.stream.XMLInputFactory; +import javax.xml.stream.XMLOutputFactory; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamReader; +import javax.xml.stream.XMLStreamWriter; +import org.aion.mcf.config.Cfg; +import org.aion.mcf.config.CfgApi; +import org.aion.mcf.config.CfgDb; +import org.aion.mcf.config.CfgGui; +import org.aion.mcf.config.CfgLog; +import org.aion.mcf.config.CfgNet; +import org.aion.mcf.config.CfgReports; +import org.aion.mcf.config.CfgSync; +import org.aion.mcf.config.CfgTx; import org.aion.zero.exceptions.HeaderStructureException; import org.aion.zero.impl.AionGenesis; import org.aion.zero.impl.GenesisBlockLoader; -/** - * @author chris - */ +/** @author chris */ public final class CfgAion extends Cfg { private static String NETWORK = "mainnet"; @@ -52,7 +65,6 @@ public final class CfgAion extends Cfg { private static final String NODE_ID_PLACEHOLDER = "[NODE-ID-PLACEHOLDER]"; - public CfgAion() { this.mode = "aion"; this.id = UUID.randomUUID().toString(); @@ -105,8 +117,7 @@ public CfgConsensusPow getConsensus() { } public synchronized AionGenesis getGenesis() { - if (this.genesis == null) - setGenesis(); + if (this.genesis == null) setGenesis(); return this.genesis; } @@ -118,7 +129,7 @@ public static int getK() { return K; } - private void closeFileInputStream(final FileInputStream fis){ + private void closeFileInputStream(final FileInputStream fis) { if (fis != null) { try { fis.close(); @@ -136,25 +147,24 @@ public void dbFromXML() { try { fis = new FileInputStream(cfgFile); XMLStreamReader sr = input.createXMLStreamReader(fis); - loop: while (sr.hasNext()) { + loop: + while (sr.hasNext()) { int eventType = sr.next(); switch (eventType) { - case XMLStreamReader.START_ELEMENT: - String elementName = sr.getLocalName().toLowerCase(); - switch (elementName) { - case "db": - this.db.fromXML(sr); - break; - default: - skipElement(sr); - break; - } - break; - case XMLStreamReader.END_ELEMENT: - if (sr.getLocalName().toLowerCase().equals("aion")) - break loop; - else + case XMLStreamReader.START_ELEMENT: + String elementName = sr.getLocalName().toLowerCase(); + switch (elementName) { + case "db": + this.db.fromXML(sr); + break; + default: + skipElement(sr); + break; + } break; + case XMLStreamReader.END_ELEMENT: + if (sr.getLocalName().toLowerCase().equals("aion")) break loop; + else break; } } } catch (Exception e) { @@ -167,61 +177,60 @@ public void dbFromXML() { public boolean fromXML(final XMLStreamReader sr) throws XMLStreamException { boolean shouldWriteBackToFile = false; - loop: while (sr.hasNext()) { + loop: + while (sr.hasNext()) { int eventType = sr.next(); switch (eventType) { case XMLStreamReader.START_ELEMENT: String elementName = sr.getLocalName().toLowerCase(); switch (elementName) { - case "id": - String nodeId = readValue(sr); - if(NODE_ID_PLACEHOLDER.equals(nodeId)) { - this.id = UUID.randomUUID().toString(); - shouldWriteBackToFile = true; - } else { - this.id = nodeId; - } - break; - case "mode": - this.mode = readValue(sr); - break; - case "api": - this.api.fromXML(sr); - break; - case "net": - this.net.fromXML(sr); - break; - case "sync": - this.sync.fromXML(sr); - break; - case "consensus": - this.consensus.fromXML(sr); - break; - case "db": - this.db.fromXML(sr); - break; - case "log": - this.log.fromXML(sr); - break; - case "tx": - this.tx.fromXML(sr); - break; - case "reports": - this.reports.fromXML(sr); - break; - case "gui": - this.gui.fromXML(sr); - break; - default: - skipElement(sr); - break; + case "id": + String nodeId = readValue(sr); + if (NODE_ID_PLACEHOLDER.equals(nodeId)) { + this.id = UUID.randomUUID().toString(); + shouldWriteBackToFile = true; + } else { + this.id = nodeId; + } + break; + case "mode": + this.mode = readValue(sr); + break; + case "api": + this.api.fromXML(sr); + break; + case "net": + this.net.fromXML(sr); + break; + case "sync": + this.sync.fromXML(sr); + break; + case "consensus": + this.consensus.fromXML(sr); + break; + case "db": + this.db.fromXML(sr); + break; + case "log": + this.log.fromXML(sr); + break; + case "tx": + this.tx.fromXML(sr); + break; + case "reports": + this.reports.fromXML(sr); + break; + case "gui": + this.gui.fromXML(sr); + break; + default: + skipElement(sr); + break; } break; case XMLStreamReader.END_ELEMENT: - if (sr.getLocalName().toLowerCase().equals("aion")) - break loop; - else - break; + if (sr.getLocalName().toLowerCase().equals("aion")) break loop; + else break; } } return shouldWriteBackToFile; @@ -249,6 +258,19 @@ public boolean fromXML(File cfgFile) { System.out.println(""); System.exit(1); } + + // checks for absolute path for database + File db = new File(this.getDb().getPath()); + if (db.isAbsolute()) { + this.setDatabaseDirectory(db); + } + + // checks for absolute path for log + File log = new File(this.getLog().getLogPath()); + if (log.isAbsolute()) { + this.setLogDirectory(log); + } + return shouldWriteBackToFile; } @@ -279,8 +301,7 @@ public void toXML(final String[] args, File file) { if (subArgsArr.length > 0) { List _nodes = new ArrayList<>(); for (String subArg : subArgsArr) { - if (!subArg.equals("")) - _nodes.add(subArg); + if (!subArg.equals("")) _nodes.add(subArg); } this.getNet().setNodes(_nodes.toArray(new String[0])); } @@ -306,8 +327,7 @@ public void toXML(final String[] args, File file) { } } } - if (override) - System.out.println("Config Override"); + if (override) System.out.println("Config Override"); } XMLOutputFactory output = XMLOutputFactory.newInstance(); diff --git a/modAionImpl/test/org/aion/zero/impl/cli/CliTest.java b/modAionImpl/test/org/aion/zero/impl/cli/CliTest.java index 4e9a037d76..52a1177e96 100644 --- a/modAionImpl/test/org/aion/zero/impl/cli/CliTest.java +++ b/modAionImpl/test/org/aion/zero/impl/cli/CliTest.java @@ -66,7 +66,9 @@ public class CliTest { private static final String initialConfigFile = "test_resources/config.xml"; private static final String initialGenesisFile = "test_resources/genesis.json"; private static final String dataDirectory = "datadir"; + private static final String alternativeDirectory = "random"; private static final File path = new File(BASE_PATH, dataDirectory); + private static final File alternativePath = new File(BASE_PATH, alternativeDirectory); private static File config = BASE_PATH.contains(module) @@ -118,6 +120,7 @@ public void setup() { @After public void shutdown() { deleteRecursively(path); + deleteRecursively(alternativePath); // to avoid deleting config for all tests if (BASE_PATH.contains(module)) { @@ -303,6 +306,40 @@ private void printPaths(Cfg cfg) { + "\n----------------------------------------------------------------------------\n\n"); } + /** + * Ensures that the { -d, --datadir, -n, --network } arguments work + * with absolute paths for the database and log. The absolute path overwrites the datadir option + * location. + */ + @Test + @Parameters(method = "parametersWithDirectoryAndNetwork") + public void testDirectoryAndNetwork_wAbsoluteDbAndLogPath( + String[] input, ReturnType expectedReturn, String expectedPath) { + + String db = new File(alternativePath, "database").getAbsolutePath(); + cfg.getDb().setPath(db); + String log = new File(alternativePath, "log").getAbsolutePath(); + cfg.getLog().setLogPath(log); + + // save and reload for changes to take effect + cfg.toXML(null); + cfg.fromXML(); + + assertThat(cli.call(input, cfg)).isEqualTo(expectedReturn); + assertThat(cfg.getBasePath()).isEqualTo(expectedPath); + + assertThat(cfg.getDatabasePath()) + .isNotEqualTo(new File(expectedPath, "database").getAbsolutePath()); + assertThat(cfg.getDatabasePath()).isEqualTo(db); + + assertThat(cfg.getLogPath()).isNotEqualTo(new File(expectedPath, "log").getAbsolutePath()); + assertThat(cfg.getLogPath()).isEqualTo(log); + + if (verbose) { + printPaths(cfg); + } + } + /** Parameters for testing {@link #testConfig(String[], String)}. */ @SuppressWarnings("unused") private Object parametersWithConfig() { @@ -602,73 +639,6 @@ public void testInfo_execLocation(String option) { assertThat(cli.call(new String[] {option, "-d", dataDirectory}, cfg)).isEqualTo(EXIT); } - // /** - // * Sets up a spy Cli class that returns the String "password" when the cli.readPassword() - // is - // * called using any two params. - // */ - // @Before - // public void setup() throws IOException { - // doReturn("password").when(cli).readPassword(any(), any()); - // - // // Copies config folder recursively - // File src = new File(BASE_PATH + "/../modBoot/resource"); - // File dst = new File(BASE_PATH + "/config"); - // copyRecursively(src, dst); - // - // CfgAion.setConfFilePath(BASE_PATH + "/config/mainnet/config.xml"); - // CfgAion.setGenesisFilePath(BASE_PATH + "/config/mainnet/genesis.json"); - // Keystore.setKeystorePath(BASE_PATH + "/keystore"); - // } - // - // @After - // public void shutdown() { - // // Deletes created folders recursively - // File path1 = new File(BASE_PATH + "/aaaaaaaa"); - // File path2 = new File(BASE_PATH + "/abbbbbbb"); - // File path3 = new File(BASE_PATH + "/abcccccc"); - // File path4 = new File(BASE_PATH + "/keystore"); - // File path5 = new File(BASE_PATH + "/config"); - // if (path1.exists() - // || path2.exists() - // || path3.exists() - // || path4.exists() - // || path5.exists()) { - // deleteRecursively(path1); - // deleteRecursively(path2); - // deleteRecursively(path3); - // deleteRecursively(path4); - // deleteRecursively(path5); - // } - // - // CfgAion.setConfFilePath(BASE_PATH + "/config/mainnet/config.xml"); - // CfgAion.setGenesisFilePath(BASE_PATH + "/config/mainnet/genesis.json"); - // Keystore.setKeystorePath(BASE_PATH + "/keystore"); - // } - - // /** Ensures correct behavior for the -c and --config arguments. */ - // @Test - // public void testConfig() { - // // compatibility with old kernels - // assertEquals(EXIT, cli.call(new String[] {"-c"}, CfgAion.inst())); - // assertEquals(EXIT, cli.call(new String[] {"--config"}, CfgAion.inst())); - // - // // available networks - // for (Cli.Network net : Cli.Network.values()) { - // assertEquals(EXIT, cli.call(new String[] {"-c", net.toString()}, CfgAion.inst())); - // assertEquals(EXIT, cli.call(new String[] {"--config", net.toString()}, - // CfgAion.inst())); - // } - // - // // accepted alias - // assertEquals(EXIT, cli.call(new String[] {"-c", "testnet"}, CfgAion.inst())); - // assertEquals(EXIT, cli.call(new String[] {"--config", "testnet"}, CfgAion.inst())); - // - // // incorrect value - // assertEquals(ERROR, cli.call(new String[] {"-c", "random"}, CfgAion.inst())); - // assertEquals(ERROR, cli.call(new String[] {"--config", "random"}, CfgAion.inst())); - // } - /** Tests the -a create arguments work. */ @Test @Ignore From 9ecb1c5c26762a7122235e8219f4cde3ecbb4ba6 Mon Sep 17 00:00:00 2001 From: Alexandra Date: Thu, 20 Sep 2018 13:41:06 -0400 Subject: [PATCH 18/31] correct call to CLI from boot given the new implementation --- .../src/org/aion/zero/impl/cli/Cli.java | 35 +++++++++++++++---- modBoot/src/org/aion/Aion.java | 26 +++++--------- modMcf/src/org/aion/mcf/config/Cfg.java | 34 ++++++++++++------ 3 files changed, 61 insertions(+), 34 deletions(-) diff --git a/modAionImpl/src/org/aion/zero/impl/cli/Cli.java b/modAionImpl/src/org/aion/zero/impl/cli/Cli.java index e76770e523..cbb3f9ee3e 100644 --- a/modAionImpl/src/org/aion/zero/impl/cli/Cli.java +++ b/modAionImpl/src/org/aion/zero/impl/cli/Cli.java @@ -178,6 +178,9 @@ public ReturnType call(final String[] args, Cfg cfg) { return ReturnType.EXIT; } + // make directories for kernel execution + makeDirs(cfg); + if (options.isCreateAccount()) { if (!createAccount()) { return ERROR; @@ -370,8 +373,6 @@ public ReturnType call(final String[] args, Cfg cfg) { return EXIT; } - // make directories for kernel execution - makeDirs(cfg); // if no return happened earlier, run the kernel return RUN; } catch (Throwable e) { @@ -481,12 +482,32 @@ private void makeDirs(Cfg cfg) { file.mkdirs(); } - // copy config & genesis - copyRecursively(cfg.getInitialConfigFile(), cfg.getExecConfigFile()); - copyRecursively(cfg.getInitialGenesisFile(), cfg.getExecGenesisFile()); + // copy config file + File initial = cfg.getInitialConfigFile(); + File target = cfg.getExecConfigFile(); + if (!initial.equals(target)) { + copyRecursively(initial, target); + } + + // copy genesis file + initial = cfg.getInitialGenesisFile(); + target = cfg.getExecGenesisFile(); + if (!initial.equals(target)) { + copyRecursively(initial, target); + } + + // create target log directory + file = cfg.getLogDirectory(); + if (!file.exists()) { + file.mkdirs(); + } + + // create target database directory + file = cfg.getDatabaseDirectory(); + if (!file.exists()) { + file.mkdirs(); + } - // TODO-Ale: create target log directory - // TODO-Ale: create target database directory // TODO-Ale: create target keystore directory } diff --git a/modBoot/src/org/aion/Aion.java b/modBoot/src/org/aion/Aion.java index 3a62ad19f1..6cc4a5f826 100644 --- a/modBoot/src/org/aion/Aion.java +++ b/modBoot/src/org/aion/Aion.java @@ -83,22 +83,14 @@ public static void main(String args[]) { CfgAion cfg = CfgAion.inst(); - /* - * if in the config.xml id is set as default [NODE-ID-PLACEHOLDER] - * return true which means should save back to xml config - */ - if (cfg.fromXML()) { - if(args != null && args.length > 0 && !(args[0].equals("-v")||args[0].equals("--version"))) { - cfg.toXML(new String[]{"--id=" + cfg.getId()}); - } - } - - // Reads CLI (must be after the cfg.fromXML()) - if (args != null && args.length > 0) { - ReturnType ret = new Cli().call(args, cfg); - if (ret != ReturnType.RUN) { - exit(ret.getValue()); - } + // true means the UUID must be set + boolean overwrite = cfg.fromXML(); + ReturnType ret = new Cli().call(args, cfg); + if (ret != ReturnType.RUN) { + exit(ret.getValue()); + } else if (overwrite) { + // only updating the file in case of RUN return value + cfg.toXML(new String[]{"--id=" + cfg.getId()}); } // UUID check @@ -126,7 +118,7 @@ public static void main(String args[]) { * Logger initialize with LOGFILE and LOGPATH (user config inputs) */ AionLoggerFactory - .init(cfg.getLog().getModules(), cfg.getLog().getLogFile(), cfg.getLog().getLogPath()); + .init(cfg.getLog().getModules(), cfg.getLog().getLogFile(), cfg.getLogPath()); Logger genLog = AionLoggerFactory.getLogger(LogEnum.GEN.name()); // TODO-Ale: fix diff --git a/modMcf/src/org/aion/mcf/config/Cfg.java b/modMcf/src/org/aion/mcf/config/Cfg.java index e57b47cbb6..ee04bbfb3b 100644 --- a/modMcf/src/org/aion/mcf/config/Cfg.java +++ b/modMcf/src/org/aion/mcf/config/Cfg.java @@ -207,11 +207,15 @@ public String getNetwork() { } public String getLogPath() { + return getLogDirectory().getAbsolutePath(); + } + + public File getLogDirectory() { if (logDirectory != null) { // set when using absolute paths - return logDirectory.getAbsolutePath(); + return logDirectory; } else { - return new File(getExecDirectory(), getLog().getLogPath()).getAbsolutePath(); + return new File(getExecDirectory(), getLog().getLogPath()); } } @@ -220,11 +224,15 @@ public void setLogDirectory(File _logDirectory) { } public String getDatabasePath() { + return getDatabaseDirectory().getAbsolutePath(); + } + + public File getDatabaseDirectory() { if (databaseDirectory != null) { // set when using absolute paths - return databaseDirectory.getAbsolutePath(); + return databaseDirectory; } else { - return new File(getExecDirectory(), getDb().getPath()).getAbsolutePath(); + return new File(getExecDirectory(), getDb().getPath()); } } @@ -334,16 +342,22 @@ public static void skipElement(final XMLStreamReader sr) throws XMLStreamExcepti } /** - * @return boolean value used return to also determine if we need to write back to file with - * current config + * Loads the configuration from the default config file. Returns a boolean value used to + * determine if the configuration needs to be saved back to disk with a valid peer identifier. + * + * @return {@code true} when the peer id read from the file is [NODE-ID-PLACEHOLDER] which needs + * to be replaced by a valid user ID on disk, {@code false} otherwise. */ public abstract boolean fromXML(); /** - * @return boolean value used return to also determine if we need to write back to file with - * current config * return true which means should save back to xml config return true which - * means should save back to xml config if in the config.xml id is set as default - * [NODE-ID-PLACEHOLDER] return true which means should save back to xml config + * Loads the configuration from the given config file. + * + *

    Returns a boolean value used to determine if the configuration needs to be saved back to + * disk with a valid peer identifier. + * + * @return {@code true} when the peer id read from the file is [NODE-ID-PLACEHOLDER] which needs + * to be replaced by a valid user ID on disk, {@code false} otherwise. */ public abstract boolean fromXML(File configFile); From cb389d08da694082866ab34ba9c75e1c4e6980a8 Mon Sep 17 00:00:00 2001 From: Alexandra Date: Thu, 20 Sep 2018 15:42:46 -0400 Subject: [PATCH 19/31] adding mastery config and correct conquest genesis --- modBoot/resource/conquest/genesis.json | 39 +++++------ modBoot/resource/mastery/config.xml | 97 ++++++++++++++++++++++++++ modBoot/resource/mastery/genesis.json | 19 +++++ 3 files changed, 134 insertions(+), 21 deletions(-) create mode 100755 modBoot/resource/mastery/config.xml create mode 100755 modBoot/resource/mastery/genesis.json diff --git a/modBoot/resource/conquest/genesis.json b/modBoot/resource/conquest/genesis.json index ff86d9ca7b..b2cff36d2a 100644 --- a/modBoot/resource/conquest/genesis.json +++ b/modBoot/resource/conquest/genesis.json @@ -1,52 +1,49 @@ { "alloc": { - "0xa0483412e8c8e769df037231d336e96f7f6d843cf7224c3d8fbe0ec7cdc12ac6": { - "balance": "314159000000000000000000" + "0xa025f4fd54064e869f158c1b4eb0ed34820f67e60ee80a53b469f725efc06378": { + "balance": "465934586660000000000000000" }, - "0xa0353561f8b6b5a8b56d535647a4ddd7278e80c2494e3314d1f0605470f56925": { + "0xa0e6a0c9c85db355fdceccc44444618b3213b3d5c3c3e26dcfe039ed07f310cd": { "balance": "314159000000000000000000" }, - "0xa0274c1858ca50576f4d4d18b719787b76bb454c33749172758a620555bf4586": { + "0xa08849e680dbede69077b3be7d9c8c37f5849c46cce63eafb26bd2083ce32a48": { "balance": "314159000000000000000000" }, - "0xa06691a968e8fe22dc79b6dd503d44cb96d9a523ae265b63c6752389be90185d": { + "0xa00d29449981629c92658eaedb6b383e7d98ac2933d4d0783f6d60313a826f46": { "balance": "314159000000000000000000" }, - "0xa0a95b372efe55c77a75364407f0403dfefd3131519ca980b2d92b1d2d7297a7": { + "0xa04be405fe794146df4a6a0cac0009933323d65e29dedfaf80a1f880fa8cd329": { "balance": "314159000000000000000000" }, - "0xa07262e1d026fca027e5f4f38668303b74a2bba8bd07470fa39d0f3b03f882f1": { + "0xa0dd7205acbaad446e7bd4e1755a9d1e8dd74b793656cc7af5876cba0f616bab": { "balance": "314159000000000000000000" }, - "0xa08f92c80e34c95b8b2e8cb208d47e3e6048b7e0ea44d866e33e865c365b1417": { + "0xa076b66cb825ca43aab11aa807ced2586023e6a62d8d600b0f3e16445a8d3ced": { "balance": "314159000000000000000000" }, - "0xa022045f22772463c94e08d1fd32f7b251d4c9cfd1c92e039f1517b906776283": { + "0xa0438499953bab8e92399de99e1edab602119d75dd0e90da256efd461b0cfe29": { "balance": "314159000000000000000000" }, - "0xa04c282f636feff4d6b35174fc2d8e05c23df4b1d59508712712627184dd8a93": { + "0xa05765005030df3d38502f6d3144a321631bd8663b5a87bc12d69a1330c9481a": { "balance": "314159000000000000000000" }, - "0xa0f8654c63ae53598cf42435f53b1ebd9b7df6cbceba10af235aa2393f03034c": { + "0xa046cc48bcde4b0b2ce2dbefb318f3778946b6c0011f691ecc4025cc145a93d3": { "balance": "314159000000000000000000" }, - "0xa0f3defb01b531c5a28680eb928e79ea18bc155f1060e1d923d660d74883518b": { + "0xa07c95cc8729a0503c5ad50eb37ec8a27cd22d65de3bb225982ec55201366920": { "balance": "314159000000000000000000" - }, - "0xa02198c9192bb89e9b2e8536d96cbf287fde80625afcf1a5e5632e23c1260d61": { - "balance": "465934586660000000000000000" } }, - "networkBalanceAlloc": { + "networkBalanceAllocs": { "0": { "balance": "465934586660000000000000000" } }, - "energyLimit": "10000000", + "energyLimit": "15000000", "nonce": "0x00", - "difficulty": "0x10", + "difficulty": "0x0010", "coinbase": "0x0000000000000000000000000000000000000000000000000000000000000000", - "timestamp": "1497536993", - "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", - "chainId": "2" + "timestamp": "1525924800", + "parentHash": "0x10E71BF64DCB8C60766CCB491AB6E3ACEC1AB07D0D5A088FCF533CBFBC801295", + "chainId": "128" } diff --git a/modBoot/resource/mastery/config.xml b/modBoot/resource/mastery/config.xml new file mode 100755 index 0000000000..1f1ec44ce6 --- /dev/null +++ b/modBoot/resource/mastery/config.xml @@ -0,0 +1,97 @@ + + + aion + [NODE-ID-PLACEHOLDER] + + + + false + + web3,eth,personal,stratum,ops + + + + + 10E9 + + 100E9 + + false + + + + 32 + + p2p://a30d1000-8c7e-496c-9c4e-c89318280274@168.62.170.146:30303 + p2p://a30d2000-729a-4584-86f1-e19ab97cf9ce@23.96.22.19:30303 + p2p://a30d3000-216b-47d4-ac44-5d8181b56e7e@137.117.56.84:30303 + + + 0.0.0.0 + 30303 + false + 128 + + + + + 32 + + false + + + true + a08fc457b39b03c30dc71bdb89a4d0409dd4fa42f6539a5c3ee4054af9b71f23 + 1 + AION + + + + + + + + + + database + + true + + + + + FULL + + + leveldb + + true + + + + true + + log + WARN + INFO + ERROR + INFO + INFO + ERROR + INFO + INFO + INFO + + + + + true + + aion.sh + + /placeholder/for/aion_root_dir + + + + + diff --git a/modBoot/resource/mastery/genesis.json b/modBoot/resource/mastery/genesis.json new file mode 100755 index 0000000000..a489e2661b --- /dev/null +++ b/modBoot/resource/mastery/genesis.json @@ -0,0 +1,19 @@ +{ + "alloc": { + "0xa0b88269779d225510ca880ed742e445db0c70efb1ee3159b6d56479ae3501f9": { + "balance": "314000000000000000000000000" + } + }, + "networkBalanceAllocs": { + "0": { + "balance": "465934586660000000000000000" + } + }, + "energyLimit": "15000000", + "nonce": "0x00", + "difficulty": "0x0008", + "coinbase": "0xa08fc457b39b03c30dc71bdb89a4d0409dd4fa42f6539a5c3ee4054af9b71f23", + "timestamp": "1525924800", + "parentHash": "0x10E71BF64DCB8C60766CCB491AB6E3ACEC1AB07D0D5A088FCF533CBFBC801295", + "chainId": "32" +} From 5bf4f1501bb91bc4e0b08aff413ce012e5206531 Mon Sep 17 00:00:00 2001 From: Alexandra Date: Thu, 20 Sep 2018 17:31:12 -0400 Subject: [PATCH 20/31] debugging and enhanced unit tests for config option --- .../src/org/aion/zero/impl/cli/Cli.java | 46 ++++++----- .../test/org/aion/zero/impl/cli/CliTest.java | 43 +++++++---- modBoot/src/org/aion/Aion.java | 5 -- modMcf/src/org/aion/mcf/config/Cfg.java | 76 +++++++++++-------- 4 files changed, 101 insertions(+), 69 deletions(-) diff --git a/modAionImpl/src/org/aion/zero/impl/cli/Cli.java b/modAionImpl/src/org/aion/zero/impl/cli/Cli.java index cbb3f9ee3e..eadc834343 100644 --- a/modAionImpl/src/org/aion/zero/impl/cli/Cli.java +++ b/modAionImpl/src/org/aion/zero/impl/cli/Cli.java @@ -39,7 +39,6 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; -import java.util.UUID; import org.aion.base.util.Hex; import org.aion.crypto.ECKey; import org.aion.crypto.ECKeyFac; @@ -139,41 +138,43 @@ public ReturnType call(final String[] args, Cfg cfg) { // no return -> allow for other parameters combined with -d } + // reading from correct config file + File configFile = cfg.getExecConfigFile(); + if (!configFile.exists()) { + configFile = cfg.getInitialConfigFile(); + } + + // true means the UUID must be set + boolean overwrite = cfg.fromXML(configFile); + // 4. can be influenced by the -d argument above if (options.getConfig() != null) { // network was already set above - // TODO-Ale: handle case where cannot find initial file - // read from initial config - if (cfg.fromXML(cfg.getInitialConfigFile())) { - // set user id when not present - cfg.setId(UUID.randomUUID().toString()); - } + // if the directory was set we generate a new file + if (options.getDirectory() != null) { + configFile = cfg.getExecConfigFile(); - // ensure path exists - File dir = cfg.getExecConfigDirectory(); - if (!dir.exists()) { - dir.mkdirs(); + // ensure path exists + File dir = cfg.getExecConfigDirectory(); + if (!dir.exists()) { + dir.mkdirs(); + } + configFile.createNewFile(); } // save to disk - cfg.toXML(null, cfg.getExecConfigFile()); + cfg.toXML(null, configFile); - System.out.println("\nNew config generated at " + cfg.getExecConfigPath() + "."); + System.out.println("\nNew config generated at " + configFile + "."); return ReturnType.EXIT; } // 5. options that can be influenced by the -d and -n arguments if (options.isInfo()) { - File configFile = cfg.getExecConfigFile(); - if (!configFile.exists()) { - configFile = cfg.getInitialConfigFile(); - } - - System.out.println("Reading config file at " + configFile.getAbsolutePath() + "."); - cfg.fromXML(configFile); + System.out.println("Read config file at " + configFile.getAbsolutePath() + "."); printInfo(cfg); return ReturnType.EXIT; } @@ -181,6 +182,11 @@ public ReturnType call(final String[] args, Cfg cfg) { // make directories for kernel execution makeDirs(cfg); + if (overwrite) { + // only updating the file in case the user id was not set + cfg.toXML(new String[] {"--id=" + cfg.getId()}, cfg.getExecConfigFile()); + } + if (options.isCreateAccount()) { if (!createAccount()) { return ERROR; diff --git a/modAionImpl/test/org/aion/zero/impl/cli/CliTest.java b/modAionImpl/test/org/aion/zero/impl/cli/CliTest.java index 52a1177e96..45316dfbdc 100644 --- a/modAionImpl/test/org/aion/zero/impl/cli/CliTest.java +++ b/modAionImpl/test/org/aion/zero/impl/cli/CliTest.java @@ -100,7 +100,7 @@ public void setup() { if (!configPath.exists()) { configPath.mkdirs(); } - cfg.toXML(null, mainnetConfig); + Cli.copyRecursively(config, mainnetConfig); Cli.copyRecursively(genesis, mainnetGenesis); } @@ -110,7 +110,7 @@ public void setup() { if (!configPath.exists()) { configPath.mkdirs(); } - cfg.toXML(null, testnetConfig); + Cli.copyRecursively(config, mainnetConfig); Cli.copyRecursively(genesis, testnetGenesis); } @@ -340,34 +340,37 @@ public void testDirectoryAndNetwork_wAbsoluteDbAndLogPath( } } - /** Parameters for testing {@link #testConfig(String[], String)}. */ + /** Parameters for testing {@link #testConfig(String[], File, String)}. */ @SuppressWarnings("unused") private Object parametersWithConfig() { List parameters = new ArrayList<>(); String[] options = new String[] {"-c", "--config"}; + File config = new File(BASE_PATH, "config/mainnet/config.xml"); String expected = new File(BASE_PATH, "mainnet").getAbsolutePath(); for (String op : options) { // without parameter - parameters.add(new Object[] {new String[] {op}, expected}); + parameters.add(new Object[] {new String[] {op}, config, expected}); // invalid parameter - parameters.add(new Object[] {new String[] {op, "invalid"}, expected}); + parameters.add(new Object[] {new String[] {op, "invalid"}, config, expected}); // mainnet as parameter - parameters.add(new Object[] {new String[] {op, "mainnet"}, expected}); + parameters.add(new Object[] {new String[] {op, "mainnet"}, config, expected}); } + config = new File(BASE_PATH, "config/mastery/config.xml"); expected = new File(BASE_PATH, "mastery").getAbsolutePath(); for (String op : options) { // mastery as parameter - parameters.add(new Object[] {new String[] {op, "mastery"}, expected}); + parameters.add(new Object[] {new String[] {op, "mastery"}, config, expected}); // testnet as parameter - parameters.add(new Object[] {new String[] {op, "testnet"}, expected}); + parameters.add(new Object[] {new String[] {op, "testnet"}, config, expected}); } // config and directory String[] dir_options = new String[] {"-d", "--datadir"}; + config = new File(path, "mainnet/config/config.xml"); expected = new File(path, "mainnet").getAbsolutePath(); String[] net_values = new String[] {"mainnet", "invalid"}; @@ -377,21 +380,23 @@ private Object parametersWithConfig() { // with relative path parameters.add( new Object[] { - new String[] {opDir, dataDirectory, opCfg, valNet}, expected + new String[] {opDir, dataDirectory, opCfg, valNet}, config, expected }); parameters.add( new Object[] { - new String[] {opCfg, valNet, opDir, dataDirectory}, expected + new String[] {opCfg, valNet, opDir, dataDirectory}, config, expected }); // with absolute path parameters.add( new Object[] { new String[] {opDir, path.getAbsolutePath(), opCfg, valNet}, + config, expected }); parameters.add( new Object[] { new String[] {opCfg, valNet, opDir, path.getAbsolutePath()}, + config, expected }); } @@ -400,6 +405,7 @@ private Object parametersWithConfig() { // config and directory with testnet net_values = new String[] {"mastery", "testnet"}; + config = new File(path, "mastery/config/config.xml"); expected = new File(path, "mastery").getAbsolutePath(); for (String opDir : dir_options) { for (String opCfg : options) { @@ -407,21 +413,23 @@ private Object parametersWithConfig() { // with relative path parameters.add( new Object[] { - new String[] {opDir, dataDirectory, opCfg, netVal}, expected + new String[] {opDir, dataDirectory, opCfg, netVal}, config, expected }); parameters.add( new Object[] { - new String[] {opCfg, netVal, opDir, dataDirectory}, expected + new String[] {opCfg, netVal, opDir, dataDirectory}, config, expected }); // with absolute path parameters.add( new Object[] { new String[] {opDir, path.getAbsolutePath(), opCfg, netVal}, + config, expected }); parameters.add( new Object[] { new String[] {opCfg, netVal, opDir, path.getAbsolutePath()}, + config, expected }); } @@ -434,7 +442,11 @@ private Object parametersWithConfig() { /** Ensures that the { -c, --config } arguments work. */ @Test @Parameters(method = "parametersWithConfig") - public void testConfig(String[] input, String expectedPath) { + public void testConfig(String[] input, File expectedFile, String expectedPath) { + if (expectedFile.exists()) { + assertThat(cfg.fromXML(expectedFile)).isTrue(); + } + assertThat(cli.call(input, cfg)).isEqualTo(EXIT); assertThat(cfg.getBasePath()).isEqualTo(expectedPath); assertThat(cfg.getExecConfigPath()) @@ -442,6 +454,11 @@ public void testConfig(String[] input, String expectedPath) { assertThat(cfg.getExecGenesisPath()) .isEqualTo(new File(expectedPath, "config/genesis.json").getAbsolutePath()); + // cfg.resetInternal(); + + assertThat(expectedFile.exists()).isTrue(); + assertThat(cfg.fromXML(expectedFile)).isFalse(); + if (verbose) { printPaths(cfg); } diff --git a/modBoot/src/org/aion/Aion.java b/modBoot/src/org/aion/Aion.java index 6cc4a5f826..ddaed5f296 100644 --- a/modBoot/src/org/aion/Aion.java +++ b/modBoot/src/org/aion/Aion.java @@ -83,14 +83,9 @@ public static void main(String args[]) { CfgAion cfg = CfgAion.inst(); - // true means the UUID must be set - boolean overwrite = cfg.fromXML(); ReturnType ret = new Cli().call(args, cfg); if (ret != ReturnType.RUN) { exit(ret.getValue()); - } else if (overwrite) { - // only updating the file in case of RUN return value - cfg.toXML(new String[]{"--id=" + cfg.getId()}); } // UUID check diff --git a/modMcf/src/org/aion/mcf/config/Cfg.java b/modMcf/src/org/aion/mcf/config/Cfg.java index ee04bbfb3b..34e4b8faa7 100644 --- a/modMcf/src/org/aion/mcf/config/Cfg.java +++ b/modMcf/src/org/aion/mcf/config/Cfg.java @@ -30,20 +30,22 @@ /** @author chris */ public abstract class Cfg { - private final String configFile = "config.xml"; - private final String genesisFile = "genesis.json"; - private final String keystoreDirectory = "keystore"; - private final String configDirectory = "config"; + private final String configDirectoryName = "config"; + private final String configFileName = "config.xml"; + private final String genesisFileName = "genesis.json"; + private final String keystoreDirectoryName = "keystore"; private File logDirectory = null; private File databaseDirectory = null; private File execDirectory = null; + private File configFile = null; + private File genesisFile = null; private String network = "mainnet"; protected String BASE_PATH = System.getProperty("user.dir"); protected final String INITIAL_PATH = System.getProperty("user.dir"); - protected final File oldConfigDir = new File(INITIAL_PATH, configDirectory); + protected final File oldConfigDir = new File(INITIAL_PATH, configDirectoryName); /** @implNote modified only from {@link #setNetwork(String)} */ protected File newConfigDir = new File(oldConfigDir, network); @@ -159,6 +161,8 @@ public void resetInternal() { logDirectory = null; databaseDirectory = null; execDirectory = null; + genesisFile = null; + configFile = null; network = "mainnet"; BASE_PATH = INITIAL_PATH; @@ -242,18 +246,18 @@ public void setDatabaseDirectory(File _databaseDirectory) { public String getKeystorePath() { // todo-Ale - return new File(getBasePath(), keystoreDirectory).getAbsolutePath(); + return new File(getBasePath(), keystoreDirectoryName).getAbsolutePath(); } /** Returns the configuration directory location for the kernel execution. */ public File getExecConfigDirectory() { // TODO check different input - return new File(getExecDirectory(), configDirectory); + return new File(getExecDirectory(), configDirectoryName); } /** Returns the location where the config file is saved for kernel execution. */ public File getExecConfigFile() { - return new File(getExecConfigDirectory(), configFile); + return new File(getExecConfigDirectory(), configFileName); } /** Returns the location where the config file is saved for kernel execution. */ @@ -263,7 +267,7 @@ public String getExecConfigPath() { /** Returns the location where the genesis file is saved for kernel execution. */ public File getExecGenesisFile() { - return new File(getExecConfigDirectory(), genesisFile); + return new File(getExecConfigDirectory(), genesisFileName); } /** Returns the location where the genesis file is saved for kernel execution. */ @@ -273,21 +277,26 @@ public String getExecGenesisPath() { /** @implNote Maintains the old setup if the config file is present in the old location. */ public File getInitialConfigFile() { - // TODO-Ale: may want to consider exec path as well - // use old config location for compatibility with old kernels - File config = new File(oldConfigDir, configFile); - - // TODO: read mainnet config when ignore set - if (ignoreOldSetup || !config.exists()) { - config = new File(newConfigDir, configFile); - if (execDirectory == null) { - execDirectory = new File(INITIAL_PATH); + if (configFile == null) { + // TODO-Ale: may want to consider exec path as well + // use old config location for compatibility with old kernels + File config = new File(oldConfigDir, configFileName); + + // TODO: read mainnet config when ignore set + if (ignoreOldSetup || !config.exists()) { + if (execDirectory == null) { + execDirectory = new File(INITIAL_PATH); + } + config = getExecConfigFile(); + if (!config.exists()) { + config = new File(newConfigDir, configFileName); + } + } else { + useOldSetup = true; } - } else { - useOldSetup = true; + configFile = config; } - - return config; + return configFile; } /** @implNote Maintains the old setup if the config file is present in the old location. */ @@ -296,17 +305,22 @@ public String getInitialConfigPath() { } public File getInitialGenesisFile() { - // use old genesis location for compatibility with old kernels - File genesis = new File(oldConfigDir, genesisFile); - - if (ignoreOldSetup || !genesis.exists()) { - genesis = new File(newConfigDir, genesisFile); - if (execDirectory == null) { - execDirectory = new File(INITIAL_PATH); + if (genesisFile == null) { + // use old genesis location for compatibility with old kernels + File genesis = new File(oldConfigDir, genesisFileName); + + if (ignoreOldSetup || !genesis.exists()) { + if (execDirectory == null) { + execDirectory = new File(INITIAL_PATH); + } + genesis = getExecGenesisFile(); + if (!genesis.exists()) { + genesis = new File(newConfigDir, genesisFileName); + } } + genesisFile = genesis; } - - return genesis; + return genesisFile; } /** @implNote Maintains the old setup if the config file is present in the old location. */ From 582becc0055a4ca23b30e6b500aafa540bc302b7 Mon Sep 17 00:00:00 2001 From: Alexandra Date: Fri, 21 Sep 2018 14:43:03 -0400 Subject: [PATCH 21/31] debugging and testing creating and listing accounts --- .../src/org/aion/zero/impl/cli/Arguments.java | 67 +++---- .../src/org/aion/zero/impl/cli/Cli.java | 149 +++++++++----- .../org/aion/zero/impl/cli/ArgumentsTest.java | 30 +-- .../test/org/aion/zero/impl/cli/CliTest.java | 183 ++++++++++++++++-- modBoot/src/org/aion/Aion.java | 2 + modMcf/src/org/aion/mcf/config/Cfg.java | 7 +- 6 files changed, 306 insertions(+), 132 deletions(-) diff --git a/modAionImpl/src/org/aion/zero/impl/cli/Arguments.java b/modAionImpl/src/org/aion/zero/impl/cli/Arguments.java index 0c9baa02db..c801fc0263 100644 --- a/modAionImpl/src/org/aion/zero/impl/cli/Arguments.java +++ b/modAionImpl/src/org/aion/zero/impl/cli/Arguments.java @@ -24,10 +24,7 @@ import java.util.ArrayList; import java.util.List; -import picocli.CommandLine; import picocli.CommandLine.Command; -import picocli.CommandLine.Help.Ansi.Style; -import picocli.CommandLine.Help.ColorScheme; import picocli.CommandLine.Option; /** @@ -47,23 +44,23 @@ public class Arguments { // account management @Option( - names = {"ac", "-a create"}, + names = {"ac", "-a create", "--account create"}, description = "create a new account") private boolean createAccount = false; @Option( - names = {"al", "-a list"}, + names = {"al", "-a list", "--account list"}, description = "list all existing accounts") private boolean listAccounts = false; @Option( - names = {"ae", "-a export"}, + names = {"ae", "-a export", "--account export"}, paramLabel = "", description = "export private key of an account") private String exportAccount = null; @Option( - names = {"ai", "-a import"}, + names = {"ai", "-a import", "--account import"}, paramLabel = "", description = "import private key") private String importAccount = null; @@ -75,23 +72,23 @@ public class Arguments { paramLabel = "", description = "create config for the selected network\noptions: mainnet, conquest, mastery") - public String config = null; + private String config = null; // get information and version @Option( names = {"-i", "--info"}, description = "display information") - public boolean info = false; + private boolean info = false; @Option( names = {"-v"}, description = "display version") - public boolean version = false; + private boolean version = false; @Option( names = {"--version"}, description = "display version tag") - public boolean versionTag = false; + private boolean versionTag = false; // create ssl certificate @Option( @@ -101,13 +98,13 @@ public class Arguments { description = "create a ssl certificate for:\n - localhost (when no parameters are given), or" + "\n - the given hostname and ip") - public String[] ssl = null; + private String[] ssl = null; // offline block management @Option( names = {"pb", "--prune-blocks"}, description = "remove blocks on side chains and update block info") - public boolean rebuildBlockInfo = false; + private boolean rebuildBlockInfo = false; @Option( names = {"-r", "--revert"}, @@ -121,19 +118,19 @@ public class Arguments { names = {"-n", "--network"}, description = "execute kernel with selected network\noptions: mainnet, conquest, mastery") - public String network = null; + private String network = null; @Option( names = {"-d", "--datadir"}, description = "execute kernel with selected database directory") - public String directory = null; + private String directory = null; // offline database query and update @Option( names = {"ps", "--state"}, paramLabel = "", description = "reorganize the state storage\noptions: FULL, TOP, SPREAD") - public String pruntStateOption = null; + private String pruntStateOption = null; // print info from db @Option( @@ -141,29 +138,29 @@ public class Arguments { arity = "0..1", paramLabel = "", description = "print top blocks from database") - public String dumpBlocksCount = null; + private String dumpBlocksCount = null; @Option( names = {"--dump-state-size"}, arity = "0..1", paramLabel = "", description = "retrieves the state size (node count) for the top blocks") - public String dumpStateSizeCount = null; + private String dumpStateSizeCount = null; @Option( names = {"--dump-state"}, arity = "0..1", paramLabel = "", description = "retrieves the state for the top main chain blocks") - public String dumpStateCount = null; + private String dumpStateCount = null; @Option( names = {"--db-compact"}, description = "if using leveldb, it triggers its database compaction processes") - public boolean dbCompact; + private boolean dbCompact; /** Compacts the account options into specific commands. */ - public static String[] preprocess(String[] arguments) { + public static String[] preProcess(String[] arguments) { List list = new ArrayList<>(); int i = 0; @@ -171,7 +168,11 @@ public static String[] preprocess(String[] arguments) { if (arguments[i].equals("-a") || arguments[i].equals("--account") || arguments[i].equals("-s")) { - list.add(arguments[i] + " " + arguments[i + 1]); + if (i + 1 < arguments.length) { + list.add(arguments[i] + " " + arguments[i + 1]); + } else { + list.add(arguments[i]); + } i++; } else { list.add(arguments[i]); @@ -257,26 +258,4 @@ public String getDumpStateCount() { public boolean isDbCompact() { return dbCompact; } - - public static void main(String... args) { - Arguments params = new Arguments(); - String[] argv = {"-a create", "-a list", "-a export", "0x123"}; - - CommandLine commandLine = new CommandLine(params); - ColorScheme colorScheme = - new ColorScheme() - .commands(Style.bold, Style.underline) // combine multiple styles - .options(Style.fg_yellow) // yellow foreground color - .parameters(Style.fg_yellow) - .optionParams(Style.italic); - commandLine.usage(System.out, colorScheme); - // commandLine.usage(System.out); - - commandLine.parse(argv); - System.out.println(params.createAccount); - System.out.println(params.listAccounts); - System.out.println(params.exportAccount.toString()); - - System.out.println(commandLine.getUsageMessage(colorScheme)); - } } diff --git a/modAionImpl/src/org/aion/zero/impl/cli/Cli.java b/modAionImpl/src/org/aion/zero/impl/cli/Cli.java index eadc834343..01bd1b7a8c 100644 --- a/modAionImpl/src/org/aion/zero/impl/cli/Cli.java +++ b/modAionImpl/src/org/aion/zero/impl/cli/Cli.java @@ -20,7 +20,6 @@ * Contributors: * Aion foundation. */ - package org.aion.zero.impl.cli; import static org.aion.zero.impl.cli.Cli.ReturnType.ERROR; @@ -48,6 +47,7 @@ import org.aion.zero.impl.Version; import org.aion.zero.impl.config.Network; import org.aion.zero.impl.db.RecoveryUtils; +import org.apache.commons.lang3.ArrayUtils; import picocli.CommandLine; /** @@ -60,17 +60,17 @@ public class Cli { // TODO-Ale: consider using initial path from cfg private final String BASE_PATH = System.getProperty("user.dir"); - File keystoreDir = + private final File keystoreDir = new File(System.getProperty("user.dir") + File.separator + CfgSsl.SSL_KEYSTORE_DIR); - private Arguments options = new Arguments(); - private CommandLine parser = new CommandLine(options); + private final Arguments options = new Arguments(); + private final CommandLine parser = new CommandLine(options); public enum ReturnType { RUN(2), EXIT(0), ERROR(1); - private int value; + private final int value; ReturnType(int _value) { this.value = _value; @@ -81,11 +81,12 @@ public int getValue() { } } + @SuppressWarnings("ResultOfMethodCallIgnored") public ReturnType call(final String[] args, Cfg cfg) { try { - // the preprocess method handles arguments that are separated by space + // the pre-process method handles arguments that are separated by space // parsing populates the options object - parser.parse(Arguments.preprocess(args)); + parser.parse(Arguments.preProcess(args)); } catch (Exception e) { System.out.println("Unable to parse the input arguments due to: "); if (e.getMessage() != null) { @@ -159,22 +160,38 @@ public ReturnType call(final String[] args, Cfg cfg) { // ensure path exists File dir = cfg.getExecConfigDirectory(); if (!dir.exists()) { - dir.mkdirs(); + if (!dir.mkdirs()) { + System.out.println( + "ERROR: Unable to create directory: " + + getRelativePath(dir.getAbsolutePath())); + return ERROR; + } + } + try { + configFile.createNewFile(); + } catch (IOException e) { + System.out.println( + "ERROR: Unable to create file: " + + getRelativePath(configFile.getAbsolutePath())); + return ERROR; } - configFile.createNewFile(); } // save to disk cfg.toXML(null, configFile); - System.out.println("\nNew config generated at " + configFile + "."); + System.out.println( + "\nNew config generated at: " + + getRelativePath(configFile.getAbsolutePath())); return ReturnType.EXIT; } // 5. options that can be influenced by the -d and -n arguments if (options.isInfo()) { - System.out.println("Read config file at " + configFile.getAbsolutePath() + "."); + System.out.println( + "Reading config file from: " + + getRelativePath(configFile.getAbsolutePath())); printInfo(cfg); return ReturnType.EXIT; } @@ -187,6 +204,9 @@ public ReturnType call(final String[] args, Cfg cfg) { cfg.toXML(new String[] {"--id=" + cfg.getId()}, cfg.getExecConfigFile()); } + // set correct keystore directory + Keystore.setKeystorePath(cfg.getKeystorePath()); + if (options.isCreateAccount()) { if (!createAccount()) { return ERROR; @@ -300,13 +320,13 @@ public ReturnType call(final String[] args, Cfg cfg) { block_count = Long.parseLong(parameter); } catch (NumberFormatException e) { System.out.println( - "The given argument <" + "The given argument «" + parameter - + "> cannot be converted to a number."); + + "» cannot be converted to a number."); return ERROR; } if (block_count < 1) { - System.out.println("The given argument <" + parameter + "> is not valid."); + System.out.println("The given argument «" + parameter + "» is not valid."); block_count = 2L; } @@ -329,9 +349,9 @@ public ReturnType call(final String[] args, Cfg cfg) { level = Long.parseLong(parameter); } catch (NumberFormatException e) { System.out.println( - "The given argument <" + "The given argument «" + parameter - + "> cannot be converted to a number."); + + "» cannot be converted to a number."); return ERROR; } if (level == -1L) { @@ -358,13 +378,13 @@ public ReturnType call(final String[] args, Cfg cfg) { count = Long.parseLong(parameter); } catch (NumberFormatException e) { System.out.println( - "The given argument <" + "The given argument «" + parameter - + "> cannot be converted to a number."); + + "» cannot be converted to a number."); return ERROR; } if (count < 1) { - System.out.println("The given argument <" + parameter + "> is not valid."); + System.out.println("The given argument «" + parameter + "» is not valid."); count = 10L; } @@ -389,6 +409,16 @@ public ReturnType call(final String[] args, Cfg cfg) { } } + /** + * Utility method for truncating absolute paths wrt the {@link #BASE_PATH}. + * + * @return the path without the {@link #BASE_PATH} prefix. + */ + private String getRelativePath(String path) { + // if absolute paths are given with different prefix the replacement won't work + return path.replaceFirst(BASE_PATH, "."); + } + /** Print the CLI help info. */ private void printHelp() { String usage = parser.getUsageMessage(); @@ -399,7 +429,7 @@ private void printHelp() { // making the use of a regular expression necessary here usage = usage.replaceFirst(" \\[[^ ]* .*]", "]"); - System.out.println(usage.toString()); + System.out.println(usage); } private void printInfo(Cfg cfg) { @@ -442,10 +472,12 @@ private boolean setDirectory(String directory, Cfg cfg) { } if (!file.exists()) { - file.mkdirs(); + if (!file.mkdirs()) { + return false; + } } - if (file != null && file.isDirectory() && file.canWrite()) { + if (file.isDirectory() && file.canWrite()) { cfg.setExecDirectory(file); return true; } else { @@ -476,6 +508,7 @@ private void printInvalidNetwork() { * * @param cfg the configuration for the runtime kernel environment */ + @SuppressWarnings("ResultOfMethodCallIgnored") private void makeDirs(Cfg cfg) { File file = cfg.getExecDirectory(); if (!file.exists()) { @@ -514,16 +547,21 @@ private void makeDirs(Cfg cfg) { file.mkdirs(); } - // TODO-Ale: create target keystore directory + // create target keystore directory + file = cfg.getKeystoreDirectory(); + if (!file.exists()) { + file.mkdirs(); + } } /** * Creates a new account. * - * @return true only if the new account was successfully created, otherwise false. + * @return {@code true} only if the new account was successfully created, {@code false} + * otherwise. */ private boolean createAccount() { - String password = null, password2 = null; + String password, password2; try (BufferedReader reader = new BufferedReader(new InputStreamReader(System.in))) { password = readPassword("Please enter a password: ", reader); password2 = readPassword("Please re-enter your password: ", reader); @@ -540,6 +578,8 @@ private boolean createAccount() { String address = Keystore.create(password); if (!address.equals("0x")) { System.out.println("A new account has been created: " + address); + System.out.println( + "The account was stored in: " + getRelativePath(Keystore.getKeystorePath())); return true; } else { System.out.println("Failed to create an account!"); @@ -547,17 +587,22 @@ private boolean createAccount() { } } - /** - * List all existing account. - * - * @return boolean - */ + /** List all existing accounts. */ + @SuppressWarnings("SameReturnValue") private boolean listAccounts() { String[] accounts = Keystore.list(); - for (String account : accounts) { - System.out.println(account); - } + if (ArrayUtils.isNotEmpty(accounts)) { + System.out.println( + "All accounts from: " + getRelativePath(Keystore.getKeystorePath()) + "\n"); + + for (String account : accounts) { + System.out.println("\t" + account); + } + } else { + System.out.println( + "No accounts found at: " + getRelativePath(Keystore.getKeystorePath())); + } return true; } @@ -565,15 +610,18 @@ private boolean listAccounts() { * Dumps the private of the given account. * * @param address address of the account - * @return boolean + * @return {@code true} if the operation was successful, {@code false} otherwise. */ private boolean exportPrivateKey(String address) { + System.out.println( + "Searching for account in: " + getRelativePath(Keystore.getKeystorePath())); + if (!Keystore.exist(address)) { System.out.println("The account does not exist!"); return false; } - String password = null; + String password; try (BufferedReader reader = new BufferedReader(new InputStreamReader(System.in))) { password = readPassword("Please enter your password: ", reader); } catch (IOException e) { @@ -595,7 +643,7 @@ private boolean exportPrivateKey(String address) { * Imports a private key. * * @param privateKey private key in hex string - * @return boolean + * @return {@code true} if the operation was successful, {@code false} otherwise. */ private boolean importPrivateKey(String privateKey) { // TODO: the Hex.decode() method catches all exceptions which may cause @@ -614,7 +662,7 @@ private boolean importPrivateKey(String privateKey) { return false; } - String password = null, password2 = null; + String password, password2; try (BufferedReader reader = new BufferedReader(new InputStreamReader(System.in))) { password = readPassword("Please enter a password: ", reader); password2 = readPassword("Please re-enter your password: ", reader); @@ -630,17 +678,23 @@ private boolean importPrivateKey(String privateKey) { String address = Keystore.create(password, key); if (!address.equals("0x")) { - System.out.println("The private key was imported, the address is: " + address); + System.out.println( + "The private key was imported to: " + + getRelativePath(Keystore.getKeystorePath()) + + "\nThe address is: " + + address); return true; } else { - System.out.println("Failed to import the private key. Already exists?"); + System.out.println( + "Failed to import the private key. It may already exist in: " + + getRelativePath(Keystore.getKeystorePath())); return false; } } /** * Returns a password after prompting the user to enter it. This method attempts first to read - * user input from a console evironment and if one is not available it instead attempts to read + * user input from a console environment and if one is not available it instead attempts to read * from reader. * * @param prompt The read-password prompt to display to the user. @@ -690,23 +744,13 @@ private RecoveryUtils.Status revertTo(String blockNumber) { block = Long.parseLong(blockNumber); } catch (NumberFormatException e) { System.out.println( - "The given argument <" + blockNumber + "> cannot be converted to a number."); + "The given argument «" + blockNumber + "» cannot be converted to a number."); return RecoveryUtils.Status.ILLEGAL_ARGUMENT; } return RecoveryUtils.revertTo(block); } - /** - * Checks for illegal inputs (for datadir && network names) - * - * @param value - * @return - */ - public static boolean isValid(String value) { - return !value.isEmpty() && !value.matches(".*[-=+,.?;:'!@#$%^&*].*"); - } - private void createKeystoreDirIfMissing() { if (!keystoreDir.isDirectory()) { if (!keystoreDir.mkdir()) { @@ -746,7 +790,7 @@ private String getCertPass(Console console) { "Enter certificate password (at least " + minPassLen + " characters):\n")); - if ((certPass == null) || (certPass.isEmpty())) { + if (certPass.isEmpty()) { System.out.println("Error: no certificate password entered."); System.exit(1); } else if (certPass.length() < minPassLen) { @@ -773,6 +817,7 @@ public static boolean copyRecursively(File src, File target) { } } + @SuppressWarnings("ResultOfMethodCallIgnored") private static boolean copyDirectoryContents(File src, File target) { Preconditions.checkArgument(src.isDirectory(), "Source dir is not a directory: %s", src); diff --git a/modAionImpl/test/org/aion/zero/impl/cli/ArgumentsTest.java b/modAionImpl/test/org/aion/zero/impl/cli/ArgumentsTest.java index 787d9e01f2..d8dbb7c8d8 100644 --- a/modAionImpl/test/org/aion/zero/impl/cli/ArgumentsTest.java +++ b/modAionImpl/test/org/aion/zero/impl/cli/ArgumentsTest.java @@ -41,21 +41,21 @@ public void testPreprocess_wCreate() { "-a create", "-n", "mainnet", "-d", "abc", }; - String[] actual = Arguments.preprocess(input); + String[] actual = Arguments.preProcess(input); assertThat(actual).isEqualTo(expected); // middle parameter input = new String[] {"-d", "abc", "-a", "create", "-n", "mainnet"}; expected = new String[] {"-d", "abc", "-a create", "-n", "mainnet"}; - actual = Arguments.preprocess(input); + actual = Arguments.preProcess(input); assertThat(actual).isEqualTo(expected); // last parameter input = new String[] {"-n", "mainnet", "-d", "abc", "-a", "create"}; expected = new String[] {"-n", "mainnet", "-d", "abc", "-a create"}; - actual = Arguments.preprocess(input); + actual = Arguments.preProcess(input); assertThat(actual).isEqualTo(expected); } @@ -71,21 +71,21 @@ public void testPreprocess_wImport() { "-a import", "0x123", "-n", "mainnet", "-d", "abc", }; - String[] actual = Arguments.preprocess(input); + String[] actual = Arguments.preProcess(input); assertThat(actual).isEqualTo(expected); // middle parameter input = new String[] {"-d", "abc", "-a", "import", "0x123", "-n", "mainnet"}; expected = new String[] {"-d", "abc", "-a import", "0x123", "-n", "mainnet"}; - actual = Arguments.preprocess(input); + actual = Arguments.preProcess(input); assertThat(actual).isEqualTo(expected); // last parameter input = new String[] {"-n", "mainnet", "-d", "abc", "-a", "import", "0x123"}; expected = new String[] {"-n", "mainnet", "-d", "abc", "-a import", "0x123"}; - actual = Arguments.preprocess(input); + actual = Arguments.preProcess(input); assertThat(actual).isEqualTo(expected); } @@ -101,21 +101,21 @@ public void testPreprocess_wExport() { "-a export", "0x123", "-n", "mainnet", "-d", "abc", }; - String[] actual = Arguments.preprocess(input); + String[] actual = Arguments.preProcess(input); assertThat(actual).isEqualTo(expected); // middle parameter input = new String[] {"-d", "abc", "-a", "export", "0x123", "-n", "mainnet"}; expected = new String[] {"-d", "abc", "-a export", "0x123", "-n", "mainnet"}; - actual = Arguments.preprocess(input); + actual = Arguments.preProcess(input); assertThat(actual).isEqualTo(expected); // last parameter input = new String[] {"-n", "mainnet", "-d", "abc", "-a", "export", "0x123"}; expected = new String[] {"-n", "mainnet", "-d", "abc", "-a export", "0x123"}; - actual = Arguments.preprocess(input); + actual = Arguments.preProcess(input); assertThat(actual).isEqualTo(expected); } @@ -131,21 +131,21 @@ public void testPreprocess_wList() { "-a list", "-n", "mainnet", "-d", "abc", }; - String[] actual = Arguments.preprocess(input); + String[] actual = Arguments.preProcess(input); assertThat(actual).isEqualTo(expected); // middle parameter input = new String[] {"-d", "abc", "-a", "list", "-n", "mainnet"}; expected = new String[] {"-d", "abc", "-a list", "-n", "mainnet"}; - actual = Arguments.preprocess(input); + actual = Arguments.preProcess(input); assertThat(actual).isEqualTo(expected); // last parameter input = new String[] {"-n", "mainnet", "-d", "abc", "-a", "list"}; expected = new String[] {"-n", "mainnet", "-d", "abc", "-a list"}; - actual = Arguments.preprocess(input); + actual = Arguments.preProcess(input); assertThat(actual).isEqualTo(expected); } @@ -161,21 +161,21 @@ public void testPreprocess_wSsl() { "-s create", "-n", "mainnet", "-d", "abc", }; - String[] actual = Arguments.preprocess(input); + String[] actual = Arguments.preProcess(input); assertThat(actual).isEqualTo(expected); // middle parameter input = new String[] {"-d", "abc", "-s", "create", "-n", "mainnet"}; expected = new String[] {"-d", "abc", "-s create", "-n", "mainnet"}; - actual = Arguments.preprocess(input); + actual = Arguments.preProcess(input); assertThat(actual).isEqualTo(expected); // last parameter input = new String[] {"-n", "mainnet", "-d", "abc", "-s", "create"}; expected = new String[] {"-n", "mainnet", "-d", "abc", "-s create"}; - actual = Arguments.preprocess(input); + actual = Arguments.preProcess(input); assertThat(actual).isEqualTo(expected); } } diff --git a/modAionImpl/test/org/aion/zero/impl/cli/CliTest.java b/modAionImpl/test/org/aion/zero/impl/cli/CliTest.java index 45316dfbdc..4a2b73a6cf 100644 --- a/modAionImpl/test/org/aion/zero/impl/cli/CliTest.java +++ b/modAionImpl/test/org/aion/zero/impl/cli/CliTest.java @@ -27,6 +27,8 @@ import static org.aion.zero.impl.cli.Cli.ReturnType.EXIT; import static org.aion.zero.impl.cli.Cli.ReturnType.RUN; import static org.junit.Assert.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doReturn; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; @@ -53,6 +55,7 @@ import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.*; /** CliTest for new version with use of different networks. */ @RunWith(JUnitParamsRunner.class) @@ -70,24 +73,24 @@ public class CliTest { private static final File path = new File(BASE_PATH, dataDirectory); private static final File alternativePath = new File(BASE_PATH, alternativeDirectory); - private static File config = + private static final File config = BASE_PATH.contains(module) ? new File(BASE_PATH, initialConfigFile) : new File(BASE_PATH, module + "/" + initialConfigFile); - private static File oldConfig = new File(BASE_PATH, "config/config.xml"); - private static File mainnetConfig = new File(BASE_PATH, "config/mainnet/config.xml"); - private static File testnetConfig = new File(BASE_PATH, "config/mastery/config.xml"); + private static final File oldConfig = new File(BASE_PATH, "config/config.xml"); + private static final File mainnetConfig = new File(BASE_PATH, "config/mainnet/config.xml"); + private static final File testnetConfig = new File(BASE_PATH, "config/mastery/config.xml"); - private static File genesis = + private static final File genesis = BASE_PATH.contains(module) ? new File(BASE_PATH, initialGenesisFile) : new File(BASE_PATH, module + "/" + initialGenesisFile); - private static File oldGenesis = new File(BASE_PATH, "config/genesis.json"); - private static File mainnetGenesis = new File(BASE_PATH, "config/mainnet/genesis.json"); - private static File testnetGenesis = new File(BASE_PATH, "config/mastery/genesis.json"); + private static final File oldGenesis = new File(BASE_PATH, "config/genesis.json"); + private static final File mainnetGenesis = new File(BASE_PATH, "config/mainnet/genesis.json"); + private static final File testnetGenesis = new File(BASE_PATH, "config/mastery/genesis.json"); /** @implNote set this to true to enable printing */ - private static boolean verbose = true; + private static final boolean verbose = true; @Before public void setup() { @@ -656,20 +659,162 @@ public void testInfo_execLocation(String option) { assertThat(cli.call(new String[] {option, "-d", dataDirectory}, cfg)).isEqualTo(EXIT); } - /** Tests the -a create arguments work. */ + private List parametersWithAccount(String[] options) { + List parameters = new ArrayList<>(); + + // only info + for (String op : options) { + // without parameter + parameters.add(new Object[] {new String[] {op}, EXIT}); + // invalid parameter + parameters.add(new Object[] {new String[] {op, "value"}, ERROR}); + } + + // with network + for (String op : options) { + // mastery as parameter + parameters.add(new Object[] {new String[] {op, "-n", "mastery"}, EXIT}); + parameters.add(new Object[] {new String[] {"-n", "mastery", op}, EXIT}); + // invalid parameter + parameters.add(new Object[] {new String[] {op, "value", "-n", "mastery"}, ERROR}); + } + + // with directory + for (String op : options) { + // with relative path + parameters.add(new Object[] {new String[] {op, "-d", dataDirectory}, EXIT}); + parameters.add(new Object[] {new String[] {"-d", dataDirectory, op}, EXIT}); + // + invalid parameter + parameters.add(new Object[] {new String[] {op, "value", "-d", dataDirectory}, ERROR}); + // with absolute path + parameters.add(new Object[] {new String[] {op, "-d", path.getAbsolutePath()}, EXIT}); + parameters.add(new Object[] {new String[] {"-d", path.getAbsolutePath(), op}, EXIT}); + // + invalid parameter + parameters.add( + new Object[] {new String[] {op, "value", "-d", path.getAbsolutePath()}, ERROR}); + } + + // with network and directory + for (String op : options) { + // with relative path + parameters.add( + new Object[] {new String[] {op, "-d", dataDirectory, "-n", "mastery"}, EXIT}); + parameters.add( + new Object[] {new String[] {"-n", "mastery", op, "-d", dataDirectory}, EXIT}); + parameters.add( + new Object[] {new String[] {"-n", "mastery", "-d", dataDirectory, op}, EXIT}); + // with absolute path + parameters.add( + new Object[] { + new String[] {op, "-n", "mastery", "-d", path.getAbsolutePath()}, EXIT + }); + parameters.add( + new Object[] { + new String[] {"-d", path.getAbsolutePath(), op, "-n", "mastery"}, EXIT + }); + parameters.add( + new Object[] { + new String[] {"-d", path.getAbsolutePath(), "-n", "mastery", op}, EXIT + }); + } + + return parameters; + } + + /** Parameters for testing {@link #testCreateAccount(String[], ReturnType)}. */ + @SuppressWarnings("unused") + private Object parametersWithCreateAccount() { + return parametersWithAccount(new String[] {"-a create", "ac", "--account create"}) + .toArray(); + } + + /** Ensures that the { -a create, ac } arguments work. */ @Test - @Ignore - public void testCreateAccount() { - String args[] = {"-a", "create"}; - assertEquals(EXIT, cli.call(args, CfgAion.inst())); + @Parameters(method = "parametersWithCreateAccount") + public void testCreateAccount(String[] input, ReturnType expectedReturn) { + Cli mockCli = Mockito.spy(new Cli()); + doReturn("password").when(mockCli).readPassword(any(), any()); + + // number of accounts before create call + int count = Keystore.list().length; + assertThat(mockCli.call(input, CfgAion.inst())).isEqualTo(expectedReturn); + if (expectedReturn == EXIT) { + // ensure number of accounts was incremented + assertThat(Keystore.list().length).isEqualTo(count + 1); + } else { + // ensure number of accounts is unchanged + assertThat(Keystore.list().length).isEqualTo(count); + } } - /** Tests the -a list arguments work. */ @Test - @Ignore - public void testListAccounts() { - String args[] = {"-a", "list"}; - assertEquals(EXIT, cli.call(args, CfgAion.inst())); + public void testCreateAccount_withSplitInput() { + Cli mockCli = Mockito.spy(new Cli()); + doReturn("password").when(mockCli).readPassword(any(), any()); + + // number of accounts before create call + int count = Keystore.list().length; + + assertThat(mockCli.call(new String[] {"-a"}, CfgAion.inst())).isEqualTo(ERROR); + // ensure number of accounts is unchanged + assertThat(Keystore.list().length).isEqualTo(count); + + assertThat(mockCli.call(new String[] {"-a", "cre"}, CfgAion.inst())).isEqualTo(ERROR); + // ensure number of accounts is unchanged + assertThat(Keystore.list().length).isEqualTo(count); + + assertThat(mockCli.call(new String[] {"--acc", "create"}, CfgAion.inst())).isEqualTo(ERROR); + // ensure number of accounts is unchanged + assertThat(Keystore.list().length).isEqualTo(count); + + assertThat(mockCli.call(new String[] {"-a", "create"}, CfgAion.inst())).isEqualTo(EXIT); + // ensure number of accounts was incremented + assertThat(Keystore.list().length).isEqualTo(count + 1); + + assertThat(mockCli.call(new String[] {"--account", "create"}, CfgAion.inst())) + .isEqualTo(EXIT); + // ensure number of accounts was incremented + assertThat(Keystore.list().length).isEqualTo(count + 2); + } + + // TODO: add test for create account with old config location + + /** Parameters for testing {@link #testListAccounts(String[], ReturnType)}. */ + @SuppressWarnings("unused") + private Object parametersWithListAccount() { + return parametersWithAccount(new String[] {"-a list", "al", "--account list"}).toArray(); + } + + /** Ensures that the { -a list, al } arguments work. */ + @Test + @Parameters(method = "parametersWithListAccount") + public void testListAccounts(String[] input, ReturnType expectedReturn) { + assertThat(cli.call(input, CfgAion.inst())).isEqualTo(expectedReturn); + } + + @Test + public void testListAccounts_withSplitInput() { + Cli mockCli = Mockito.spy(new Cli()); + doReturn("password").when(mockCli).readPassword(any(), any()); + + // used to ensure number of accounts is unchanged + int count = Keystore.list().length; + + assertThat(mockCli.call(new String[] {"-a"}, CfgAion.inst())).isEqualTo(ERROR); + assertThat(Keystore.list().length).isEqualTo(count); + + assertThat(mockCli.call(new String[] {"-a", "lis"}, CfgAion.inst())).isEqualTo(ERROR); + assertThat(Keystore.list().length).isEqualTo(count); + + assertThat(mockCli.call(new String[] {"--acc", "list"}, CfgAion.inst())).isEqualTo(ERROR); + assertThat(Keystore.list().length).isEqualTo(count); + + assertThat(mockCli.call(new String[] {"-a", "list"}, CfgAion.inst())).isEqualTo(EXIT); + assertThat(Keystore.list().length).isEqualTo(count); + + assertThat(mockCli.call(new String[] {"--account", "list"}, CfgAion.inst())) + .isEqualTo(EXIT); + assertThat(Keystore.list().length).isEqualTo(count); } /** Tests the -a export arguments work on a valid account. */ diff --git a/modBoot/src/org/aion/Aion.java b/modBoot/src/org/aion/Aion.java index ddaed5f296..11861fb0d2 100644 --- a/modBoot/src/org/aion/Aion.java +++ b/modBoot/src/org/aion/Aion.java @@ -121,8 +121,10 @@ public static void main(String args[]) { // Log/Database path if (!cfg.getLog().getLogFile()) { System.out.println("Logger disabled; to enable please check log settings in config.xml"); + filePath[0] = "« disabled »"; } else if (!cfg.getLog().isValidPath() && cfg.getLog().getLogFile()) { System.out.println("File path is invalid; please check log setting in config.xml"); + // TODO set default location return; } else if (cfg.getLog().isValidPath() && cfg.getLog().getLogFile()) { filePath[0] = cfg.getBasePath() + "/" + cfg.getLog().getLogPath(); diff --git a/modMcf/src/org/aion/mcf/config/Cfg.java b/modMcf/src/org/aion/mcf/config/Cfg.java index 34e4b8faa7..1cecc605d2 100644 --- a/modMcf/src/org/aion/mcf/config/Cfg.java +++ b/modMcf/src/org/aion/mcf/config/Cfg.java @@ -245,8 +245,11 @@ public void setDatabaseDirectory(File _databaseDirectory) { } public String getKeystorePath() { - // todo-Ale - return new File(getBasePath(), keystoreDirectoryName).getAbsolutePath(); + return getKeystoreDirectory().getAbsolutePath(); + } + + public File getKeystoreDirectory() { + return new File(getBasePath(), keystoreDirectoryName); } /** Returns the configuration directory location for the kernel execution. */ From 9d67ac38b008fc39af3c35d9694226c0f6264d0a Mon Sep 17 00:00:00 2001 From: Alexandra Date: Fri, 21 Sep 2018 16:33:24 -0400 Subject: [PATCH 22/31] unit tests for exporting private keys --- .../test/org/aion/zero/impl/cli/CliTest.java | 156 +++++++++++++++--- 1 file changed, 132 insertions(+), 24 deletions(-) diff --git a/modAionImpl/test/org/aion/zero/impl/cli/CliTest.java b/modAionImpl/test/org/aion/zero/impl/cli/CliTest.java index 4a2b73a6cf..886e0c6e92 100644 --- a/modAionImpl/test/org/aion/zero/impl/cli/CliTest.java +++ b/modAionImpl/test/org/aion/zero/impl/cli/CliTest.java @@ -55,7 +55,7 @@ import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.*; +import org.mockito.Mockito; /** CliTest for new version with use of different networks. */ @RunWith(JUnitParamsRunner.class) @@ -794,51 +794,159 @@ public void testListAccounts(String[] input, ReturnType expectedReturn) { @Test public void testListAccounts_withSplitInput() { - Cli mockCli = Mockito.spy(new Cli()); - doReturn("password").when(mockCli).readPassword(any(), any()); - // used to ensure number of accounts is unchanged int count = Keystore.list().length; - assertThat(mockCli.call(new String[] {"-a"}, CfgAion.inst())).isEqualTo(ERROR); + assertThat(cli.call(new String[] {"-a"}, CfgAion.inst())).isEqualTo(ERROR); assertThat(Keystore.list().length).isEqualTo(count); - assertThat(mockCli.call(new String[] {"-a", "lis"}, CfgAion.inst())).isEqualTo(ERROR); + assertThat(cli.call(new String[] {"-a", "lis"}, CfgAion.inst())).isEqualTo(ERROR); assertThat(Keystore.list().length).isEqualTo(count); - assertThat(mockCli.call(new String[] {"--acc", "list"}, CfgAion.inst())).isEqualTo(ERROR); + assertThat(cli.call(new String[] {"--acc", "list"}, CfgAion.inst())).isEqualTo(ERROR); assertThat(Keystore.list().length).isEqualTo(count); - assertThat(mockCli.call(new String[] {"-a", "list"}, CfgAion.inst())).isEqualTo(EXIT); + assertThat(cli.call(new String[] {"-a", "list"}, CfgAion.inst())).isEqualTo(EXIT); assertThat(Keystore.list().length).isEqualTo(count); - assertThat(mockCli.call(new String[] {"--account", "list"}, CfgAion.inst())) - .isEqualTo(EXIT); + assertThat(cli.call(new String[] {"--account", "list"}, CfgAion.inst())).isEqualTo(EXIT); assertThat(Keystore.list().length).isEqualTo(count); } - /** Tests the -a export arguments work on a valid account. */ + /** Ensures that the { -a export, ae, --account export } arguments work. */ @Test - @Ignore - public void testExportPrivateKey() { - String account = Keystore.create("password"); + @Parameters({"-a export", "ae", "--account export"}) + public void testExportPrivateKey(String option) { + Cli mockCli = Mockito.spy(new Cli()); + doReturn("password").when(mockCli).readPassword(any(), any()); - String[] args = {"-a", "export", account}; - assertEquals(EXIT, cli.call(args, CfgAion.inst())); + // create account + assertThat(mockCli.call(new String[] {"ac"}, CfgAion.inst())).isEqualTo(EXIT); + + String account = Keystore.list()[0]; + assertEquals(EXIT, mockCli.call(new String[] {option, account}, CfgAion.inst())); } /** - * Tests the -a export arguments fail when the suupplied account is a proper substring of a - * valid account. + * Ensures that the { -a export, ae, --account export } arguments work in + * combination with the data directory option. */ @Test - @Ignore - public void testExportSubstringOfAccount() { - String account = Keystore.create("password"); - String substrAcc = account.substring(1); + @Parameters({"-a export", "ae", "--account export"}) + public void testExportPrivateKey_withDataDir(String option) { + Cli mockCli = Mockito.spy(new Cli()); + doReturn("password").when(mockCli).readPassword(any(), any()); - String[] args = {"-a", "export", substrAcc}; - assertEquals(ERROR, cli.call(args, CfgAion.inst())); + // create account + assertThat(mockCli.call(new String[] {"ac", "-d", dataDirectory}, CfgAion.inst())) + .isEqualTo(EXIT); + + String account = Keystore.list()[0]; + assertEquals( + EXIT, + mockCli.call(new String[] {option, account, "-d", dataDirectory}, CfgAion.inst())); + assertEquals( + EXIT, + mockCli.call(new String[] {"-d", dataDirectory, option, account}, CfgAion.inst())); + } + + /** + * Ensures that the { -a export, ae, --account export } arguments work in + * combination with the network option. + */ + @Test + @Parameters({"-a export", "ae", "--account export"}) + public void testExportPrivateKey_withNetwork(String option) { + Cli mockCli = Mockito.spy(new Cli()); + doReturn("password").when(mockCli).readPassword(any(), any()); + + // create account + assertThat(mockCli.call(new String[] {"ac", "-n", "mastery"}, CfgAion.inst())) + .isEqualTo(EXIT); + + String account = Keystore.list()[0]; + assertEquals( + EXIT, + mockCli.call(new String[] {option, account, "-n", "mastery"}, CfgAion.inst())); + assertEquals( + EXIT, + mockCli.call(new String[] {"-n", "mastery", option, account}, CfgAion.inst())); + } + + /** + * Ensures that the { -a export, ae, --account export } arguments work in + * combination with the data directory and network options. + */ + @Test + @Parameters({"-a export", "ae", "--account export"}) + public void testExportPrivateKey_withDataDirAndNetwork(String option) { + Cli mockCli = Mockito.spy(new Cli()); + doReturn("password").when(mockCli).readPassword(any(), any()); + + // create account + assertThat( + mockCli.call( + new String[] {"ac", "-d", dataDirectory, "-n", "mastery"}, + CfgAion.inst())) + .isEqualTo(EXIT); + + String account = Keystore.list()[0]; + assertEquals( + EXIT, + mockCli.call( + new String[] {"-n", "mastery", option, account, "-d", dataDirectory}, + CfgAion.inst())); + assertEquals( + EXIT, + mockCli.call( + new String[] {"-n", "mastery", "-d", dataDirectory, option, account}, + CfgAion.inst())); + assertEquals( + EXIT, + mockCli.call( + new String[] {option, account, "-d", dataDirectory, "-n", "mastery"}, + CfgAion.inst())); + } + + /** + * Ensured that the { -a export, ae, --account export } arguments fail when + * the given an incorrect account value (proper substring of a valid account). + */ + @Test + @Parameters({"-a export", "ae", "--account export"}) + public void testExportPrivateKey_wSubstringOfAccount(String prefix) { + Cli mockCli = Mockito.spy(new Cli()); + doReturn("password").when(mockCli).readPassword(any(), any()); + + // create account + assertThat(mockCli.call(new String[] {"ac"}, CfgAion.inst())).isEqualTo(EXIT); + + String subStrAcc = Keystore.list()[0].substring(1); + + assertEquals(ERROR, cli.call(new String[] {prefix, subStrAcc}, CfgAion.inst())); + } + + @Test + public void testExportPrivateKey_withSplitInput() { + Cli mockCli = Mockito.spy(new Cli()); + doReturn("password").when(mockCli).readPassword(any(), any()); + // create account + assertThat(mockCli.call(new String[] {"ac"}, CfgAion.inst())).isEqualTo(EXIT); + String account = Keystore.list()[0]; + + assertThat(mockCli.call(new String[] {"-a", account}, CfgAion.inst())).isEqualTo(ERROR); + + assertThat(mockCli.call(new String[] {"-a", "exp", account}, CfgAion.inst())) + .isEqualTo(ERROR); + + assertThat(mockCli.call(new String[] {"--acc", "export", account}, CfgAion.inst())) + .isEqualTo(ERROR); + + assertThat(mockCli.call(new String[] {"-a", "export", account}, CfgAion.inst())) + .isEqualTo(EXIT); + + assertThat(mockCli.call(new String[] {"--account", "export", account}, CfgAion.inst())) + .isEqualTo(EXIT); } /** Tests the -a import arguments work on a fail import key. */ From ed07d0179965f06f268ac72c3a196f02c19a8833 Mon Sep 17 00:00:00 2001 From: Alexandra Date: Fri, 21 Sep 2018 17:30:44 -0400 Subject: [PATCH 23/31] unit tests for importing private keys --- .../test/org/aion/zero/impl/cli/CliTest.java | 274 +++++++++++------- 1 file changed, 167 insertions(+), 107 deletions(-) diff --git a/modAionImpl/test/org/aion/zero/impl/cli/CliTest.java b/modAionImpl/test/org/aion/zero/impl/cli/CliTest.java index 886e0c6e92..0abc7af297 100644 --- a/modAionImpl/test/org/aion/zero/impl/cli/CliTest.java +++ b/modAionImpl/test/org/aion/zero/impl/cli/CliTest.java @@ -52,7 +52,6 @@ import org.aion.zero.impl.config.CfgAion; import org.junit.After; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mockito; @@ -61,8 +60,9 @@ @RunWith(JUnitParamsRunner.class) public class CliTest { - private Cli cli = new Cli(); + private final Cli cli = new Cli(); private CfgAion cfg = CfgAion.inst(); + private final Cli mockCli = Mockito.spy(new Cli()); private static final String BASE_PATH = System.getProperty("user.dir"); private static final String module = "modAionImpl"; @@ -118,6 +118,8 @@ public void setup() { } cfg.resetInternal(); + + doReturn("password").when(mockCli).readPassword(any(), any()); } @After @@ -449,6 +451,7 @@ public void testConfig(String[] input, File expectedFile, String expectedPath) { if (expectedFile.exists()) { assertThat(cfg.fromXML(expectedFile)).isTrue(); } + cfg.resetInternal(); assertThat(cli.call(input, cfg)).isEqualTo(EXIT); assertThat(cfg.getBasePath()).isEqualTo(expectedPath); @@ -457,8 +460,6 @@ public void testConfig(String[] input, File expectedFile, String expectedPath) { assertThat(cfg.getExecGenesisPath()) .isEqualTo(new File(expectedPath, "config/genesis.json").getAbsolutePath()); - // cfg.resetInternal(); - assertThat(expectedFile.exists()).isTrue(); assertThat(cfg.fromXML(expectedFile)).isFalse(); @@ -732,12 +733,9 @@ private Object parametersWithCreateAccount() { @Test @Parameters(method = "parametersWithCreateAccount") public void testCreateAccount(String[] input, ReturnType expectedReturn) { - Cli mockCli = Mockito.spy(new Cli()); - doReturn("password").when(mockCli).readPassword(any(), any()); - // number of accounts before create call int count = Keystore.list().length; - assertThat(mockCli.call(input, CfgAion.inst())).isEqualTo(expectedReturn); + assertThat(mockCli.call(input, cfg)).isEqualTo(expectedReturn); if (expectedReturn == EXIT) { // ensure number of accounts was incremented assertThat(Keystore.list().length).isEqualTo(count + 1); @@ -749,30 +747,26 @@ public void testCreateAccount(String[] input, ReturnType expectedReturn) { @Test public void testCreateAccount_withSplitInput() { - Cli mockCli = Mockito.spy(new Cli()); - doReturn("password").when(mockCli).readPassword(any(), any()); - // number of accounts before create call int count = Keystore.list().length; - assertThat(mockCli.call(new String[] {"-a"}, CfgAion.inst())).isEqualTo(ERROR); + assertThat(mockCli.call(new String[] {"-a"}, cfg)).isEqualTo(ERROR); // ensure number of accounts is unchanged assertThat(Keystore.list().length).isEqualTo(count); - assertThat(mockCli.call(new String[] {"-a", "cre"}, CfgAion.inst())).isEqualTo(ERROR); + assertThat(mockCli.call(new String[] {"-a", "cre"}, cfg)).isEqualTo(ERROR); // ensure number of accounts is unchanged assertThat(Keystore.list().length).isEqualTo(count); - assertThat(mockCli.call(new String[] {"--acc", "create"}, CfgAion.inst())).isEqualTo(ERROR); + assertThat(mockCli.call(new String[] {"--acc", "create"}, cfg)).isEqualTo(ERROR); // ensure number of accounts is unchanged assertThat(Keystore.list().length).isEqualTo(count); - assertThat(mockCli.call(new String[] {"-a", "create"}, CfgAion.inst())).isEqualTo(EXIT); + assertThat(mockCli.call(new String[] {"-a", "create"}, cfg)).isEqualTo(EXIT); // ensure number of accounts was incremented assertThat(Keystore.list().length).isEqualTo(count + 1); - assertThat(mockCli.call(new String[] {"--account", "create"}, CfgAion.inst())) - .isEqualTo(EXIT); + assertThat(mockCli.call(new String[] {"--account", "create"}, cfg)).isEqualTo(EXIT); // ensure number of accounts was incremented assertThat(Keystore.list().length).isEqualTo(count + 2); } @@ -789,7 +783,7 @@ private Object parametersWithListAccount() { @Test @Parameters(method = "parametersWithListAccount") public void testListAccounts(String[] input, ReturnType expectedReturn) { - assertThat(cli.call(input, CfgAion.inst())).isEqualTo(expectedReturn); + assertThat(cli.call(input, cfg)).isEqualTo(expectedReturn); } @Test @@ -797,19 +791,19 @@ public void testListAccounts_withSplitInput() { // used to ensure number of accounts is unchanged int count = Keystore.list().length; - assertThat(cli.call(new String[] {"-a"}, CfgAion.inst())).isEqualTo(ERROR); + assertThat(cli.call(new String[] {"-a"}, cfg)).isEqualTo(ERROR); assertThat(Keystore.list().length).isEqualTo(count); - assertThat(cli.call(new String[] {"-a", "lis"}, CfgAion.inst())).isEqualTo(ERROR); + assertThat(cli.call(new String[] {"-a", "lis"}, cfg)).isEqualTo(ERROR); assertThat(Keystore.list().length).isEqualTo(count); - assertThat(cli.call(new String[] {"--acc", "list"}, CfgAion.inst())).isEqualTo(ERROR); + assertThat(cli.call(new String[] {"--acc", "list"}, cfg)).isEqualTo(ERROR); assertThat(Keystore.list().length).isEqualTo(count); - assertThat(cli.call(new String[] {"-a", "list"}, CfgAion.inst())).isEqualTo(EXIT); + assertThat(cli.call(new String[] {"-a", "list"}, cfg)).isEqualTo(EXIT); assertThat(Keystore.list().length).isEqualTo(count); - assertThat(cli.call(new String[] {"--account", "list"}, CfgAion.inst())).isEqualTo(EXIT); + assertThat(cli.call(new String[] {"--account", "list"}, cfg)).isEqualTo(EXIT); assertThat(Keystore.list().length).isEqualTo(count); } @@ -817,14 +811,11 @@ public void testListAccounts_withSplitInput() { @Test @Parameters({"-a export", "ae", "--account export"}) public void testExportPrivateKey(String option) { - Cli mockCli = Mockito.spy(new Cli()); - doReturn("password").when(mockCli).readPassword(any(), any()); - // create account - assertThat(mockCli.call(new String[] {"ac"}, CfgAion.inst())).isEqualTo(EXIT); + assertThat(mockCli.call(new String[] {"ac"}, cfg)).isEqualTo(EXIT); String account = Keystore.list()[0]; - assertEquals(EXIT, mockCli.call(new String[] {option, account}, CfgAion.inst())); + assertEquals(EXIT, mockCli.call(new String[] {option, account}, cfg)); } /** @@ -834,20 +825,12 @@ public void testExportPrivateKey(String option) { @Test @Parameters({"-a export", "ae", "--account export"}) public void testExportPrivateKey_withDataDir(String option) { - Cli mockCli = Mockito.spy(new Cli()); - doReturn("password").when(mockCli).readPassword(any(), any()); - // create account - assertThat(mockCli.call(new String[] {"ac", "-d", dataDirectory}, CfgAion.inst())) - .isEqualTo(EXIT); + assertThat(mockCli.call(new String[] {"ac", "-d", dataDirectory}, cfg)).isEqualTo(EXIT); String account = Keystore.list()[0]; - assertEquals( - EXIT, - mockCli.call(new String[] {option, account, "-d", dataDirectory}, CfgAion.inst())); - assertEquals( - EXIT, - mockCli.call(new String[] {"-d", dataDirectory, option, account}, CfgAion.inst())); + assertEquals(EXIT, mockCli.call(new String[] {option, account, "-d", dataDirectory}, cfg)); + assertEquals(EXIT, mockCli.call(new String[] {"-d", dataDirectory, option, account}, cfg)); } /** @@ -857,20 +840,12 @@ public void testExportPrivateKey_withDataDir(String option) { @Test @Parameters({"-a export", "ae", "--account export"}) public void testExportPrivateKey_withNetwork(String option) { - Cli mockCli = Mockito.spy(new Cli()); - doReturn("password").when(mockCli).readPassword(any(), any()); - // create account - assertThat(mockCli.call(new String[] {"ac", "-n", "mastery"}, CfgAion.inst())) - .isEqualTo(EXIT); + assertThat(mockCli.call(new String[] {"ac", "-n", "mastery"}, cfg)).isEqualTo(EXIT); String account = Keystore.list()[0]; - assertEquals( - EXIT, - mockCli.call(new String[] {option, account, "-n", "mastery"}, CfgAion.inst())); - assertEquals( - EXIT, - mockCli.call(new String[] {"-n", "mastery", option, account}, CfgAion.inst())); + assertEquals(EXIT, mockCli.call(new String[] {option, account, "-n", "mastery"}, cfg)); + assertEquals(EXIT, mockCli.call(new String[] {"-n", "mastery", option, account}, cfg)); } /** @@ -880,32 +855,23 @@ public void testExportPrivateKey_withNetwork(String option) { @Test @Parameters({"-a export", "ae", "--account export"}) public void testExportPrivateKey_withDataDirAndNetwork(String option) { - Cli mockCli = Mockito.spy(new Cli()); - doReturn("password").when(mockCli).readPassword(any(), any()); - // create account - assertThat( - mockCli.call( - new String[] {"ac", "-d", dataDirectory, "-n", "mastery"}, - CfgAion.inst())) + assertThat(mockCli.call(new String[] {"ac", "-d", dataDirectory, "-n", "mastery"}, cfg)) .isEqualTo(EXIT); String account = Keystore.list()[0]; assertEquals( EXIT, mockCli.call( - new String[] {"-n", "mastery", option, account, "-d", dataDirectory}, - CfgAion.inst())); + new String[] {"-n", "mastery", option, account, "-d", dataDirectory}, cfg)); assertEquals( EXIT, mockCli.call( - new String[] {"-n", "mastery", "-d", dataDirectory, option, account}, - CfgAion.inst())); + new String[] {"-n", "mastery", "-d", dataDirectory, option, account}, cfg)); assertEquals( EXIT, mockCli.call( - new String[] {option, account, "-d", dataDirectory, "-n", "mastery"}, - CfgAion.inst())); + new String[] {option, account, "-d", dataDirectory, "-n", "mastery"}, cfg)); } /** @@ -915,84 +881,178 @@ public void testExportPrivateKey_withDataDirAndNetwork(String option) { @Test @Parameters({"-a export", "ae", "--account export"}) public void testExportPrivateKey_wSubstringOfAccount(String prefix) { - Cli mockCli = Mockito.spy(new Cli()); - doReturn("password").when(mockCli).readPassword(any(), any()); - // create account - assertThat(mockCli.call(new String[] {"ac"}, CfgAion.inst())).isEqualTo(EXIT); + assertThat(mockCli.call(new String[] {"ac"}, cfg)).isEqualTo(EXIT); String subStrAcc = Keystore.list()[0].substring(1); - assertEquals(ERROR, cli.call(new String[] {prefix, subStrAcc}, CfgAion.inst())); + assertEquals(ERROR, cli.call(new String[] {prefix, subStrAcc}, cfg)); } @Test public void testExportPrivateKey_withSplitInput() { - Cli mockCli = Mockito.spy(new Cli()); - doReturn("password").when(mockCli).readPassword(any(), any()); // create account - assertThat(mockCli.call(new String[] {"ac"}, CfgAion.inst())).isEqualTo(EXIT); + assertThat(mockCli.call(new String[] {"ac"}, cfg)).isEqualTo(EXIT); String account = Keystore.list()[0]; - assertThat(mockCli.call(new String[] {"-a", account}, CfgAion.inst())).isEqualTo(ERROR); + assertThat(mockCli.call(new String[] {"-a", account}, cfg)).isEqualTo(ERROR); - assertThat(mockCli.call(new String[] {"-a", "exp", account}, CfgAion.inst())) - .isEqualTo(ERROR); + assertThat(mockCli.call(new String[] {"-a", "exp", account}, cfg)).isEqualTo(ERROR); - assertThat(mockCli.call(new String[] {"--acc", "export", account}, CfgAion.inst())) - .isEqualTo(ERROR); + assertThat(mockCli.call(new String[] {"--acc", "export", account}, cfg)).isEqualTo(ERROR); - assertThat(mockCli.call(new String[] {"-a", "export", account}, CfgAion.inst())) - .isEqualTo(EXIT); + assertThat(mockCli.call(new String[] {"-a", "export", account}, cfg)).isEqualTo(EXIT); - assertThat(mockCli.call(new String[] {"--account", "export", account}, CfgAion.inst())) + assertThat(mockCli.call(new String[] {"--account", "export", account}, cfg)) .isEqualTo(EXIT); } - /** Tests the -a import arguments work on a fail import key. */ + /** Ensures that the { -a import, a, --account import } arguments work. */ @Test - @Ignore - public void testImportPrivateKey() { + @Parameters({"-a import", "ai", "--account import"}) + public void testImportPrivateKey(String option) { + // test 1: successful call ECKey key = ECKeyFac.inst().create(); + String pKey = Hex.toHexString(key.getPrivKeyBytes()); - String[] args = {"-a", "import", Hex.toHexString(key.getPrivKeyBytes())}; - assertEquals(EXIT, cli.call(args, CfgAion.inst())); + assertEquals(EXIT, mockCli.call(new String[] {option, pKey}, cfg)); + assertThat(Keystore.list().length).isEqualTo(1); + + // test 2: error -> known key + assertEquals(ERROR, mockCli.call(new String[] {option, pKey}, cfg)); + assertThat(Keystore.list().length).isEqualTo(1); } - /** Tests the -a import arguments fail when a non-private key is supplied. */ + /** + * Ensures that the { -a import, a, --account import } arguments work in + * combination with the data directory option. + */ @Test - @Ignore - public void testImportNonPrivateKey() { - String account = Keystore.create("password"); + @Parameters({"-a import", "ai", "--account import"}) + public void testImportPrivateKey_withDataDir(String option) { + // test 1: -d last + ECKey key = ECKeyFac.inst().create(); + String pKey = Hex.toHexString(key.getPrivKeyBytes()); - String[] args = {"-a", "import", account}; - assertEquals(ERROR, cli.call(args, CfgAion.inst())); + assertEquals(EXIT, mockCli.call(new String[] {option, pKey, "-d", dataDirectory}, cfg)); + assertThat(Keystore.list().length).isEqualTo(1); + + // test 2: known key + assertEquals(ERROR, mockCli.call(new String[] {option, pKey, "-d", dataDirectory}, cfg)); + assertThat(Keystore.list().length).isEqualTo(1); + + // test 3: -d first + key = ECKeyFac.inst().create(); + pKey = Hex.toHexString(key.getPrivKeyBytes()); + + assertEquals(EXIT, mockCli.call(new String[] {"-d", dataDirectory, option, pKey}, cfg)); + assertThat(Keystore.list().length).isEqualTo(2); } - /** Tests the -a import arguments work when a valid private key is supplied. */ + /** + * Ensures that the { -a import, a, --account import } arguments work in + * combination with the network option. + */ @Test - @Ignore - public void testImportPrivateKey2() { + @Parameters({"-a import", "ai", "--account import"}) + public void testImportPrivateKey_withNetwork(String option) { + // test 1: -n last ECKey key = ECKeyFac.inst().create(); - System.out.println("Original address : " + Hex.toHexString(key.getAddress())); - System.out.println("Original public key : " + Hex.toHexString(key.getPubKey())); - System.out.println("Original private key: " + Hex.toHexString(key.getPrivKeyBytes())); + String pKey = Hex.toHexString(key.getPrivKeyBytes()); + + assertEquals(EXIT, mockCli.call(new String[] {option, pKey, "-n", "mastery"}, cfg)); + assertThat(Keystore.list().length).isEqualTo(1); + + // test 2: known key + assertEquals(ERROR, mockCli.call(new String[] {option, pKey, "-n", "mastery"}, cfg)); + assertThat(Keystore.list().length).isEqualTo(1); - String[] args = {"-a", "import", Hex.toHexString(key.getPrivKeyBytes())}; - assertEquals(EXIT, cli.call(args, CfgAion.inst())); + // test 3: -n first + key = ECKeyFac.inst().create(); + pKey = Hex.toHexString(key.getPrivKeyBytes()); - ECKey key2 = Keystore.getKey(Hex.toHexString(key.getAddress()), "password"); - System.out.println("Imported address : " + Hex.toHexString(key2.getAddress())); - System.out.println("Imported public key : " + Hex.toHexString(key2.getPubKey())); - System.out.println("Imported private key: " + Hex.toHexString(key2.getPrivKeyBytes())); + assertEquals(EXIT, mockCli.call(new String[] {"-n", "mastery", option, pKey}, cfg)); + assertThat(Keystore.list().length).isEqualTo(2); } - /** Tests the -a import arguments fail given an invalid private key. */ + /** + * Ensures that the { -a import, a, --account import } arguments work in + * combination with the data directory and network options. + */ @Test - @Ignore - public void testImportPrivateKeyWrong() { - String[] args = {"-a", "import", "hello"}; - assertEquals(ERROR, cli.call(args, CfgAion.inst())); + @Parameters({"-a import", "ai", "--account import"}) + public void testImportPrivateKey_withDataDirAndNetwork(String option) { + // test 1: -d -n last + ECKey key = ECKeyFac.inst().create(); + String pKey = Hex.toHexString(key.getPrivKeyBytes()); + + assertEquals( + EXIT, + mockCli.call( + new String[] {option, pKey, "-d", dataDirectory, "-n", "mastery"}, cfg)); + assertThat(Keystore.list().length).isEqualTo(1); + + // test 2: known key + assertEquals( + ERROR, + mockCli.call( + new String[] {option, pKey, "-d", dataDirectory, "-n", "mastery"}, cfg)); + assertThat(Keystore.list().length).isEqualTo(1); + + // test 3: -d -n first + key = ECKeyFac.inst().create(); + pKey = Hex.toHexString(key.getPrivKeyBytes()); + + assertEquals( + EXIT, + mockCli.call( + new String[] {"-n", "mastery", "-d", dataDirectory, option, pKey}, cfg)); + assertThat(Keystore.list().length).isEqualTo(2); + + // test 4: ai middle + key = ECKeyFac.inst().create(); + pKey = Hex.toHexString(key.getPrivKeyBytes()); + + assertEquals( + EXIT, + mockCli.call( + new String[] {"-n", "mastery", option, pKey, "-d", dataDirectory}, cfg)); + assertThat(Keystore.list().length).isEqualTo(3); + } + + @Test + public void testImportPrivateKey_withSplitInput() { + // create account + ECKey key = ECKeyFac.inst().create(); + String pKey = Hex.toHexString(key.getPrivKeyBytes()); + + assertThat(mockCli.call(new String[] {"-a", pKey}, cfg)).isEqualTo(ERROR); + + assertThat(mockCli.call(new String[] {"-a", "imp", pKey}, cfg)).isEqualTo(ERROR); + + assertThat(mockCli.call(new String[] {"--acc", "import", pKey}, cfg)).isEqualTo(ERROR); + + assertThat(mockCli.call(new String[] {"-a", "import", pKey}, cfg)).isEqualTo(EXIT); + assertThat(Keystore.list().length).isEqualTo(1); + + key = ECKeyFac.inst().create(); + pKey = Hex.toHexString(key.getPrivKeyBytes()); + assertThat(mockCli.call(new String[] {"--account", "import", pKey}, cfg)).isEqualTo(EXIT); + assertThat(Keystore.list().length).isEqualTo(2); + } + + /** + * Ensures that the { -a import, a, --account import } arguments fail when + * a non-private key is supplied. + */ + @Test + @Parameters({"-a import", "ai", "--account import"}) + public void testImportNonPrivateKey(String option) { + String fakePKey = Hex.toHexString("random".getBytes()); + + assertThat(mockCli.call(new String[] {option, fakePKey}, cfg)).isEqualTo(ERROR); + + assertThat(mockCli.call(new String[] {option, "hello"}, cfg)).isEqualTo(ERROR); } // Methods below taken from FileUtils class From 95cdea218ed2cd0d1905f93c1898bd3208d02ba5 Mon Sep 17 00:00:00 2001 From: Alexandra Date: Fri, 21 Sep 2018 17:54:21 -0400 Subject: [PATCH 24/31] option/directory for custom networks --- .../org/aion/zero/impl/config/Network.java | 31 ++++++- .../aion/zero/impl/config/NetworkTest.java | 5 ++ modBoot/resource/custom/config.xml | 82 +++++++++++++++++++ modBoot/resource/custom/genesis.json | 19 +++++ 4 files changed, 133 insertions(+), 4 deletions(-) create mode 100644 modBoot/resource/custom/config.xml create mode 100644 modBoot/resource/custom/genesis.json diff --git a/modAionImpl/src/org/aion/zero/impl/config/Network.java b/modAionImpl/src/org/aion/zero/impl/config/Network.java index 0d6db1a13d..0a293005c1 100644 --- a/modAionImpl/src/org/aion/zero/impl/config/Network.java +++ b/modAionImpl/src/org/aion/zero/impl/config/Network.java @@ -32,9 +32,10 @@ public enum Network { MAINNET("mainnet", 256), CONQUEST("conquest", 128), - MASTERY("mastery", 32); + MASTERY("mastery", 32), + CUSTOM("custom", 0); - private String name; + private final String name; private int identifier; /** @@ -85,22 +86,44 @@ public static Network determineNetwork(String network) { /** * Utility method that determines the correct network based on the given identifier. * - * @param identifier an integer value representing the network identifier + * @param identifier a positive integer value representing the network identifier * @return the network object corresponding to the int value or null when the value is not * mapped to an object. */ public static Network determineNetwork(int identifier) { + if (identifier < 0) { + return null; + } + for (Network net : Network.values()) { if (identifier == net.getIdentifier()) { return net; } } - return null; + // custom networks may have any positive identifier not already taken + Network net = Network.CUSTOM; + net.identifier = identifier; + return net; } public static String valueString() { String output = Arrays.toString(Network.values()); return output.substring(1, output.length() - 1); } + + /** + * Generates a custom network with the given identifier. + * + * @param identifier a positive integer value representing the network identifier + * @return a custom network object with the given identifier. + */ + public static Object getCustomNet(int identifier) { + if (identifier < 0) { + return null; + } + Network net = Network.CUSTOM; + net.identifier = identifier; + return net; + } } diff --git a/modAionImpl/test/org/aion/zero/impl/config/NetworkTest.java b/modAionImpl/test/org/aion/zero/impl/config/NetworkTest.java index 134767f3e7..a61535d168 100644 --- a/modAionImpl/test/org/aion/zero/impl/config/NetworkTest.java +++ b/modAionImpl/test/org/aion/zero/impl/config/NetworkTest.java @@ -52,6 +52,10 @@ private Object stringToNetworkMappings() { parameters.add(new Object[] {"testnet", Network.MASTERY}); parameters.add(new Object[] {"TESTNET", Network.MASTERY}); + parameters.add(new Object[] {"custom", Network.CUSTOM}); + parameters.add(new Object[] {"CUSTOM", Network.CUSTOM}); + parameters.add(new Object[] {"custom", Network.getCustomNet(1000)}); + parameters.add(new Object[] {"undefined", null}); return parameters.toArray(); @@ -73,6 +77,7 @@ private Object intToNetworkMappings() { } parameters.add(new Object[] {-1, null}); + parameters.add(new Object[] {1000, Network.getCustomNet(1000)}); return parameters.toArray(); } diff --git a/modBoot/resource/custom/config.xml b/modBoot/resource/custom/config.xml new file mode 100644 index 0000000000..645f43f1ec --- /dev/null +++ b/modBoot/resource/custom/config.xml @@ -0,0 +1,82 @@ + + + aion + [NODE-ID-PLACEHOLDER] + + + + false + + web3,eth,personal,stratum + + 1 + + + + + 10E9 + + 100E9 + + false + + + + 0 + + + + 0.0.0.0 + 30303 + false + 128 + + + + + 32 + + false + + + true + 0000000000000000000000000000000000000000000000000000000000000000 + 2 + AION + + + + + + + + + + database + + false + + + + + FULL + + + leveldb + + false + + + + true + + log + INFO + ERROR + INFO + INFO + ERROR + INFO + INFO + + diff --git a/modBoot/resource/custom/genesis.json b/modBoot/resource/custom/genesis.json new file mode 100644 index 0000000000..f8b0b2249f --- /dev/null +++ b/modBoot/resource/custom/genesis.json @@ -0,0 +1,19 @@ +{ + "alloc": { + "0x0000000000000000000000000000000000000000000000000000000000000000": { + "balance": "465934586660000000000000000" + } + }, + "networkBalanceAllocs": { + "0": { + "balance": "465934586660000000000000000" + } + }, + "energyLimit": "15000000", + "nonce": "0x00", + "difficulty": "0x0010", + "coinbase": "0x0000000000000000000000000000000000000000000000000000000000000000", + "timestamp": "1525924800", + "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "chainId": "0" +} From d24ef53d6e6215f5f230d4c523cf6eb8e1e61e13 Mon Sep 17 00:00:00 2001 From: Alexandra Date: Mon, 24 Sep 2018 09:25:09 -0400 Subject: [PATCH 25/31] made CLI tests less verbose; corrected GenesisTestNetJsonTest to use test resources --- .../test/org/aion/util/TestResources.java | 29 ++++------- .../zero/impl/GenesisTestNetJsonTest.java | 51 +++++++++++------- .../test/org/aion/zero/impl/cli/CliTest.java | 2 +- modAionImpl/test_resources/testnet.json | 52 +++++++++++++++++++ 4 files changed, 95 insertions(+), 39 deletions(-) create mode 100755 modAionImpl/test_resources/testnet.json diff --git a/modAionImpl/test/org/aion/util/TestResources.java b/modAionImpl/test/org/aion/util/TestResources.java index fc0bed1b32..f7a64d5000 100644 --- a/modAionImpl/test/org/aion/util/TestResources.java +++ b/modAionImpl/test/org/aion/util/TestResources.java @@ -36,22 +36,21 @@ public class TestResources { private static final String userDir = System.getProperty("user.dir"); - private static final String module = "modAionImpl"; - private static final String rawDataFileWithRandomBlocks = "test_resources/raw-block-data.txt"; - private static final String rawDataFileWithConsecutiveBlocks = - "test_resources/consecutive-raw-block-data.txt"; + private static final String rawDataFileWithRandomBlocks = "raw-block-data.txt"; + private static final String rawDataFileWithConsecutiveBlocks = "consecutive-raw-block-data.txt"; + private static final String moduleDir = "modAionImpl"; + private static final String testResourceDir = "test_resources"; + + public static final File TEST_RESOURCE_DIR = + userDir.contains(moduleDir) + ? new File(userDir, testResourceDir) + : new File(userDir, moduleDir + File.separator + testResourceDir); /** Extracts raw block data from a test resource file. */ public static List rawBlockData(String rawDataFile) { List parameters = new ArrayList<>(); - File file; - - if (userDir.contains(module)) { - file = new File(userDir, rawDataFile); - } else { - file = new File(userDir, module + "/" + rawDataFile); - } + File file = new File(TEST_RESOURCE_DIR, rawDataFile); BufferedReader reader = null; @@ -83,13 +82,7 @@ public static List rawBlockData(String rawDataFile) { public static List rawBlockData(int limit, String rawDataFile) { List parameters = new ArrayList<>(); - File file; - - if (userDir.contains(module)) { - file = new File(userDir, rawDataFile); - } else { - file = new File(userDir, module + "/" + rawDataFile); - } + File file = new File(TEST_RESOURCE_DIR, rawDataFile); BufferedReader reader = null; diff --git a/modAionImpl/test/org/aion/zero/impl/GenesisTestNetJsonTest.java b/modAionImpl/test/org/aion/zero/impl/GenesisTestNetJsonTest.java index 08c5e5cb98..9c17983ad8 100644 --- a/modAionImpl/test/org/aion/zero/impl/GenesisTestNetJsonTest.java +++ b/modAionImpl/test/org/aion/zero/impl/GenesisTestNetJsonTest.java @@ -1,27 +1,45 @@ +/* + * Copyright (c) 2017-2018 Aion foundation. + * + * This file is part of the aion network project. + * + * The aion network project is free software: you can redistribute it + * and/or modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or any later version. + * + * The aion network project is distributed in the hope that it will + * be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with the aion network project source files. + * If not, see . + * + * Contributors: + * Aion foundation. + */ package org.aion.zero.impl; +import static com.google.common.truth.Truth.assertThat; +import static org.aion.util.TestResources.TEST_RESOURCE_DIR; + +import java.io.File; +import java.io.IOException; +import java.math.BigInteger; +import java.util.Map; import org.aion.base.type.Address; import org.aion.base.util.ByteUtil; import org.aion.mcf.core.AccountState; import org.aion.zero.exceptions.HeaderStructureException; -import org.aion.zero.impl.AionGenesis; -import org.aion.zero.impl.GenesisBlockLoader; import org.junit.BeforeClass; import org.junit.Test; -import java.io.IOException; -import java.math.BigInteger; -import java.util.Map; - -import static com.google.common.truth.Truth.assertThat; - public class GenesisTestNetJsonTest { - // if we're running in the module itself - private static final String TESTNET_DEFAULT_LOCATION = "../modBoot/resource/conquest/genesis.json"; - - // if we're running in the project folder - private static final String TESTNET_ALT_LOCATION = "./modBoot/resource/conquest/genesis.json"; + private static final String TESTNET_DEFAULT_LOCATION = + new File(TEST_RESOURCE_DIR, "testnet.json").getAbsolutePath(); private static AionGenesis genesis; @@ -41,13 +59,6 @@ public static void loadFile() { } System.out.println(String.format("Failed to load genesis from: %s", TESTNET_DEFAULT_LOCATION)); - System.out.println("trying alternative"); - try { - genesis = GenesisBlockLoader.loadJSON(TESTNET_ALT_LOCATION); - } catch (IOException | HeaderStructureException e2) { - System.out.println("Failed to load from alternative location"); - e2.printStackTrace(); - } } } diff --git a/modAionImpl/test/org/aion/zero/impl/cli/CliTest.java b/modAionImpl/test/org/aion/zero/impl/cli/CliTest.java index 0abc7af297..70ada0c772 100644 --- a/modAionImpl/test/org/aion/zero/impl/cli/CliTest.java +++ b/modAionImpl/test/org/aion/zero/impl/cli/CliTest.java @@ -90,7 +90,7 @@ public class CliTest { private static final File testnetGenesis = new File(BASE_PATH, "config/mastery/genesis.json"); /** @implNote set this to true to enable printing */ - private static final boolean verbose = true; + private static final boolean verbose = false; @Before public void setup() { diff --git a/modAionImpl/test_resources/testnet.json b/modAionImpl/test_resources/testnet.json new file mode 100755 index 0000000000..ff86d9ca7b --- /dev/null +++ b/modAionImpl/test_resources/testnet.json @@ -0,0 +1,52 @@ +{ + "alloc": { + "0xa0483412e8c8e769df037231d336e96f7f6d843cf7224c3d8fbe0ec7cdc12ac6": { + "balance": "314159000000000000000000" + }, + "0xa0353561f8b6b5a8b56d535647a4ddd7278e80c2494e3314d1f0605470f56925": { + "balance": "314159000000000000000000" + }, + "0xa0274c1858ca50576f4d4d18b719787b76bb454c33749172758a620555bf4586": { + "balance": "314159000000000000000000" + }, + "0xa06691a968e8fe22dc79b6dd503d44cb96d9a523ae265b63c6752389be90185d": { + "balance": "314159000000000000000000" + }, + "0xa0a95b372efe55c77a75364407f0403dfefd3131519ca980b2d92b1d2d7297a7": { + "balance": "314159000000000000000000" + }, + "0xa07262e1d026fca027e5f4f38668303b74a2bba8bd07470fa39d0f3b03f882f1": { + "balance": "314159000000000000000000" + }, + "0xa08f92c80e34c95b8b2e8cb208d47e3e6048b7e0ea44d866e33e865c365b1417": { + "balance": "314159000000000000000000" + }, + "0xa022045f22772463c94e08d1fd32f7b251d4c9cfd1c92e039f1517b906776283": { + "balance": "314159000000000000000000" + }, + "0xa04c282f636feff4d6b35174fc2d8e05c23df4b1d59508712712627184dd8a93": { + "balance": "314159000000000000000000" + }, + "0xa0f8654c63ae53598cf42435f53b1ebd9b7df6cbceba10af235aa2393f03034c": { + "balance": "314159000000000000000000" + }, + "0xa0f3defb01b531c5a28680eb928e79ea18bc155f1060e1d923d660d74883518b": { + "balance": "314159000000000000000000" + }, + "0xa02198c9192bb89e9b2e8536d96cbf287fde80625afcf1a5e5632e23c1260d61": { + "balance": "465934586660000000000000000" + } + }, + "networkBalanceAlloc": { + "0": { + "balance": "465934586660000000000000000" + } + }, + "energyLimit": "10000000", + "nonce": "0x00", + "difficulty": "0x10", + "coinbase": "0x0000000000000000000000000000000000000000000000000000000000000000", + "timestamp": "1497536993", + "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "chainId": "2" +} From c9b166ca03c35c366245d308bbdeb33b3ffd965b Mon Sep 17 00:00:00 2001 From: Alexandra Date: Thu, 27 Sep 2018 16:12:26 -0400 Subject: [PATCH 26/31] debugging and refactoring --- .../src/org/aion/zero/impl/cli/Cli.java | 36 ++- .../org/aion/zero/impl/config/CfgAion.java | 10 +- .../org/aion/zero/impl/config/Network.java | 2 +- .../test/org/aion/zero/impl/cli/CliTest.java | 199 ++++++------ modBoot/src/org/aion/Aion.java | 48 +-- modMcf/src/org/aion/mcf/config/Cfg.java | 300 ++++++++++-------- modMcf/src/org/aion/mcf/config/CfgLog.java | 5 + 7 files changed, 329 insertions(+), 271 deletions(-) diff --git a/modAionImpl/src/org/aion/zero/impl/cli/Cli.java b/modAionImpl/src/org/aion/zero/impl/cli/Cli.java index 01bd1b7a8c..1cd499e8aa 100644 --- a/modAionImpl/src/org/aion/zero/impl/cli/Cli.java +++ b/modAionImpl/src/org/aion/zero/impl/cli/Cli.java @@ -192,12 +192,16 @@ public ReturnType call(final String[] args, Cfg cfg) { System.out.println( "Reading config file from: " + getRelativePath(configFile.getAbsolutePath())); + if (overwrite) { + // updating the file in case the user id was not set + cfg.toXML(new String[] {"--id=" + cfg.getId()}, configFile); + } printInfo(cfg); return ReturnType.EXIT; } // make directories for kernel execution - makeDirs(cfg); + makeDirs(configFile, cfg); if (overwrite) { // only updating the file in case the user id was not set @@ -205,7 +209,7 @@ public ReturnType call(final String[] args, Cfg cfg) { } // set correct keystore directory - Keystore.setKeystorePath(cfg.getKeystorePath()); + Keystore.setKeystorePath(cfg.getKeystoreDir().getAbsolutePath()); if (options.isCreateAccount()) { if (!createAccount()) { @@ -478,7 +482,7 @@ private boolean setDirectory(String directory, Cfg cfg) { } if (file.isDirectory() && file.canWrite()) { - cfg.setExecDirectory(file); + cfg.setDataDirectory(file); return true; } else { return false; @@ -498,7 +502,7 @@ private void setNetwork(String network, Cfg cfg) { private void printInvalidNetwork() { System.out.println("\nInvalid network selected!\n"); System.out.println("------ Available Networks ------"); - System.out.println(Network.valueString()); + System.out.println(Network.valuesString()); System.out.println("--------------------------------\n"); } @@ -509,8 +513,8 @@ private void printInvalidNetwork() { * @param cfg the configuration for the runtime kernel environment */ @SuppressWarnings("ResultOfMethodCallIgnored") - private void makeDirs(Cfg cfg) { - File file = cfg.getExecDirectory(); + private void makeDirs(File startConfigFile, Cfg cfg) { + File file = cfg.getExecDir(); if (!file.exists()) { file.mkdirs(); } @@ -522,33 +526,33 @@ private void makeDirs(Cfg cfg) { } // copy config file - File initial = cfg.getInitialConfigFile(); + File initial = startConfigFile; File target = cfg.getExecConfigFile(); if (!initial.equals(target)) { copyRecursively(initial, target); - } - // copy genesis file - initial = cfg.getInitialGenesisFile(); - target = cfg.getExecGenesisFile(); - if (!initial.equals(target)) { - copyRecursively(initial, target); + // copy genesis file + initial = cfg.getInitialGenesisFile(); + target = cfg.getExecGenesisFile(); + if (!initial.equals(target)) { + copyRecursively(initial, target); + } } // create target log directory - file = cfg.getLogDirectory(); + file = cfg.getLogDir(); if (!file.exists()) { file.mkdirs(); } // create target database directory - file = cfg.getDatabaseDirectory(); + file = cfg.getDatabaseDir(); if (!file.exists()) { file.mkdirs(); } // create target keystore directory - file = cfg.getKeystoreDirectory(); + file = cfg.getKeystoreDir(); if (!file.exists()) { file.mkdirs(); } diff --git a/modAionImpl/src/org/aion/zero/impl/config/CfgAion.java b/modAionImpl/src/org/aion/zero/impl/config/CfgAion.java index b08bee85a3..52df09ab4a 100644 --- a/modAionImpl/src/org/aion/zero/impl/config/CfgAion.java +++ b/modAionImpl/src/org/aion/zero/impl/config/CfgAion.java @@ -54,9 +54,6 @@ /** @author chris */ public final class CfgAion extends Cfg { - private static String NETWORK = "mainnet"; - private Network network = Network.MAINNET; - protected AionGenesis genesis; protected static final int N = 210; @@ -77,6 +74,7 @@ public CfgAion() { this.tx = new CfgTx(); this.reports = new CfgReports(); this.gui = new CfgGui(); + initializeConfiguration(); } private static class CfgAionHolder { @@ -94,7 +92,7 @@ public static void setInst(CfgAion cfgAion) { @Override public void setGenesis() { try { - this.genesis = GenesisBlockLoader.loadJSON(getInitialGenesisPath()); + this.genesis = GenesisBlockLoader.loadJSON(getInitialGenesisFile().getAbsolutePath()); } catch (IOException | HeaderStructureException e) { System.out.println(String.format("Genesis load exception %s", e.getMessage())); System.out.println("defaulting to default AionGenesis configuration"); @@ -262,13 +260,13 @@ public boolean fromXML(File cfgFile) { // checks for absolute path for database File db = new File(this.getDb().getPath()); if (db.isAbsolute()) { - this.setDatabaseDirectory(db); + this.setDatabaseDir(db); } // checks for absolute path for log File log = new File(this.getLog().getLogPath()); if (log.isAbsolute()) { - this.setLogDirectory(log); + this.setLogDir(log); } return shouldWriteBackToFile; diff --git a/modAionImpl/src/org/aion/zero/impl/config/Network.java b/modAionImpl/src/org/aion/zero/impl/config/Network.java index 0a293005c1..63643847aa 100644 --- a/modAionImpl/src/org/aion/zero/impl/config/Network.java +++ b/modAionImpl/src/org/aion/zero/impl/config/Network.java @@ -107,7 +107,7 @@ public static Network determineNetwork(int identifier) { return net; } - public static String valueString() { + public static String valuesString() { String output = Arrays.toString(Network.values()); return output.substring(1, output.length() - 1); } diff --git a/modAionImpl/test/org/aion/zero/impl/cli/CliTest.java b/modAionImpl/test/org/aion/zero/impl/cli/CliTest.java index 70ada0c772..90f2ead18f 100644 --- a/modAionImpl/test/org/aion/zero/impl/cli/CliTest.java +++ b/modAionImpl/test/org/aion/zero/impl/cli/CliTest.java @@ -23,12 +23,15 @@ package org.aion.zero.impl.cli; import static com.google.common.truth.Truth.assertThat; +import static org.aion.util.TestResources.TEST_RESOURCE_DIR; import static org.aion.zero.impl.cli.Cli.ReturnType.ERROR; import static org.aion.zero.impl.cli.Cli.ReturnType.EXIT; import static org.aion.zero.impl.cli.Cli.ReturnType.RUN; import static org.junit.Assert.assertEquals; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doCallRealMethod; import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.spy; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; @@ -54,40 +57,45 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.Mockito; /** CliTest for new version with use of different networks. */ @RunWith(JUnitParamsRunner.class) public class CliTest { private final Cli cli = new Cli(); - private CfgAion cfg = CfgAion.inst(); - private final Cli mockCli = Mockito.spy(new Cli()); + private final CfgAion cfg = CfgAion.inst(); + private final Cli mockCli = spy(cli); + // base paths private static final String BASE_PATH = System.getProperty("user.dir"); + private static final File MAIN_BASE_PATH = new File(BASE_PATH, "mainnet"); + private static final File TEST_BASE_PATH = new File(BASE_PATH, "mastery"); + + // config paths + private static final File CONFIG_PATH = new File(BASE_PATH, "config"); + private static final File MAIN_CONFIG_PATH = new File(CONFIG_PATH, "mainnet"); + private static final File TEST_CONFIG_PATH = new File(CONFIG_PATH, "mastery"); + private static final String module = "modAionImpl"; - private static final String initialConfigFile = "test_resources/config.xml"; - private static final String initialGenesisFile = "test_resources/genesis.json"; + + private static final String configFileName = "config.xml"; + private static final String genesisFileName = "genesis.json"; + private static final String dataDirectory = "datadir"; private static final String alternativeDirectory = "random"; + private static final File path = new File(BASE_PATH, dataDirectory); private static final File alternativePath = new File(BASE_PATH, alternativeDirectory); - private static final File config = - BASE_PATH.contains(module) - ? new File(BASE_PATH, initialConfigFile) - : new File(BASE_PATH, module + "/" + initialConfigFile); - private static final File oldConfig = new File(BASE_PATH, "config/config.xml"); - private static final File mainnetConfig = new File(BASE_PATH, "config/mainnet/config.xml"); - private static final File testnetConfig = new File(BASE_PATH, "config/mastery/config.xml"); - - private static final File genesis = - BASE_PATH.contains(module) - ? new File(BASE_PATH, initialGenesisFile) - : new File(BASE_PATH, module + "/" + initialGenesisFile); - private static final File oldGenesis = new File(BASE_PATH, "config/genesis.json"); - private static final File mainnetGenesis = new File(BASE_PATH, "config/mainnet/genesis.json"); - private static final File testnetGenesis = new File(BASE_PATH, "config/mastery/genesis.json"); + private static final File config = new File(TEST_RESOURCE_DIR, configFileName); + private static final File oldConfig = new File(CONFIG_PATH, configFileName); + private static final File mainnetConfig = new File(MAIN_CONFIG_PATH, configFileName); + private static final File testnetConfig = new File(TEST_CONFIG_PATH, configFileName); + + private static final File genesis = new File(TEST_RESOURCE_DIR, genesisFileName); + private static final File oldGenesis = new File(CONFIG_PATH, genesisFileName); + private static final File mainnetGenesis = new File(MAIN_CONFIG_PATH, genesisFileName); + private static final File testnetGenesis = new File(TEST_CONFIG_PATH, genesisFileName); /** @implNote set this to true to enable printing */ private static final boolean verbose = false; @@ -99,9 +107,8 @@ public void setup() { if (BASE_PATH.contains(module) && !mainnetConfig.exists()) { // save config to disk at expected location for new kernel - File configPath = new File(BASE_PATH, "config/mainnet"); - if (!configPath.exists()) { - configPath.mkdirs(); + if (!MAIN_CONFIG_PATH.exists()) { + assertThat(MAIN_CONFIG_PATH.mkdirs()).isTrue(); } Cli.copyRecursively(config, mainnetConfig); Cli.copyRecursively(genesis, mainnetGenesis); @@ -109,9 +116,8 @@ public void setup() { if (BASE_PATH.contains(module) && !testnetConfig.exists()) { // save config to disk at expected location for new kernel - File configPath = new File(BASE_PATH, "config/mastery"); - if (!configPath.exists()) { - configPath.mkdirs(); + if (!TEST_CONFIG_PATH.exists()) { + assertThat(TEST_CONFIG_PATH.mkdirs()).isTrue(); } Cli.copyRecursively(config, mainnetConfig); Cli.copyRecursively(genesis, testnetGenesis); @@ -120,6 +126,7 @@ public void setup() { cfg.resetInternal(); doReturn("password").when(mockCli).readPassword(any(), any()); + doCallRealMethod().when(mockCli).call(any(), any()); } @After @@ -129,11 +136,11 @@ public void shutdown() { // to avoid deleting config for all tests if (BASE_PATH.contains(module)) { - deleteRecursively(new File(BASE_PATH, "config")); + deleteRecursively(CONFIG_PATH); } - deleteRecursively(new File(BASE_PATH, "mainnet")); - deleteRecursively(new File(BASE_PATH, "mastery")); + deleteRecursively(MAIN_BASE_PATH); + deleteRecursively(TEST_BASE_PATH); } /** @@ -153,6 +160,7 @@ private Object parametersWithDirectoryAndNetwork() { String[] dir_options = new String[] {"-d", "--datadir"}; String[] net_options = new String[] {"-n", "--network"}; String expected = new File(path, "mainnet").getAbsolutePath(); + String expOnError = MAIN_BASE_PATH.getAbsolutePath(); // data directory alone for (String op : dir_options) { @@ -161,9 +169,9 @@ private Object parametersWithDirectoryAndNetwork() { // with absolute path parameters.add(new Object[] {new String[] {op, path.getAbsolutePath()}, RUN, expected}); // without value - parameters.add(new Object[] {new String[] {op}, ERROR, BASE_PATH}); + parameters.add(new Object[] {new String[] {op}, ERROR, expOnError}); // with invalid characters (Linux & Win) - parameters.add(new Object[] {new String[] {op, "/\\<>:\"|?*"}, ERROR, BASE_PATH}); + parameters.add(new Object[] {new String[] {op, "/\\<>:\"|?*"}, ERROR, expOnError}); } // network and directory @@ -198,13 +206,13 @@ private Object parametersWithDirectoryAndNetwork() { } // network alone - expected = new File(BASE_PATH, "mainnet").getAbsolutePath(); + expected = MAIN_BASE_PATH.getAbsolutePath(); for (String op : net_options) { // without parameter - parameters.add(new Object[] {new String[] {op}, ERROR, BASE_PATH}); + parameters.add(new Object[] {new String[] {op}, ERROR, expOnError}); // with two parameters parameters.add( - new Object[] {new String[] {op, "testnet", "mastery"}, ERROR, BASE_PATH}); + new Object[] {new String[] {op, "testnet", "mastery"}, ERROR, expOnError}); // invalid parameter parameters.add(new Object[] {new String[] {op, "invalid"}, RUN, expected}); // mainnet as parameter @@ -213,7 +221,7 @@ private Object parametersWithDirectoryAndNetwork() { // network alone with testnet net_values = new String[] {"mastery", "testnet"}; - expected = new File(BASE_PATH, "mastery").getAbsolutePath(); + expected = TEST_BASE_PATH.getAbsolutePath(); for (String op : net_options) { for (String netVal : net_values) { // mastery as parameter @@ -253,14 +261,14 @@ private Object parametersWithDirectoryAndNetwork() { } // with subdirectories - String dir = dataDirectory + "/subfolder"; + String dir = dataDirectory + File.separator + "subfolder"; File path = new File(BASE_PATH, dir); expected = new File(path, "mainnet").getAbsolutePath(); for (String op : dir_options) { // with relative path with subdirectories parameters.add(new Object[] {new String[] {op, dir}, RUN, expected}); // with multiple values - parameters.add(new Object[] {new String[] {op, dataDirectory, dir}, ERROR, BASE_PATH}); + parameters.add(new Object[] {new String[] {op, dataDirectory, dir}, ERROR, expOnError}); } return parameters.toArray(); @@ -275,15 +283,13 @@ public void testDirectoryAndNetwork( String[] input, ReturnType expectedReturn, String expectedPath) { assertThat(cli.call(input, cfg)).isEqualTo(expectedReturn); assertThat(cfg.getBasePath()).isEqualTo(expectedPath); - assertThat(cfg.getExecConfigPath()) - .isEqualTo(new File(expectedPath, "config/config.xml").getAbsolutePath()); - assertThat(cfg.getExecGenesisPath()) - .isEqualTo(new File(expectedPath, "config/genesis.json").getAbsolutePath()); - assertThat(cfg.getDatabasePath()) - .isEqualTo(new File(expectedPath, "database").getAbsolutePath()); - assertThat(cfg.getLogPath()).isEqualTo(new File(expectedPath, "log").getAbsolutePath()); - assertThat(cfg.getKeystorePath()) - .isEqualTo(new File(expectedPath, "keystore").getAbsolutePath()); + assertThat(cfg.getExecConfigFile()) + .isEqualTo(new File(expectedPath, "config" + File.separator + configFileName)); + assertThat(cfg.getExecGenesisFile()) + .isEqualTo(new File(expectedPath, "config" + File.separator + genesisFileName)); + assertThat(cfg.getDatabaseDir()).isEqualTo(new File(expectedPath, "database")); + assertThat(cfg.getLogDir()).isEqualTo(new File(expectedPath, "log")); + assertThat(cfg.getKeystoreDir()).isEqualTo(new File(expectedPath, "keystore")); if (verbose) { printPaths(cfg); @@ -294,20 +300,20 @@ private void printPaths(Cfg cfg) { System.out.println( "\n-------------------------------- USED PATHS --------------------------------" + "\n> Logger path: " - + cfg.getLogPath() + + cfg.getLogDir().getAbsolutePath() + "\n> Database path: " - + cfg.getDatabasePath() + + cfg.getDatabaseDir().getAbsolutePath() + "\n> Keystore path: " - + cfg.getKeystorePath() + + cfg.getKeystoreDir().getAbsolutePath() + "\n> Config write: " - + cfg.getExecConfigPath() + + cfg.getExecConfigFile().getAbsolutePath() + "\n> Genesis write: " - + cfg.getExecGenesisPath() + + cfg.getExecGenesisFile().getAbsolutePath() + "\n----------------------------------------------------------------------------" + "\n> Config read: " - + cfg.getInitialConfigPath() + + cfg.getInitialConfigFile().getAbsolutePath() + "\n> Genesis read: " - + cfg.getInitialGenesisPath() + + cfg.getInitialGenesisFile().getAbsolutePath() + "\n----------------------------------------------------------------------------\n\n"); } @@ -321,24 +327,23 @@ private void printPaths(Cfg cfg) { public void testDirectoryAndNetwork_wAbsoluteDbAndLogPath( String[] input, ReturnType expectedReturn, String expectedPath) { - String db = new File(alternativePath, "database").getAbsolutePath(); - cfg.getDb().setPath(db); - String log = new File(alternativePath, "log").getAbsolutePath(); - cfg.getLog().setLogPath(log); + File db = new File(alternativePath, "database"); + cfg.getDb().setPath(db.getAbsolutePath()); + File log = new File(alternativePath, "log"); + cfg.getLog().setLogPath(log.getAbsolutePath()); // save and reload for changes to take effect - cfg.toXML(null); + cfg.toXML(null, cfg.getInitialConfigFile()); cfg.fromXML(); assertThat(cli.call(input, cfg)).isEqualTo(expectedReturn); assertThat(cfg.getBasePath()).isEqualTo(expectedPath); - assertThat(cfg.getDatabasePath()) - .isNotEqualTo(new File(expectedPath, "database").getAbsolutePath()); - assertThat(cfg.getDatabasePath()).isEqualTo(db); + assertThat(cfg.getDatabaseDir()).isNotEqualTo(new File(expectedPath, "database")); + assertThat(cfg.getDatabaseDir()).isEqualTo(db); - assertThat(cfg.getLogPath()).isNotEqualTo(new File(expectedPath, "log").getAbsolutePath()); - assertThat(cfg.getLogPath()).isEqualTo(log); + assertThat(cfg.getLogDir()).isNotEqualTo(new File(expectedPath, "log")); + assertThat(cfg.getLogDir()).isEqualTo(log); if (verbose) { printPaths(cfg); @@ -351,31 +356,32 @@ private Object parametersWithConfig() { List parameters = new ArrayList<>(); String[] options = new String[] {"-c", "--config"}; - File config = new File(BASE_PATH, "config/mainnet/config.xml"); - String expected = new File(BASE_PATH, "mainnet").getAbsolutePath(); + String expected = MAIN_BASE_PATH.getAbsolutePath(); for (String op : options) { // without parameter - parameters.add(new Object[] {new String[] {op}, config, expected}); + parameters.add(new Object[] {new String[] {op}, mainnetConfig, expected}); // invalid parameter - parameters.add(new Object[] {new String[] {op, "invalid"}, config, expected}); + parameters.add(new Object[] {new String[] {op, "invalid"}, mainnetConfig, expected}); // mainnet as parameter - parameters.add(new Object[] {new String[] {op, "mainnet"}, config, expected}); + parameters.add(new Object[] {new String[] {op, "mainnet"}, mainnetConfig, expected}); } - config = new File(BASE_PATH, "config/mastery/config.xml"); - expected = new File(BASE_PATH, "mastery").getAbsolutePath(); + expected = TEST_BASE_PATH.getAbsolutePath(); for (String op : options) { // mastery as parameter - parameters.add(new Object[] {new String[] {op, "mastery"}, config, expected}); + parameters.add(new Object[] {new String[] {op, "mastery"}, testnetConfig, expected}); // testnet as parameter - parameters.add(new Object[] {new String[] {op, "testnet"}, config, expected}); + parameters.add(new Object[] {new String[] {op, "testnet"}, testnetConfig, expected}); } // config and directory String[] dir_options = new String[] {"-d", "--datadir"}; - config = new File(path, "mainnet/config/config.xml"); + File config = + new File( + path, + "mainnet" + File.separator + "config" + File.separator + configFileName); expected = new File(path, "mainnet").getAbsolutePath(); String[] net_values = new String[] {"mainnet", "invalid"}; @@ -410,7 +416,10 @@ private Object parametersWithConfig() { // config and directory with testnet net_values = new String[] {"mastery", "testnet"}; - config = new File(path, "mastery/config/config.xml"); + config = + new File( + path, + "mastery" + File.separator + "config" + File.separator + configFileName); expected = new File(path, "mastery").getAbsolutePath(); for (String opDir : dir_options) { for (String opCfg : options) { @@ -451,14 +460,13 @@ public void testConfig(String[] input, File expectedFile, String expectedPath) { if (expectedFile.exists()) { assertThat(cfg.fromXML(expectedFile)).isTrue(); } - cfg.resetInternal(); assertThat(cli.call(input, cfg)).isEqualTo(EXIT); assertThat(cfg.getBasePath()).isEqualTo(expectedPath); - assertThat(cfg.getExecConfigPath()) - .isEqualTo(new File(expectedPath, "config/config.xml").getAbsolutePath()); - assertThat(cfg.getExecGenesisPath()) - .isEqualTo(new File(expectedPath, "config/genesis.json").getAbsolutePath()); + assertThat(cfg.getExecConfigFile()) + .isEqualTo(new File(expectedPath, "config" + File.separator + configFileName)); + assertThat(cfg.getExecGenesisFile()) + .isEqualTo(new File(expectedPath, "config" + File.separator + genesisFileName)); assertThat(expectedFile.exists()).isTrue(); assertThat(cfg.fromXML(expectedFile)).isFalse(); @@ -474,7 +482,7 @@ private Object parametersWithConfigForOldLocation() { List parameters = new ArrayList<>(); String[] options = new String[] {"-c", "--config"}; - String expected = new File(BASE_PATH, "mainnet").getAbsolutePath(); + String expected = MAIN_BASE_PATH.getAbsolutePath(); for (String op : options) { // without parameter @@ -485,7 +493,7 @@ private Object parametersWithConfigForOldLocation() { parameters.add(new Object[] {new String[] {op, "mainnet"}, expected}); } - expected = new File(BASE_PATH, "mastery").getAbsolutePath(); + expected = TEST_BASE_PATH.getAbsolutePath(); for (String op : options) { // mastery as parameter @@ -506,9 +514,9 @@ private Object parametersWithConfigForOldLocation() { public void testConfig_oldLocation(String[] input, String expectedPath) { // ensure config exists on disk at expected location for old kernel if (!oldConfig.exists()) { - File configPath = new File(BASE_PATH, "config"); + File configPath = CONFIG_PATH; if (!configPath.exists()) { - configPath.mkdirs(); + assertThat(configPath.mkdirs()).isTrue(); } cfg.toXML(null, oldConfig); Cli.copyRecursively(genesis, oldGenesis); @@ -516,10 +524,10 @@ public void testConfig_oldLocation(String[] input, String expectedPath) { assertThat(cli.call(input, cfg)).isEqualTo(EXIT); assertThat(cfg.getBasePath()).isEqualTo(expectedPath); - assertThat(cfg.getExecConfigPath()) - .isEqualTo(new File(expectedPath, "config/config.xml").getAbsolutePath()); - assertThat(cfg.getExecGenesisPath()) - .isEqualTo(new File(expectedPath, "config/genesis.json").getAbsolutePath()); + assertThat(cfg.getExecConfigFile()) + .isEqualTo(new File(expectedPath, "config" + File.separator + configFileName)); + assertThat(cfg.getExecGenesisFile()) + .isEqualTo(new File(expectedPath, "config" + File.separator + genesisFileName)); if (verbose) { printPaths(cfg); @@ -532,25 +540,26 @@ private Object parametersWithInfo() { List parameters = new ArrayList<>(); String[] options = new String[] {"-i", "--info"}; - String expected = new File(BASE_PATH, "mainnet").getAbsolutePath(); + String expected = MAIN_BASE_PATH.getAbsolutePath(); + String expOnError = expected; // only info for (String op : options) { // without parameter parameters.add(new Object[] {new String[] {op}, EXIT, expected}); // invalid parameter - parameters.add(new Object[] {new String[] {op, "value"}, ERROR, BASE_PATH}); + parameters.add(new Object[] {new String[] {op, "value"}, ERROR, expOnError}); } // with network - expected = new File(BASE_PATH, "mastery").getAbsolutePath(); + expected = TEST_BASE_PATH.getAbsolutePath(); for (String op : options) { // mastery as parameter parameters.add(new Object[] {new String[] {op, "-n", "mastery"}, EXIT, expected}); parameters.add(new Object[] {new String[] {"-n", "mastery", op}, EXIT, expected}); // invalid parameter parameters.add( - new Object[] {new String[] {op, "value", "-n", "mastery"}, ERROR, BASE_PATH}); + new Object[] {new String[] {op, "value", "-n", "mastery"}, ERROR, expOnError}); } // with directory @@ -562,7 +571,7 @@ private Object parametersWithInfo() { // + invalid parameter parameters.add( new Object[] { - new String[] {op, "value", "-d", dataDirectory}, ERROR, BASE_PATH + new String[] {op, "value", "-d", dataDirectory}, ERROR, expOnError }); // with absolute path parameters.add( @@ -572,7 +581,7 @@ private Object parametersWithInfo() { // + invalid parameter parameters.add( new Object[] { - new String[] {op, "value", "-d", path.getAbsolutePath()}, ERROR, BASE_PATH + new String[] {op, "value", "-d", path.getAbsolutePath()}, ERROR, expOnError }); } @@ -632,9 +641,9 @@ public void testInfo(String[] input, ReturnType expectedReturn, String expectedP public void testInfo_oldLocation(String option) { // ensure config exists on disk at expected location for old kernel if (!oldConfig.exists()) { - File configPath = new File(BASE_PATH, "config"); + File configPath = CONFIG_PATH; if (!configPath.exists()) { - configPath.mkdirs(); + assertThat(configPath.mkdirs()).isTrue(); } cfg.toXML(null, oldConfig); Cli.copyRecursively(genesis, oldGenesis); diff --git a/modBoot/src/org/aion/Aion.java b/modBoot/src/org/aion/Aion.java index 11861fb0d2..a6fd02f021 100644 --- a/modBoot/src/org/aion/Aion.java +++ b/modBoot/src/org/aion/Aion.java @@ -57,6 +57,7 @@ import org.aion.zero.impl.blockchain.IAionChain; import org.aion.zero.impl.cli.Cli; import org.aion.zero.impl.config.CfgAion; +import org.aion.zero.impl.config.Network; import org.slf4j.Logger; public class Aion { @@ -109,32 +110,31 @@ public static void main(String args[]) { // from now on, all logging to console and file happens asynchronously - /* - * Logger initialize with LOGFILE and LOGPATH (user config inputs) - */ - AionLoggerFactory - .init(cfg.getLog().getModules(), cfg.getLog().getLogFile(), cfg.getLogPath()); - Logger genLog = AionLoggerFactory.getLogger(LogEnum.GEN.name()); - - // TODO-Ale: fix String[] filePath = new String[7]; // Log/Database path if (!cfg.getLog().getLogFile()) { - System.out.println("Logger disabled; to enable please check log settings in config.xml"); + System.out.println("Logger disabled; to enable please update log settings in config.xml and restart kernel."); filePath[0] = "« disabled »"; } else if (!cfg.getLog().isValidPath() && cfg.getLog().getLogFile()) { - System.out.println("File path is invalid; please check log setting in config.xml"); - // TODO set default location - return; + System.out.println("Logger disabled due to invalid file path; to enable please update log setting in config.xml and restart kernel."); + cfg.getLog().disableLogging(); + filePath[0] = "« disabled »"; } else if (cfg.getLog().isValidPath() && cfg.getLog().getLogFile()) { - filePath[0] = cfg.getBasePath() + "/" + cfg.getLog().getLogPath(); + filePath[0] = cfg.getLogPath(); } - filePath[1] = cfg.getBasePath() + "/" + cfg.getDb().getPath(); + + // Logger initialize with LOGFILE and LOGPATH (user config inputs) + AionLoggerFactory + .init(cfg.getLog().getModules(), cfg.getLog().getLogFile(), cfg.getLogPath()); + Logger genLog = AionLoggerFactory.getLogger(LogEnum.GEN.name()); + + + filePath[1] = cfg.getDatabasePath(); filePath[2] = Keystore.getKeystorePath(); - filePath[3] = cfg.getExecConfigPath(); - filePath[4] = cfg.getExecGenesisPath(); - filePath[5] = cfg.getInitialConfigPath(); - filePath[6] = cfg.getInitialGenesisPath(); + filePath[3] = cfg.getExecConfigFile().getAbsolutePath(); + filePath[4] = cfg.getExecGenesisFile().getAbsolutePath(); + filePath[5] = cfg.getInitialConfigFile().getAbsolutePath(); + filePath[6] = cfg.getInitialGenesisFile().getAbsolutePath(); String path = "\n-------------------------------- USED PATHS --------------------------------" + @@ -156,10 +156,16 @@ public static void main(String args[]) { ".' `. | `._____.' | ``|\n\n"; // always print the version string in the center of the Aion logo - String versionStr = "v"+KERNEL_VERSION; - String networkStr = cfg.getNetwork().toString(); + String versionStr = "v" + KERNEL_VERSION; + String networkStr = cfg.getNetwork(); + // if using old kernel configuration + if (networkStr == null && cfg.getNet().getId() >= 0) { + networkStr = Network.determineNetwork(cfg.getNet().getId()).toString(); + } logo = appendLogo(logo, versionStr); - logo = appendLogo(logo, networkStr); + if (networkStr != null) { + logo = appendLogo(logo, networkStr); + } genLog.info(path); genLog.info(logo); diff --git a/modMcf/src/org/aion/mcf/config/Cfg.java b/modMcf/src/org/aion/mcf/config/Cfg.java index 1cecc605d2..ecfeb42baf 100644 --- a/modMcf/src/org/aion/mcf/config/Cfg.java +++ b/modMcf/src/org/aion/mcf/config/Cfg.java @@ -22,6 +22,7 @@ */ package org.aion.mcf.config; +import com.google.common.annotations.VisibleForTesting; import java.io.File; import javax.xml.stream.XMLStreamException; import javax.xml.stream.XMLStreamReader; @@ -30,29 +31,6 @@ /** @author chris */ public abstract class Cfg { - private final String configDirectoryName = "config"; - private final String configFileName = "config.xml"; - private final String genesisFileName = "genesis.json"; - private final String keystoreDirectoryName = "keystore"; - - private File logDirectory = null; - private File databaseDirectory = null; - private File execDirectory = null; - private File configFile = null; - private File genesisFile = null; - - private String network = "mainnet"; - - protected String BASE_PATH = System.getProperty("user.dir"); - protected final String INITIAL_PATH = System.getProperty("user.dir"); - protected final File oldConfigDir = new File(INITIAL_PATH, configDirectoryName); - - /** @implNote modified only from {@link #setNetwork(String)} */ - protected File newConfigDir = new File(oldConfigDir, network); - - private boolean useOldSetup = false; - private boolean ignoreOldSetup = false; - protected String mode; protected String id; @@ -151,32 +129,113 @@ public void setConsensus(CfgConsensus _consensus) { this.consensus = _consensus; } - /** @return the base dir where all configuration + persistence is managed */ - public String getBasePath() { - return getExecDirectory().getAbsolutePath(); - } + /* ------------ execution path management ------------ */ + + // names + private final String configDirName = "config"; + private final String configFileName = "config.xml"; + private final String genesisFileName = "genesis.json"; + private final String keystoreDirName = "keystore"; + + // base path + private final File INITIAL_PATH = new File(System.getProperty("user.dir")); + + // directories containing the configuration files + private final File CONFIG_DIR = new File(INITIAL_PATH, configDirName); + private File networkConfigDir = null; + + // base configuration: old kernel OR using network config + private File baseConfigFile = null; + private File baseGenesisFile = null; + + // can be absolute in config file OR depend on execution path + private File logDir = null; + private File databaseDir = null; + private boolean absoluteLogDir = false; + private boolean absoluteDatabaseDir = false; + + // impact execution path + private String network = null; + private File dataDir = null; + + /** Data directory with network. */ + private File execDir = null; + + private File execConfigDir = null; + private File execConfigFile = null; + private File execGenesisFile = null; /** Resets internal data containing network and path. */ + @VisibleForTesting public void resetInternal() { - logDirectory = null; - databaseDirectory = null; - execDirectory = null; - genesisFile = null; - configFile = null; - network = "mainnet"; + networkConfigDir = null; + baseConfigFile = null; + baseGenesisFile = null; + logDir = null; + databaseDir = null; + absoluteLogDir = false; + absoluteDatabaseDir = false; + network = null; + dataDir = null; + execDir = null; + execConfigDir = null; + execConfigFile = null; + execGenesisFile = null; + } - BASE_PATH = INITIAL_PATH; + /** + * Determines the location of the initial configuration files ensuring compatibility with old + * kernels. + */ + protected void initializeConfiguration() { + // use old config location for compatibility with old kernels + baseConfigFile = new File(CONFIG_DIR, configFileName); + baseGenesisFile = new File(CONFIG_DIR, genesisFileName); - useOldSetup = false; - ignoreOldSetup = false; + if (!baseConfigFile.exists() || !baseGenesisFile.exists()) { + updateNetworkExecPaths(); + } else { + execDir = INITIAL_PATH; + execConfigFile = baseConfigFile; + execGenesisFile = baseGenesisFile; + updateStoragePaths(); + } } - /** Returns the directory location where the kernel configuration and persistence is managed. */ - public File getExecDirectory() { - if ((!ignoreOldSetup && useOldSetup) || execDirectory == null) { - return new File(INITIAL_PATH); - } else { - return new File(execDirectory, network.toString()); + /** + * Updates the base configuration and execution paths as is defined by new kernels where + * the configuration is placed in folders for each network type. + */ + private void updateNetworkExecPaths() { + if (network == null) { + network = "mainnet"; + } + networkConfigDir = new File(CONFIG_DIR, network); + baseConfigFile = new File(networkConfigDir, configFileName); + baseGenesisFile = new File(networkConfigDir, configFileName); + + if (dataDir == null) { + dataDir = INITIAL_PATH; + } + execDir = new File(dataDir, network); + execConfigDir = new File(execDir, configDirName); + execConfigFile = new File(execConfigDir, configFileName); + execGenesisFile = new File(execConfigDir, genesisFileName); + + updateStoragePaths(); + } + + /** Updates the path to the log, database directories. */ + private void updateStoragePaths() { + if (!absoluteLogDir) { + logDir = new File(execDir, getLog().getLogPath()); + } else if (logDir == null) { + logDir = new File(getLog().getLogPath()); + } + if (!absoluteDatabaseDir) { + databaseDir = new File(execDir, getDb().getPath()); + } else if (databaseDir == null) { + databaseDir = new File(getDb().getPath()); } } @@ -184,13 +243,12 @@ public File getExecDirectory() { * Sets the directory where the kernel data containing setup and execution information will be * stored. * - * @param _execDirectory the directory chosen for execution + * @param _dataDir the directory chosen for execution * @implNote Using this method overwrites the use of old kernel setup. */ - public void setExecDirectory(File _execDirectory) { - this.execDirectory = _execDirectory; - // default network when only datadir is set - this.ignoreOldSetup = true; + public void setDataDirectory(File _dataDir) { + this.dataDir = _dataDir; + updateNetworkExecPaths(); } /** @@ -201,9 +259,20 @@ public void setExecDirectory(File _execDirectory) { */ public void setNetwork(String _network) { this.network = _network; - this.newConfigDir = new File(oldConfigDir, network); - this.execDirectory = new File(INITIAL_PATH); - this.ignoreOldSetup = true; + updateNetworkExecPaths(); + } + + /** @return the base dir where all configuration + persistence is managed */ + public String getBasePath() { + return getExecDir().getAbsolutePath(); + } + + /** Returns the directory location where the kernel configuration and persistence is managed. */ + public File getExecDir() { + if (execDir == null) { + initializeConfiguration(); + } + return execDir; } public String getNetwork() { @@ -211,124 +280,91 @@ public String getNetwork() { } public String getLogPath() { - return getLogDirectory().getAbsolutePath(); + return getLogDir().getAbsolutePath(); } - public File getLogDirectory() { - if (logDirectory != null) { - // set when using absolute paths - return logDirectory; - } else { - return new File(getExecDirectory(), getLog().getLogPath()); + public File getLogDir() { + if (logDir == null) { + // was not updated with absolute path + logDir = new File(getExecDir(), getLog().getLogPath()); } + return logDir; } - public void setLogDirectory(File _logDirectory) { - this.logDirectory = _logDirectory; + /** + * Used to set an absolute path for the log directory. + * + * @param _logDirectory the path to be used for logging. + */ + public void setLogDir(File _logDirectory) { + this.logDir = _logDirectory; + this.absoluteLogDir = true; } public String getDatabasePath() { - return getDatabaseDirectory().getAbsolutePath(); + return getDatabaseDir().getAbsolutePath(); } - public File getDatabaseDirectory() { - if (databaseDirectory != null) { - // set when using absolute paths - return databaseDirectory; - } else { - return new File(getExecDirectory(), getDb().getPath()); + public File getDatabaseDir() { + if (databaseDir == null) { + // was not updated with absolute path + databaseDir = new File(getExecDir(), getDb().getPath()); } + return databaseDir; } - public void setDatabaseDirectory(File _databaseDirectory) { - this.databaseDirectory = _databaseDirectory; - } - - public String getKeystorePath() { - return getKeystoreDirectory().getAbsolutePath(); + /** + * Used to set an absolute path for the database directory. + * + * @param _databaseDirectory the path to be used for storing the database. + */ + public void setDatabaseDir(File _databaseDirectory) { + this.databaseDir = _databaseDirectory; + this.absoluteDatabaseDir = true; } - public File getKeystoreDirectory() { - return new File(getBasePath(), keystoreDirectoryName); + public File getKeystoreDir() { + return new File(getExecDir(), keystoreDirName); } /** Returns the configuration directory location for the kernel execution. */ public File getExecConfigDirectory() { - // TODO check different input - return new File(getExecDirectory(), configDirectoryName); + if (execConfigDir == null) { + initializeConfiguration(); + } + return execConfigDir; } /** Returns the location where the config file is saved for kernel execution. */ public File getExecConfigFile() { - return new File(getExecConfigDirectory(), configFileName); - } - - /** Returns the location where the config file is saved for kernel execution. */ - public String getExecConfigPath() { - return getExecConfigFile().getAbsolutePath(); + if (execConfigFile == null) { + initializeConfiguration(); + } + return execConfigFile; } /** Returns the location where the genesis file is saved for kernel execution. */ public File getExecGenesisFile() { - return new File(getExecConfigDirectory(), genesisFileName); - } - - /** Returns the location where the genesis file is saved for kernel execution. */ - public String getExecGenesisPath() { - return getExecGenesisFile().getAbsolutePath(); + if (execGenesisFile == null) { + initializeConfiguration(); + } + return execGenesisFile; } /** @implNote Maintains the old setup if the config file is present in the old location. */ public File getInitialConfigFile() { - if (configFile == null) { - // TODO-Ale: may want to consider exec path as well - // use old config location for compatibility with old kernels - File config = new File(oldConfigDir, configFileName); - - // TODO: read mainnet config when ignore set - if (ignoreOldSetup || !config.exists()) { - if (execDirectory == null) { - execDirectory = new File(INITIAL_PATH); - } - config = getExecConfigFile(); - if (!config.exists()) { - config = new File(newConfigDir, configFileName); - } - } else { - useOldSetup = true; - } - configFile = config; + if (baseConfigFile == null) { + initializeConfiguration(); } - return configFile; - } - - /** @implNote Maintains the old setup if the config file is present in the old location. */ - public String getInitialConfigPath() { - return getInitialConfigFile().getAbsolutePath(); + return baseConfigFile; } + /** @implNote Maintains the old setup if the genesis file is present in the old location. */ public File getInitialGenesisFile() { - if (genesisFile == null) { - // use old genesis location for compatibility with old kernels - File genesis = new File(oldConfigDir, genesisFileName); - - if (ignoreOldSetup || !genesis.exists()) { - if (execDirectory == null) { - execDirectory = new File(INITIAL_PATH); - } - genesis = getExecGenesisFile(); - if (!genesis.exists()) { - genesis = new File(newConfigDir, genesisFileName); - } - } - genesisFile = genesis; + if (baseGenesisFile == null) { + initializeConfiguration(); } - return genesisFile; - } - - /** @implNote Maintains the old setup if the config file is present in the old location. */ - public String getInitialGenesisPath() { - return getInitialGenesisFile().getAbsolutePath(); + return baseGenesisFile; } public static String readValue(final XMLStreamReader sr) throws XMLStreamException { diff --git a/modMcf/src/org/aion/mcf/config/CfgLog.java b/modMcf/src/org/aion/mcf/config/CfgLog.java index 94be49f547..8fe2ef71f1 100644 --- a/modMcf/src/org/aion/mcf/config/CfgLog.java +++ b/modMcf/src/org/aion/mcf/config/CfgLog.java @@ -158,6 +158,11 @@ public boolean getLogFile() { return this.logFile; } + /** Used to turn off logging in case of incorrect configuration. */ + public void disableLogging() { + this.logFile = false; + } + /** Method returns user input folder path of logger */ public String getLogPath() { return logPath; From 0d554645fdea9446af2955eee4ebfc8eb70f4458 Mon Sep 17 00:00:00 2001 From: Alexandra Date: Thu, 27 Sep 2018 18:17:36 -0400 Subject: [PATCH 27/31] added option for showing error in case of vm issue --- modAionImpl/build.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modAionImpl/build.xml b/modAionImpl/build.xml index 8f3dea093a..1867a6d295 100644 --- a/modAionImpl/build.xml +++ b/modAionImpl/build.xml @@ -96,7 +96,7 @@ - + @@ -125,7 +125,7 @@ - + From 60ecad8bc64eb75897768d0f3c2a0820eb415555 Mon Sep 17 00:00:00 2001 From: Alexandra Roatis Date: Fri, 28 Sep 2018 17:55:10 -0400 Subject: [PATCH 28/31] corrected try-with-resource calls --- modAionImpl/src/org/aion/zero/impl/cli/Cli.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/modAionImpl/src/org/aion/zero/impl/cli/Cli.java b/modAionImpl/src/org/aion/zero/impl/cli/Cli.java index 1cd499e8aa..98e643444d 100644 --- a/modAionImpl/src/org/aion/zero/impl/cli/Cli.java +++ b/modAionImpl/src/org/aion/zero/impl/cli/Cli.java @@ -566,7 +566,8 @@ private void makeDirs(File startConfigFile, Cfg cfg) { */ private boolean createAccount() { String password, password2; - try (BufferedReader reader = new BufferedReader(new InputStreamReader(System.in))) { + try (InputStreamReader isr = new InputStreamReader(System.in); + BufferedReader reader = new BufferedReader(isr)) { password = readPassword("Please enter a password: ", reader); password2 = readPassword("Please re-enter your password: ", reader); } catch (IOException e) { @@ -626,7 +627,8 @@ private boolean exportPrivateKey(String address) { } String password; - try (BufferedReader reader = new BufferedReader(new InputStreamReader(System.in))) { + try (InputStreamReader isr = new InputStreamReader(System.in); + BufferedReader reader = new BufferedReader(isr)) { password = readPassword("Please enter your password: ", reader); } catch (IOException e) { e.printStackTrace(); @@ -667,7 +669,8 @@ private boolean importPrivateKey(String privateKey) { } String password, password2; - try (BufferedReader reader = new BufferedReader(new InputStreamReader(System.in))) { + try (InputStreamReader isr = new InputStreamReader(System.in); + BufferedReader reader = new BufferedReader(isr)) { password = readPassword("Please enter a password: ", reader); password2 = readPassword("Please re-enter your password: ", reader); } catch (IOException e) { From fcdc51166a317bcdbbf9f73b73ac613818daee6e Mon Sep 17 00:00:00 2001 From: Alexandra Roatis Date: Fri, 28 Sep 2018 18:04:55 -0400 Subject: [PATCH 29/31] refactored network identifier to chainId --- .../org/aion/zero/impl/config/Network.java | 38 +++++++++---------- .../aion/zero/impl/config/NetworkTest.java | 2 +- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/modAionImpl/src/org/aion/zero/impl/config/Network.java b/modAionImpl/src/org/aion/zero/impl/config/Network.java index 63643847aa..f7ac622d05 100644 --- a/modAionImpl/src/org/aion/zero/impl/config/Network.java +++ b/modAionImpl/src/org/aion/zero/impl/config/Network.java @@ -36,17 +36,17 @@ public enum Network { CUSTOM("custom", 0); private final String name; - private int identifier; + private int chainId; /** * Constructor. * * @param _name network name - * @param _identifier network identifier + * @param _chainId chain identifier */ - Network(String _name, int _identifier) { + Network(String _name, int _chainId) { this.name = _name; - this.identifier = _identifier; + this.chainId = _chainId; } @Override @@ -54,8 +54,8 @@ public String toString() { return this.name; } - public int getIdentifier() { - return identifier; + public int getChainId() { + return chainId; } /** @@ -84,26 +84,26 @@ public static Network determineNetwork(String network) { } /** - * Utility method that determines the correct network based on the given identifier. + * Utility method that determines the correct network based on the given chain identifier. * - * @param identifier a positive integer value representing the network identifier + * @param chainId a positive integer value representing the network chain identifier * @return the network object corresponding to the int value or null when the value is not * mapped to an object. */ - public static Network determineNetwork(int identifier) { - if (identifier < 0) { + public static Network determineNetwork(int chainId) { + if (chainId < 0) { return null; } for (Network net : Network.values()) { - if (identifier == net.getIdentifier()) { + if (chainId == net.getChainId()) { return net; } } - // custom networks may have any positive identifier not already taken + // custom networks may have any positive chainId not already taken by other defined networks Network net = Network.CUSTOM; - net.identifier = identifier; + net.chainId = chainId; return net; } @@ -113,17 +113,17 @@ public static String valuesString() { } /** - * Generates a custom network with the given identifier. + * Generates a custom network with the given chain identifier. * - * @param identifier a positive integer value representing the network identifier - * @return a custom network object with the given identifier. + * @param chainId a positive integer value representing the network chain identifier + * @return a custom network object with the given chain identifier. */ - public static Object getCustomNet(int identifier) { - if (identifier < 0) { + public static Network getCustomNet(int chainId) { + if (chainId < 0) { return null; } Network net = Network.CUSTOM; - net.identifier = identifier; + net.chainId = chainId; return net; } } diff --git a/modAionImpl/test/org/aion/zero/impl/config/NetworkTest.java b/modAionImpl/test/org/aion/zero/impl/config/NetworkTest.java index a61535d168..c21dfd5de8 100644 --- a/modAionImpl/test/org/aion/zero/impl/config/NetworkTest.java +++ b/modAionImpl/test/org/aion/zero/impl/config/NetworkTest.java @@ -73,7 +73,7 @@ private Object intToNetworkMappings() { List parameters = new ArrayList<>(); for (Network net : Network.values()) { - parameters.add(new Object[] {net.getIdentifier(), net}); + parameters.add(new Object[] {net.getChainId(), net}); } parameters.add(new Object[] {-1, null}); From e4019454236a13ba8aa1ebb7379f93de2ea5ad58 Mon Sep 17 00:00:00 2001 From: Alexandra Roatis Date: Fri, 28 Sep 2018 18:28:49 -0400 Subject: [PATCH 30/31] added the picocli library to gradle after rebase on master-pre-merge --- lib/picocli-3.5.1.jar | Bin 228881 -> 0 bytes modAionImpl/build.gradle | 1 + 2 files changed, 1 insertion(+) delete mode 100644 lib/picocli-3.5.1.jar diff --git a/lib/picocli-3.5.1.jar b/lib/picocli-3.5.1.jar deleted file mode 100644 index 26f45e6498eb45652067dab2314e087d23e416e4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 228881 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

    syEwwzG8pkCa!%24*HFd= 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

  • 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 Date: Tue, 2 Oct 2018 17:46:09 -0400 Subject: [PATCH 31/31] bugfixing broken functionality according to the list of scipts in #650 --- modAionImpl/src/org/aion/zero/impl/cli/Cli.java | 3 +++ modBoot/src/org/aion/Aion.java | 3 ++- modMcf/src/org/aion/mcf/config/Cfg.java | 12 +++++++++++- 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/modAionImpl/src/org/aion/zero/impl/cli/Cli.java b/modAionImpl/src/org/aion/zero/impl/cli/Cli.java index 98e643444d..cb0299723a 100644 --- a/modAionImpl/src/org/aion/zero/impl/cli/Cli.java +++ b/modAionImpl/src/org/aion/zero/impl/cli/Cli.java @@ -143,6 +143,9 @@ public ReturnType call(final String[] args, Cfg cfg) { File configFile = cfg.getExecConfigFile(); if (!configFile.exists()) { configFile = cfg.getInitialConfigFile(); + } else { + // marks that the files were read from the execution path + cfg.setReadConfigFiles(configFile, cfg.getExecGenesisFile()); } // true means the UUID must be set diff --git a/modBoot/src/org/aion/Aion.java b/modBoot/src/org/aion/Aion.java index a6fd02f021..7281620beb 100644 --- a/modBoot/src/org/aion/Aion.java +++ b/modBoot/src/org/aion/Aion.java @@ -80,7 +80,6 @@ public static void main(String args[]) { */ ECKeyFac.setType(ED25519); HashUtil.setType(BLAKE2B_256); - ServiceLoader.load(EventMgrModule.class); CfgAion cfg = CfgAion.inst(); @@ -96,6 +95,8 @@ public static void main(String args[]) { exit(-1); } + ServiceLoader.load(EventMgrModule.class); + try { ServiceLoader.load(AionLoggerFactory.class); } catch (Exception e) { diff --git a/modMcf/src/org/aion/mcf/config/Cfg.java b/modMcf/src/org/aion/mcf/config/Cfg.java index ecfeb42baf..93b400ed23 100644 --- a/modMcf/src/org/aion/mcf/config/Cfg.java +++ b/modMcf/src/org/aion/mcf/config/Cfg.java @@ -196,6 +196,7 @@ protected void initializeConfiguration() { updateNetworkExecPaths(); } else { execDir = INITIAL_PATH; + execConfigDir = CONFIG_DIR; execConfigFile = baseConfigFile; execGenesisFile = baseGenesisFile; updateStoragePaths(); @@ -212,7 +213,7 @@ private void updateNetworkExecPaths() { } networkConfigDir = new File(CONFIG_DIR, network); baseConfigFile = new File(networkConfigDir, configFileName); - baseGenesisFile = new File(networkConfigDir, configFileName); + baseGenesisFile = new File(networkConfigDir, genesisFileName); if (dataDir == null) { dataDir = INITIAL_PATH; @@ -359,6 +360,15 @@ public File getInitialConfigFile() { return baseConfigFile; } + /** + * Used to updated the initial configuration to using the execution configuration files when + * reading the initial configuration from those files. + */ + public void setReadConfigFiles(File configFile, File genesisFile) { + this.baseConfigFile = configFile; + this.baseGenesisFile = genesisFile; + } + /** @implNote Maintains the old setup if the genesis file is present in the old location. */ public File getInitialGenesisFile() { if (baseGenesisFile == null) {

    %`?<{Z@Oj>kJ$XNP83V3M!3l8Uz#Zg($Q`t%JJ*2|{+>VMpD< z`HJ*N;vsM-xa<}n8cJ;XGSo54E0*?FpKoGjCR8ZkAcc+uy^l;Wi1|fTJ=-J5HdQYL z`OVpR9PbtKmI}M0T%c*K(IfV%Yyyj83nA%!W{7vZe`8o(59xQmHJ>rp(cWT z;T%jsZ)OBY#pb?-W-d*Qn_)|HXz(i2)o=MJCZd|CRgCr%5W;jt@K@}LrYXsfl5J0D z#Px2hyGP9>@5sT3(xYm_QAQjQ{FtI#tIget($#tZoto6v$)fqUML7z0k)c@n2xq=* zE>q?D>Ck!<2a`&^thJI$<2r<==)W59v-0HWxhj9;*<9%9EX>(Sgf6OnXccL+iSUsw zp|=clp9zoc3__6}Xt*Ov){8ul;_ZaIAPW6LjZ@elI)RfP95CM%1t3Ef%b7VWQ;1e?bQMwV9!!!z0+#nD$M~YdeMknNWf4$p1B?D2${{)BF{&o z02=(f2m0`f1!=9#CyX+1aode}$hx3pe>feedCQ>t(O*f#U?LdI)xv7{3%<}zfkR3^ zsdp}N(M24BMjgRzw!y+FHEs%4B++$QFJ^pP0(uQGE4eT_rHE;Ah;T{;flTx^ivD_i zhQ(_0Cz(;D@x9=?%SWE5OF|JY-W3r!NvgFu^infBsq9rhl4>~!r!j~j*KN@=e`2 z!P#_~hs}$rb=+A8qm^C5o5I1kVZ>EdxuxUSAF_Pv*s=+nfKK3>5&38}@)N5kVTqv< zF=<=fFn1d+@VERCAWD*y8yl9((oZ>QVMzZ7|BL_dJz+U2l+f;)JX_ zthMV6`I?w|5VMh&AOkxT3bFq&?Byq)m5v3VOgF0T^wo}baEXLmrI;YW8mRHtX z;X@&Fg3`R>J6~ynQ+gIW;1yO$-L3y&*kvz7-LT>&vVrMC=rDYms|{Th%##_}ix5m4 zTqeWYnv0=SpL{tB5Io7$P5cW}ij-<%3+M8kPo=7VP^wZ4ROU2YCj-od>dj0(+Ka;l zD}aFsqk^G3>bdc>ef+VrF*z72j1_9`#QnreP%VgU-inK(?-PySbLuPH7pi!{wT13X zrHA4yQdskYx#R0;TlM3wufhzbZsNcQm29?9|MjpLx#5RPT1<3o9}aiq68WTuS3Q-T zA_&Jzpyqm0go9Bqz;Ea9QkUZbc7e@v(Fi-d5N&s-cyz_1h^dK4eRVO`iG{#U+0(*~ z=5Nc>MTG@*M*Uq&qB!8)@9TdlSa@;wU9Juf?s4-6zoK8%=Gx87I}N5%?U)^%>40KB z$}z9TRM<@txLZI+5~H_H>#sQWD0p9dn-|^aNv;KEIXnKa**>tUu^2!nedT<9HJo)o z-rEg*{B2sQSgL1fOO=@Db6GXD5{7(pC6w)H> z3K5C*&f68vH0Xi>kV4XhEq>At$%fYb0aJ|}F@tMD1{jMXm%5u~Qnc9L_3vV<;xQhl zL%6VWqQ_l!eOy^0rL_$qaJ?#tX5#7c`TAbSMit(T(Fo_|x;_jhiK1_Rg@$Nco{tqb zK~1eL$<0c32R^367_4$qDb1X-P-tD*(G58NaOxS-5x^LaPc<-}Bs#bW;PT`MNu6rCfy^OE&VAg|^=ot{`GomF}yG}tQ*$6dJ+gFSQu{E0Z%}qA;@MQ3P z^roc95zt%R(GSzfu|yhCDOjq+VoN?Q&9_CBf;U(BL?jyynefDndm(LLdD68p(qa|iOHTlE1>=z7Ani2S&Ra_e z(0!GtnRXEiAttm*7(KFQDAiJul0|b^8bmr9_cX|vk~Ro;ASm~B%!!PaOK6!!L+LWr zy>`r1Q5E7qcQi<-S5s0Zk!Y(2fxpvxqD;1C-n)@_b7=P24hL12%wZp_YAsgX+um`` zs=5x-2KI7~q~B~5-*>)pbNo?d*F5HY!RGiz>6AZyCRcQJ;pWsdC2eHpfV_KmZpo?c zZ=d9$D8Lk`z7Gg*dF_SC=KYJi@Y3r1h5e-+eZBB1c;S@IJJC{LmYpMXyuBZr2RS|p z>f96Qdg{fxUD$|rKC@cxMVsmxq0=IM%)7%o z_oB$*o55RnkE?h`^<6&XTXKIK)^YrD&+*Of3usCRPR_vz3G!FBD23KQ(glaRM<|wd zb|2*7<)>kkibb8>r@TOrda2~-Owum7Up~3Ce0Y-Zc<12mk%?8E-^YA-0p-?_jJ2KH z&-`$Y{!@J4Dh~N)=lbMByP?r@6(-wSU(@*u$1{%qkRR)MVO8eoSN3?$b-XP|_xLAdW<8B;n93x1o_Bd0ISFZ?kEYXU|xU`8xA z`6mSlRf*9Zp1nWDbX={84m8k>)1$>BaA4NW-q|f?K0(HGb}n2vk*T&8g06Bhn)ojb z8&FN-qGunjAJ0i2fQ1qAi-#QFgcTT(vU{yG&pVf9F}_p>CXd<+J>nRH3ny zab%K24M*>i2FBn6Kv3g-s~TNbI+MC;?|JY4oP5+`HM`+<=Hl=!zM6hGW`9S2XD__d z6X(hY(0=v)$(uWFBe2)BMF{X!D5PcjqRsx!q+WdS9pR@gX4N%`vNWVwd<8^yN|rB> z>zj=(t2mogrscLsdLbJX+%5N^04Xa{B+r!qQ92VA&tF>~wAF~*)sGa+JY^v@?$9WD z;_q89(5B``SR`ENA@d;ilP_l?W4=N=v*2w`>YZ&Mxmt#HZvLYYA{nVQy?1g3&_~d` zdC|{?)@C|{)4>5ZOw167l}sT>j$U0ExX_D6&@%HTpgO)t=xiM25LF|^PfwvZ142Gr z0I3jK*3a}vrkx&BAy-n_?_4aBUebutu!Z6rR5L*^(>Mc3{y2suwGiLIpqgGaxpgPQ3bU!LkV}<4=FyXOWg>jV zx>%f8a!&1RZkdXC)|QvJRxH1)*m%Kt5vWDTYoEGF%p|34lN#dP!k+ol=3G5>Ued_S zcU@euz;w2M(ioF_18xPMMMW|d*XZ;gX1kl}V3?R_C%MYU+#hv~CndEvpAc|Me$A0D zuK>g|nwJ^rwHl5KSEE1QYd`31@5E;;0nPhR9u!n1YMr|JCw1)8YIhXXzQJDF@U+0N zOEOco|K%kZcE>24zZI3`@~Da|t8VKbRLLrZln1^u3^N?mfx{Iq52>lHPZV;fvSOrb zrG5I;LKy)2W2Q}I^ENG4IqTnh*WY+ewoPYNe{J`supZ79@4QyxKOlwmI3gQBgBH@d zci!9(xz>(9nay?`GLupwH|HLVQ(0mXL|zab3sZ}c`5|h=RLgo^T9U$y-n=IvRxk@D zT}ZHh4!&i#N=q=ZkiKS!kV&C<$U>AD0E~2AjD*6`br803ARIb`_>}o6{M0S2( zw8Kr_lrzcVr%eGY)bgR{3mNW|52`#Mz3gY}mg!|Frhm?bW@{)s*8TOD*;}^tLsCHg zH2CvOi}qsNjZ(^JbLu+TPNyGtEq#3RZEnh@W@>yuBsXwSl*2HR)EFzV!(LONe{5@% zR!G61de?a`R08TGr+FcZ2cN_WAlmf_dx*NM$fYBp%jg#9%}tHg+0viIldmcPRu9gp zy?Esl^>u^yGE$v)W$o;4pUnxDWn~tgAVNgay3hFDqN1>SAeVFfJ%PSZB1VJdo*gs$9!oyTaX5Nvh!aaK8f=Mhr>e|TN}fJ?lm32h^R{gx zvfk~1sh=)dSCU2MzwId`QIsme;JnjV4LX3FZ|{N|6*(79?eUYi``JvC!-|Che0re? zTi9>WTyRea<`>-a02yi%VB|mggqeVs=Rk8G;Lw50G#Y$S;7&3TmI#XbNOs33%hF5!?|qqujC9Y4j!Ot%pk4 z!T5W-@(LVqk!Vai^Pg?VJjDKLgp})casRs8m1@C~6h*6Eg=j(GOOi3D^aD?;lupym z0~+>HAGA49tcQ&Nw;H7xsVt-5Fo1p7g#C2!51^GuHH`e~?D4x+~2-i}%-_*{=f3wF{j zFt2MPZ?wQGU7S5xi~&NymqF9;jxNNF;1vOclR8L8-n|l_3=>+)FA8K;qwyX=5x_ex znUJy)E-2u_xi{`8A{%CO3$}X~5YyY6>g}x#Wv-H}nV?umH>|pUD`@XMtS`|0SN=&r zKJ>fZ%H5{j!pB%QT~@CST5q2HgAUrL9%s}*9~tO-et||cg)*h&Vz*rR4x?-iWf%*M8KuApzzF+=K)XxtZJp+fn*ZZ4pDsUovl(6aV=q-pPBVUUuv~win!*Zs zNi*6ti`q58OO3V|egxJaRsLe80AwEh%`m_##(vWKZx((Ks()TV63A?sG0JIrwf##( zWw#|5ABz^`8nf|*ojYkJ`M4ff>r@n-dRh3>_MUoO(yF3haJdCjJb?x;SfKsedQ>0U zag4nHw(*Qx1g2f$Fv&8;_Cy1*7Gipw$l;P29a#akW!|)N`To9rHfbjZUy_-Nj|?;E z0T)7cn5!g-`>!hjaVcndB+N=YzcAJTHH=ZETvl-J{bLVTMO?y*XT4g`KYvGGP+L`s zpRzeBy-1O>iy3)a5xp^paY64|8J1=eO)>Azyr?|?t(O(7S6}<3*b-QB1(3{p0-$5@ zi)RMI+4KOoX$|@Wk9>xaIuB7g4=`}QTw87*n7$8`K+m5oGTS2lCr2udj{Lm@#ir$NzKk9YaT|`(CUzcmZb+1Yr&g!EYqVJT4QU%oAoNWoR-6q z>YR}3oS5pIzt!JC8(eIrsajznH7cv zdKh5d$o8%Et-{0L`IBsV(c*U{#)oZ$gG7L>d^y5R@Jo%fO7}`Kzy5%G!SSUwG12F_oo-oRz-N#|}iasd5ry zG=ZeMrwrXeIgyOm;*Y$qe3l3^r{P5^;$=!FJNJ&p(5aY*auHkf%US7@v7j4V06MvX z9&pT)NKj|yy|C$mZsGOxo4^#4oNzr^KV*VF9|DmNb-n9B*YpK&4(`;w-Ge6fUK`YA zUwGz4=!{EEzEV(_e|A3cODc-gB3fhI)r+ps{Q8aU#zJ_(ZDku0Qnpwa=7G!1Wbc#9 zZNg_13t8dXKq^HqRqPCQ)NP;E!Ua_gD|n)>W{O~7m`mGOznyt$Dx2ZwqgOW_vSTJJ z3%UM6)GSmDSNX_^uOc6CKJnNzRaO0z$ey<;)k8$JU^>hrZ=n&^fb1}ad!|y|`$jAIlFg1>7SfjEq0ayd!VPH5 z4I$)Crfdf@Kf*Qu+>24_P*V?M8{+=Od}g4PKP(r}-iL&!epuvH^ceRNDNB;pE*QlB-&s!7Fw+z{hV={aJ_#+J|uup=sk-r`3E z>F&a%oN6^)X=Q#%pdQWcC6p8BnRG(vnezy5&iE?1=ebOfH_Mb zrLkMkrde^EMjH%R4#IH3BP?Gz3K z^Arz6dqvCwoa=DT$$3G}$!&){1o|XA z=V(F;Uy$jtbituNER+_S>!Nf+$2xG9?lJSCx4}wpF6;cafiFKy!tOM8G<-Vcwm6(1 z*a%i-S7ha#1%*#d)i7ZC>mRUEQg(sf+@YY@n$#~@)GiBDIF+L50O5*J z>z7x~`^qAMPRR>|uA)t&URW7KxC9}#m_8RQ=J&%kdHk`HJ$yVu=<2qu?m3^bdzM!; zfjs^Hnm=*6h6*SSj9JQoZwO@yk<$nlh~X{w)93v_nz1!pgADJ17eaC`0w zAXW8ZJ@C)hTML!D2FP|pzLmKOhI_=~*}%Wt(EjZ!L!eE<11u7{;}@h32-Vobun4y- zqvsJm<$jXH-^mZajvb5ve3p-*yAb$P4v`D*eU{Ge3BLimM^mX*$z!3dU~yTYQHkT1 zMdqmav>VlFOUjIWxMJ|)3vi40fhByPNx>4hm)8QR>i+)o2Fmb7!tjLxCBGe_e?}Vw zaHOA!FoJ63sfU&z)p8DRR0K>nG`E6g^t3DhUe`-ft8M0q?S`pH)FlDF>Z)F*K2 zOYkY)I#&k1@h45?@9C?e0cLO4 z_oA#diH1=xbpw|}JUuf6lbNX&}WM>W!~C(7)T}ppA$hH5bp}_4HAw=tG}1SsAnrS?JjZJY9MP|$;K6-Ke-jH z%f_gbj8T3Wvlx5lFjin@mBk1uHPe(SXcfzUPOhz#G5)Gn_+7GTSIFno(f{pfBEBaL z^x}%BSNOVE^5>V{q0I_SsG2Qf>KDrDXD~#X-)C%&zAk0x<_SFiA4q5QtbpGpxCNs%?XkR`H9q3oh$XD}Fs!C1?Zy{uWY?~&{gS+gc0 z*|+SnWTz}q{qMAWKchjv|MRKF>+>@2bMLw5o^$Sbp6A|9>`Mmx5K@HnX~g4mrC|f5 zBWpJElrv+OI_iZoda>;vn=SB}E=c#hH|w0c-s5y$yPVJu7AUbaEkJbVE@|}v#gq7} zY~EJ!44f?mB$JNO5I1A8tR*&|L}P3T;q$bk0=RFNvfTZIGe?iLKH!tqYW&vtje*{Z zVe8UX(8HBSIXU_s$WK{|)wi5r0gHYuxOO4={gp4RK8=Uo6m3))W2ufpzMdv-<&U*l zuRio9Gs3>wqvi3?;1W3h?QIDae(bRH_$g#|`pLc_90SgT={~p@?V&_Mp{kGd!j~G8 zjY4><7^4g};cE=ew@%m$`e!@WJ(WJ~?BM7W;-k0{;UFt)Uvppid7V&>MnPy=z|f&# z-p18)jtKt7vI9r%a(GHd_Be19Rldacu5=eBuD$424kLB!kXy-1<@A~y^drf6GI_YN zu5d%=RQI_j)`W95o|5q~=Hy&8*G@1PV8;-6FU5^X*B|%2o!BmSGqcY0pk=l?jOJ+w z&6mKI`U%bZE9@341!K9}_+g@M+^rI1+^5N~+@6^h9v*ekd_k6X;s~23maG#N&wIHP z0>i{LyiP2n^HrfMhFE56;_S{Jo$p7uZH+)|&JWivZ@hNt^;%Z$ap{c&WINwD<%^^bVKD^Jbp9l39my+`%O|;+FAd<4MP})ET=JIACMvLk4PCp+J~gnKJJA&J(!=rxAqJ;{(xt9Sl!@#BDV zZqI`rJa5r*A-VtI=qN>8K!>JYf!9GiV>hpYD&NO=&)`WkhwkQA>FDc=of^H1|AuKU zMfMi%3T@Gar<2}cHr8TfIN|cPGe+!c|)J~IE zCF7H}Gv;Y(^S+^+ohK6n3pa5v4A=0!Wtw*Di;Oct=BMtfRdTgC7q3l4y1CrHEY>OUUBvTx$4p`F2t`cC zVZRpb#klKBAg`_%=&w&^xgc5Sd_~T&0@i&w+Q_kD*}JEJi<{je7~&qBA}6KsVQ3CF zos-&4XGoij%G^PL%KQa)O^vX!9u-tS@U^f8sSZWzkTG|8|B}ZiQ@dlVhq1#MvEzty zBP6S0OiB?@hoVuRxxTs7o4CqSF{yLcCYirwd^L@4fhpr~Cvx#CsAM@Gt%F!8nf?4z zlBMwm+NHr^mOT0luGM8^X?yY2af2`exE9$|Y~AGcx29yK^~=Lgld1JgW3`f}tqHd| zzXmi4uHB3Ve`S-HvOl?YmXB8*En4Ir;|aCH4>L6guC?O4eU`An>?E6k00HHM^jNvV zm#mW!Vfn8`PCmA3au`rZbV!%W8@c@%l)lrl z>eKKHYtn>y_XK{?^CozyY~9Qq6A2^Q=5506q(XFYQB3*P6BNk173R8w*l zx?DWT!?ao4`+6~*?NYJHRuoT2f=hlgz3XG?Z+bV|G%txuw+*Om)XOKHuDI$o&ym&J zx=BE)F#Psdw_)CxiYhBpR2Bsb{3y+Z=UsCf=O~tFoioF_7@c(^3*N)Z)ATQtn-xqP zXOVTuB{H7XZI@WX*?u}_5m`<+q#OT5czqURp>&@;l84VwFN@Y?P1DIqm^smk_I+J@ zz<8U}>^>>&|=7#FiD$+0cG5*+Cl`7rp z?V3SjCMq>){6>8ga|vs;4S`Vok>h0^w-j#h8BTkdo+F*G^Q3rA)5=AA_xxH_g1Os0 zOEb6P%g4*jJ?2~7<$1aA?2gum-g+=~cuU*!tIm_>8)ZLa$?HA=1Sn zPEkcwR&bW^^E<~!)8#GJ1#55J;yHW#-ll6>FkH3!7H?SG6f&q!TgDT{!*KiH%7k9K zL>7re?BiJWnONI|%R`$K3>*;M*^C0(GDKpdvHjZGyQnNmp@j4EbB)=nj!m1y@m=p7 zDyyPjwQHOoI8@G*UC*8^!cb_JWF72@!*J;wM^T!g&{o#bvXdwCleNyZ%Z}#qhYb6W zZ{QKBPJ1XOl8Sl>Z{V-Ca(zEL>DsVO9qH<}xfS{S0EU$L6C*CN8l9j{Xr! z_z6N>>5I`6$WutdNu6fiW-Dbk{pQuG*6qlnrdvFet4`B|J$gF#z7B9LerPf9iUhkp zsCqh5sU&U#@rZ0MI)pyh}i+Gi_`I>>t#YqEBu_LSbkwLXi!jmUC6Yg))H!~Ty zu+_x4JMxa*J+qo|{o3)#jwQ#_lO0{cY95d1 z0B24)l7PML|Niw~hZS6bAfYM-)-cOoJ3z&$IXX~4$-c`!YlJg~7RrYf;wH}o^OeOs z)W0DU6vk+7&UsoshFHn+SybA-=(kqq33id&!pQfzxBd`Q^HB$VVlme&RFABeOF8A%eL~#Fw_(^NBk8A^sBbHzIJ(w$ax5 zz8+%uqI~yUk^5tj#_Rk!v;3`y(#L zyMCp++me?T9wIL;tt>}yCW44ZCG9y>7|y5?eAu^+&i`6FR~3#+)|oixav)e)N+ZnV%6<8Rx>CpUShqVbiS6ym*l4L@scK%i(=

  • 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

  • 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