From 564bffcba0b882319fd1f5c767a3804106d9ad1d Mon Sep 17 00:00:00 2001 From: Nicolas MASSART Date: Mon, 25 Feb 2019 23:26:32 +0100 Subject: [PATCH 1/2] RLP encode subcommand (#944) Add RLP encoding subcommand for at least ibftExtraData to be inserted in genesis file. There's only one encodable type for the moment, however the `--type` option is already there with a default value on this type. We'll add more possible types later if needed and the current type will remain the default one. The file output is optional and it writes to standard output by default, same for standard input or a file. Tests and doc change. --- docs/Reference/Pantheon-CLI-Syntax.md | 74 +++++- .../java/tech/pegasys/pantheon/Pantheon.java | 1 + .../pantheon/cli/DefaultCommandValues.java | 2 +- .../pegasys/pantheon/cli/PantheonCommand.java | 5 + .../cli/rlp/IbftExtraDataCLIAdapter.java | 47 ++++ .../pegasys/pantheon/cli/rlp/JSONToRLP.java | 28 +++ .../pantheon/cli/rlp/RLPSubCommand.java | 200 +++++++++++++++++ .../pegasys/pantheon/cli/rlp/RLPType.java | 30 +++ .../pantheon/cli/CommandTestAbstract.java | 12 +- .../pantheon/cli/rlp/RLPSubCommandTest.java | 211 ++++++++++++++++++ 10 files changed, 605 insertions(+), 5 deletions(-) create mode 100644 pantheon/src/main/java/tech/pegasys/pantheon/cli/rlp/IbftExtraDataCLIAdapter.java create mode 100644 pantheon/src/main/java/tech/pegasys/pantheon/cli/rlp/JSONToRLP.java create mode 100644 pantheon/src/main/java/tech/pegasys/pantheon/cli/rlp/RLPSubCommand.java create mode 100644 pantheon/src/main/java/tech/pegasys/pantheon/cli/rlp/RLPType.java create mode 100644 pantheon/src/test/java/tech/pegasys/pantheon/cli/rlp/RLPSubCommandTest.java diff --git a/docs/Reference/Pantheon-CLI-Syntax.md b/docs/Reference/Pantheon-CLI-Syntax.md index fcdec696a3..1753293c28 100644 --- a/docs/Reference/Pantheon-CLI-Syntax.md +++ b/docs/Reference/Pantheon-CLI-Syntax.md @@ -1033,4 +1033,76 @@ $ pantheon password hash --password= ```bash tab="Example" $ pantheon password hash --password=myPassword123 -``` \ No newline at end of file +``` + +### rlp + +This command provides RLP related actions. + +#### encode + +This command encodes a typed JSON value from a file or from the standard input into an RLP hexadecimal string. + +```bash tab="Syntax" +$ pantheon rlp encode [--from=] [--to=] [--type=] +``` + +```bash tab="Example using files" +$ pantheon rlp encode --from=ibft_extra_data.json --to=extra_data_for_ibft_genesis.txt --type=IBFT_EXTRA_DATA +``` + +```bash tab="Example using standard input/output and default type" +$ cat extra_data.json | pantheon rlp encode > rlp.txt +``` + +##### Available types for encoding + +For the moment, only IBFT extra data type of RLP data is supported. +This data is used to build the IBFT 2.0 genesis file. + +???+ summary "IBFT_EXTRA_DATA" + To generate the IBFT_EXTRA_DATA typed RLP string you need a JSON input containing an array with + all the validator addresses strings that you want to insert in your genesis. + + The JSON content is an object with a validator property that's an array of validator addresse strings. + + ??? tip "JSON Schema for IBFT_EXTRA_DATA" + The following JSON Schema can be used to validate that your JSON data is well formed. + + Among many tools you can use some online validator tool like https://www.jsonschemavalidator.net/ + to validate your JSON content. + + ```json + { + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "http://tech.pegasys.pantheon/cli_rlp_ibft_extra_data.json", + "type": "array", + "definitions": {}, + "title": "IBFT extra data", + "description":"JSON format used as input to generate an IBFT extra data RLP string", + "items": { + "$id": "#/address", + "type": "string", + "title": "Validator address", + "description":"The validator node address", + "default": "", + "examples": [ + "be068f726a13c8d46c44be6ce9d275600e1735a4", + "5ff6f4b66a46a2b2310a6f3a93aaddc0d9a1c193" + ], + "pattern":"^([0-9a-f]{40})$" + } + } + ``` + + !!!example "Example IBFT_EXTRA_DATA encoding" + ```json tab="JSON input" + [ + "be068f726a13c8d46c44be6ce9d275600e1735a4", + "5ff6f4b66a46a2b2310a6f3a93aaddc0d9a1c193" + ] + ``` + + ``` tab="RLP output" + 0xf853a00000000000000000000000000000000000000000000000000000000000000000ea94be068f726a13c8d46c44be6ce9d275600e1735a4945ff6f4b66a46a2b2310a6f3a93aaddc0d9a1c193808400000000c0 + ``` \ No newline at end of file diff --git a/pantheon/src/main/java/tech/pegasys/pantheon/Pantheon.java b/pantheon/src/main/java/tech/pegasys/pantheon/Pantheon.java index 5bb6c4ba00..436483e235 100644 --- a/pantheon/src/main/java/tech/pegasys/pantheon/Pantheon.java +++ b/pantheon/src/main/java/tech/pegasys/pantheon/Pantheon.java @@ -39,6 +39,7 @@ public static void main(final String... args) { pantheonCommand.parse( new RunLast().andExit(SUCCESS_EXIT_CODE), defaultExceptionHandler().andExit(ERROR_EXIT_CODE), + System.in, args); } } diff --git a/pantheon/src/main/java/tech/pegasys/pantheon/cli/DefaultCommandValues.java b/pantheon/src/main/java/tech/pegasys/pantheon/cli/DefaultCommandValues.java index a605093a6d..e5a206008b 100644 --- a/pantheon/src/main/java/tech/pegasys/pantheon/cli/DefaultCommandValues.java +++ b/pantheon/src/main/java/tech/pegasys/pantheon/cli/DefaultCommandValues.java @@ -26,7 +26,7 @@ import picocli.CommandLine; -interface DefaultCommandValues { +public interface DefaultCommandValues { String CONFIG_FILE_OPTION_NAME = "--config-file"; String MANDATORY_PATH_FORMAT_HELP = ""; diff --git a/pantheon/src/main/java/tech/pegasys/pantheon/cli/PantheonCommand.java b/pantheon/src/main/java/tech/pegasys/pantheon/cli/PantheonCommand.java index c1b99b8962..0d8d553ab4 100644 --- a/pantheon/src/main/java/tech/pegasys/pantheon/cli/PantheonCommand.java +++ b/pantheon/src/main/java/tech/pegasys/pantheon/cli/PantheonCommand.java @@ -31,6 +31,7 @@ import tech.pegasys.pantheon.cli.custom.EnodeToURIPropertyConverter; import tech.pegasys.pantheon.cli.custom.JsonRPCWhitelistHostsProperty; import tech.pegasys.pantheon.cli.custom.RpcAuthFileValidator; +import tech.pegasys.pantheon.cli.rlp.RLPSubCommand; import tech.pegasys.pantheon.config.GenesisConfigFile; import tech.pegasys.pantheon.consensus.clique.jsonrpc.CliqueRpcApis; import tech.pegasys.pantheon.consensus.ibft.jsonrpc.IbftRpcApis; @@ -58,6 +59,7 @@ import java.io.File; import java.io.IOException; +import java.io.InputStream; import java.net.InetAddress; import java.net.URI; import java.nio.file.Path; @@ -474,6 +476,7 @@ public PantheonCommand( public void parse( final AbstractParseResultHandler> resultHandler, final DefaultExceptionHandler> exceptionHandler, + final InputStream in, final String... args) { commandLine = new CommandLine(this); @@ -492,6 +495,8 @@ public void parse( PublicKeySubCommand.COMMAND_NAME, new PublicKeySubCommand(resultHandler.out())); commandLine.addSubcommand( PasswordSubCommand.COMMAND_NAME, new PasswordSubCommand(resultHandler.out())); + commandLine.addSubcommand( + RLPSubCommand.COMMAND_NAME, new RLPSubCommand(resultHandler.out(), in)); commandLine.registerConverter(Address.class, Address::fromHexString); commandLine.registerConverter(BytesValue.class, BytesValue::fromHexString); diff --git a/pantheon/src/main/java/tech/pegasys/pantheon/cli/rlp/IbftExtraDataCLIAdapter.java b/pantheon/src/main/java/tech/pegasys/pantheon/cli/rlp/IbftExtraDataCLIAdapter.java new file mode 100644 index 0000000000..9230d67600 --- /dev/null +++ b/pantheon/src/main/java/tech/pegasys/pantheon/cli/rlp/IbftExtraDataCLIAdapter.java @@ -0,0 +1,47 @@ +/* + * Copyright 2019 ConsenSys AG. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package tech.pegasys.pantheon.cli.rlp; + +import tech.pegasys.pantheon.consensus.ibft.IbftExtraData; +import tech.pegasys.pantheon.ethereum.core.Address; +import tech.pegasys.pantheon.util.bytes.BytesValue; + +import java.io.IOException; +import java.util.Collection; +import java.util.Collections; +import java.util.Optional; +import java.util.stream.Collectors; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; + +/** + * Adapter to convert a typed JSON to an IbftExtraData object This adapter handles the JSON to RLP + * encoding + */ +public class IbftExtraDataCLIAdapter implements JSONToRLP { + + @Override + public BytesValue encode(final String json) throws IOException { + ObjectMapper mapper = new ObjectMapper(); + TypeReference> typeRef = new TypeReference>() {}; + Collection validatorAddresse = mapper.readValue(json, typeRef); + + Collection
addresses = + validatorAddresse.stream().map(Address::fromHexString).collect(Collectors.toList()); + + return new IbftExtraData( + BytesValue.wrap(new byte[32]), Collections.emptyList(), Optional.empty(), 0, addresses) + .encode(); + } +} diff --git a/pantheon/src/main/java/tech/pegasys/pantheon/cli/rlp/JSONToRLP.java b/pantheon/src/main/java/tech/pegasys/pantheon/cli/rlp/JSONToRLP.java new file mode 100644 index 0000000000..7349748b1b --- /dev/null +++ b/pantheon/src/main/java/tech/pegasys/pantheon/cli/rlp/JSONToRLP.java @@ -0,0 +1,28 @@ +/* + * Copyright 2019 ConsenSys AG. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package tech.pegasys.pantheon.cli.rlp; + +import tech.pegasys.pantheon.util.bytes.BytesValue; + +import java.io.IOException; + +/** Behaviour of objects that can be encoded from JSON to RLP */ +interface JSONToRLP { + + /** + * Encodes the object into an RLP value. + * + * @return the RLP encoded object. + */ + BytesValue encode(String json) throws IOException; +} diff --git a/pantheon/src/main/java/tech/pegasys/pantheon/cli/rlp/RLPSubCommand.java b/pantheon/src/main/java/tech/pegasys/pantheon/cli/rlp/RLPSubCommand.java new file mode 100644 index 0000000000..ebfbfd3716 --- /dev/null +++ b/pantheon/src/main/java/tech/pegasys/pantheon/cli/rlp/RLPSubCommand.java @@ -0,0 +1,200 @@ +/* + * Copyright 2018 ConsenSys AG. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package tech.pegasys.pantheon.cli.rlp; + +import static com.google.common.base.Preconditions.checkNotNull; +import static java.nio.charset.StandardCharsets.UTF_8; +import static tech.pegasys.pantheon.cli.DefaultCommandValues.MANDATORY_FILE_FORMAT_HELP; + +import tech.pegasys.pantheon.cli.PantheonCommand; +import tech.pegasys.pantheon.cli.rlp.RLPSubCommand.EncodeSubCommand; +import tech.pegasys.pantheon.util.bytes.BytesValue; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.PrintStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Scanner; + +import com.fasterxml.jackson.databind.exc.MismatchedInputException; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import picocli.CommandLine.Command; +import picocli.CommandLine.ExecutionException; +import picocli.CommandLine.Model.CommandSpec; +import picocli.CommandLine.Option; +import picocli.CommandLine.ParameterException; +import picocli.CommandLine.ParentCommand; +import picocli.CommandLine.Spec; + +@Command( + name = RLPSubCommand.COMMAND_NAME, + description = "This command provides RLP data related actions.", + mixinStandardHelpOptions = true, + subcommands = {EncodeSubCommand.class}) +public class RLPSubCommand implements Runnable { + + public static final String COMMAND_NAME = "rlp"; + private static final Logger LOG = LogManager.getLogger(); + + private final PrintStream out; + private final InputStream in; + + @SuppressWarnings("unused") + @ParentCommand + private PantheonCommand parentCommand; + + @SuppressWarnings("unused") + @Spec + private CommandSpec spec; + + public RLPSubCommand(final PrintStream out, final InputStream in) { + this.out = out; + this.in = in; + } + + @Override + public void run() { + spec.commandLine().usage(out); + } + + /** + * RLP encode sub-command + * + *

Encode a JSON data into an RLP hex string. + */ + @Command( + name = "encode", + description = "This command encodes a JSON typed data into an RLP hex string.", + mixinStandardHelpOptions = true) + static class EncodeSubCommand implements Runnable { + + @SuppressWarnings("unused") + @ParentCommand + private RLPSubCommand parentCommand; // Picocli injects reference to parent command + + @SuppressWarnings("unused") + @Spec + private CommandSpec spec; + + @Option( + names = "--type", + description = + "Type of the RLP data to encode, possible values are ${COMPLETION-CANDIDATES}. (default: ${DEFAULT-VALUE})", + arity = "1..1") + private RLPType type = RLPType.IBFT_EXTRA_DATA; + + @Option( + names = "--from", + paramLabel = MANDATORY_FILE_FORMAT_HELP, + description = "File containing JSON object to encode", + arity = "1..1") + private File jsonSourceFile = null; + + @Option( + names = "--to", + paramLabel = MANDATORY_FILE_FORMAT_HELP, + description = "File to write encoded RLP string to.", + arity = "1..1") + private File rlpTargetFile = null; + + @Override + public void run() { + checkNotNull(parentCommand); + readInput(); + } + + /** + * Reads the stdin or from a file if one is specified by {@link #jsonSourceFile} then goes to + * {@link #encode(String)} this data + */ + private void readInput() { + // if we have an output file defined, print to it + // otherwise print to defined output, usually standard output. + StringBuilder jsonData = new StringBuilder(); + + if (jsonSourceFile != null) { + try { + BufferedReader reader = Files.newBufferedReader(jsonSourceFile.toPath(), UTF_8); + + String line; + while ((line = reader.readLine()) != null) jsonData.append(line); + } catch (IOException e) { + throw new ExecutionException(spec.commandLine(), "Unable to read JSON file."); + } + } else { + // get JSON data from standard input + try (Scanner scanner = new Scanner(parentCommand.in, UTF_8.name())) { + while (scanner.hasNextLine()) { + jsonData.append(String.join("", scanner.nextLine().split("\\s"))); + } + } + } + + // next step is to encode the value + encode(jsonData.toString()); + } + + /** + * Encodes the JSON input into an RLP data based on the {@link #type} then goes to {@link + * #writeOutput(BytesValue)} this data to file or stdout + * + * @param jsonInput the JSON string data to encode + */ + private void encode(final String jsonInput) { + if (jsonInput == null || jsonInput.isEmpty()) { + throw new ParameterException( + spec.commandLine(), "An error occurred while trying to read the JSON data."); + } else { + try { + // encode and write the value + writeOutput(type.getAdapter().encode(jsonInput)); + } catch (MismatchedInputException e) { + throw new ParameterException( + spec.commandLine(), + "Unable to map the JSON data with selected type. Please check JSON input format. " + + e); + } catch (IOException e) { + throw new ParameterException( + spec.commandLine(), + "Unable to load the JSON data. Please check JSON input format. " + e); + } + } + } + + /** + * write the encoded result to stdout or a file if the option is specified + * + * @param rlpEncodedOutput the RLP output to write to file or stdout + */ + private void writeOutput(final BytesValue rlpEncodedOutput) { + if (rlpTargetFile != null) { + final Path targetPath = rlpTargetFile.toPath(); + + try (final BufferedWriter fileWriter = Files.newBufferedWriter(targetPath, UTF_8)) { + fileWriter.write(rlpEncodedOutput.toString()); + } catch (final IOException e) { + throw new ParameterException( + spec.commandLine(), + "An error occurred while trying to write the RLP string. " + e.getMessage()); + } + } else { + parentCommand.out.println(rlpEncodedOutput); + } + } + } +} diff --git a/pantheon/src/main/java/tech/pegasys/pantheon/cli/rlp/RLPType.java b/pantheon/src/main/java/tech/pegasys/pantheon/cli/rlp/RLPType.java new file mode 100644 index 0000000000..c2115278db --- /dev/null +++ b/pantheon/src/main/java/tech/pegasys/pantheon/cli/rlp/RLPType.java @@ -0,0 +1,30 @@ +/* + * Copyright 2019 ConsenSys AG. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package tech.pegasys.pantheon.cli.rlp; + +/** Type of the RLP data to encode/decode */ +public enum RLPType { + // Enum is used to enable the listing of the possible values in PicoCLI. + IBFT_EXTRA_DATA(new IbftExtraDataCLIAdapter()); + + private final JSONToRLP adapter; + + RLPType(final JSONToRLP adapter) { + + this.adapter = adapter; + } + + public JSONToRLP getAdapter() { + return adapter; + } +} diff --git a/pantheon/src/test/java/tech/pegasys/pantheon/cli/CommandTestAbstract.java b/pantheon/src/test/java/tech/pegasys/pantheon/cli/CommandTestAbstract.java index 1dfc474415..d0f83d4c67 100644 --- a/pantheon/src/test/java/tech/pegasys/pantheon/cli/CommandTestAbstract.java +++ b/pantheon/src/test/java/tech/pegasys/pantheon/cli/CommandTestAbstract.java @@ -30,6 +30,7 @@ import java.io.ByteArrayOutputStream; import java.io.File; +import java.io.InputStream; import java.io.PrintStream; import java.net.URI; import java.nio.file.Path; @@ -58,10 +59,10 @@ public abstract class CommandTestAbstract { private final Logger TEST_LOGGER = LogManager.getLogger(); - final ByteArrayOutputStream commandOutput = new ByteArrayOutputStream(); + protected final ByteArrayOutputStream commandOutput = new ByteArrayOutputStream(); private final PrintStream outPrintStream = new PrintStream(commandOutput); - final ByteArrayOutputStream commandErrorOutput = new ByteArrayOutputStream(); + protected final ByteArrayOutputStream commandErrorOutput = new ByteArrayOutputStream(); private final PrintStream errPrintStream = new PrintStream(commandErrorOutput); @Mock RunnerBuilder mockRunnerBuilder; @@ -134,7 +135,11 @@ public void displayOutput() { TEST_LOGGER.info("Standard error {}", commandErrorOutput.toString()); } - CommandLine.Model.CommandSpec parseCommand(final String... args) { + protected CommandLine.Model.CommandSpec parseCommand(final String... args) { + return parseCommand(System.in, args); + } + + protected CommandLine.Model.CommandSpec parseCommand(final InputStream in, final String... args) { final TestPantheonCommand pantheonCommand = new TestPantheonCommand( @@ -148,6 +153,7 @@ CommandLine.Model.CommandSpec parseCommand(final String... args) { pantheonCommand.parse( new RunLast().useOut(outPrintStream).useAnsi(Ansi.OFF), new DefaultExceptionHandler>().useErr(errPrintStream).useAnsi(Ansi.OFF), + in, args); return pantheonCommand.spec; } diff --git a/pantheon/src/test/java/tech/pegasys/pantheon/cli/rlp/RLPSubCommandTest.java b/pantheon/src/test/java/tech/pegasys/pantheon/cli/rlp/RLPSubCommandTest.java new file mode 100644 index 0000000000..93c1bae29c --- /dev/null +++ b/pantheon/src/test/java/tech/pegasys/pantheon/cli/rlp/RLPSubCommandTest.java @@ -0,0 +1,211 @@ +/* + * Copyright 2019 ConsenSys AG. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package tech.pegasys.pantheon.cli.rlp; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.contentOf; + +import tech.pegasys.pantheon.cli.CommandTestAbstract; + +import java.io.BufferedWriter; +import java.io.ByteArrayInputStream; +import java.io.File; +import java.nio.file.Files; + +import org.junit.After; +import org.junit.Test; +import picocli.CommandLine.Model.CommandSpec; + +public class RLPSubCommandTest extends CommandTestAbstract { + + private static final String EXPECTED_RLP_USAGE = + "Usage: pantheon rlp [-hV] [COMMAND]" + + System.lineSeparator() + + "This command provides RLP data related actions." + + System.lineSeparator() + + " -h, --help Show this help message and exit." + + System.lineSeparator() + + " -V, --version Print version information and exit." + + System.lineSeparator() + + "Commands:" + + System.lineSeparator() + + " encode This command encodes a JSON typed data into an RLP hex string."; + + private static final String EXPECTED_RLP_ENCODE_USAGE = + "Usage: pantheon rlp encode [-hV] [--from=] [--to=] [--type=]" + + System.lineSeparator() + + "This command encodes a JSON typed data into an RLP hex string." + + System.lineSeparator() + + " --from= File containing JSON object to encode" + + System.lineSeparator() + + " --to= File to write encoded RLP string to." + + System.lineSeparator() + + " --type= Type of the RLP data to encode, possible values are" + + System.lineSeparator() + + " IBFT_EXTRA_DATA. (default: IBFT_EXTRA_DATA)" + + System.lineSeparator() + + " -h, --help Show this help message and exit." + + System.lineSeparator() + + " -V, --version Print version information and exit."; + + private static final String RLP_SUBCOMMAND_NAME = "rlp"; + private static final String RLP_ENCODE_SUBCOMMAND_NAME = "encode"; + + // RLP sub-command + @Test + public void rlpSubCommandExistAnbHaveSubCommands() { + final CommandSpec spec = parseCommand(); + assertThat(spec.subcommands()).containsKeys(RLP_SUBCOMMAND_NAME); + assertThat(spec.subcommands().get(RLP_SUBCOMMAND_NAME).getSubcommands()) + .containsKeys(RLP_ENCODE_SUBCOMMAND_NAME); + assertThat(commandOutput.toString()).isEmpty(); + assertThat(commandErrorOutput.toString()).isEmpty(); + } + + @Test + public void callingRLPSubCommandWithoutSubSubcommandMustDisplayUsage() { + parseCommand(RLP_SUBCOMMAND_NAME); + assertThat(commandOutput.toString()).startsWith(EXPECTED_RLP_USAGE); + assertThat(commandErrorOutput.toString()).isEmpty(); + } + + @Test + public void callingRPLSubCommandHelpMustDisplayUsage() { + parseCommand(RLP_SUBCOMMAND_NAME, "--help"); + assertThat(commandOutput.toString()).startsWith(EXPECTED_RLP_USAGE); + assertThat(commandErrorOutput.toString()).isEmpty(); + } + + // Encode RLP sub-command + @Test + public void callingRPLEncodeSubCommandHelpMustDisplayUsage() { + parseCommand(RLP_SUBCOMMAND_NAME, RLP_ENCODE_SUBCOMMAND_NAME, "--help"); + assertThat(commandOutput.toString()).startsWith(EXPECTED_RLP_ENCODE_USAGE); + assertThat(commandErrorOutput.toString()).isEmpty(); + } + + @Test + public void encodeWithoutPathMustWriteToStandardOutput() { + + final String jsonInput = + "[\"be068f726a13c8d46c44be6ce9d275600e1735a4\", \"5ff6f4b66a46a2b2310a6f3a93aaddc0d9a1c193\"]"; + + // set stdin + final ByteArrayInputStream stdIn = new ByteArrayInputStream(jsonInput.getBytes(UTF_8)); + + parseCommand(stdIn, RLP_SUBCOMMAND_NAME, RLP_ENCODE_SUBCOMMAND_NAME); + + final String expectedRlpString = + "0xf853a00000000000000000000000000000000000000000000000000000000000000000ea94be068f726a13c8d" + + "46c44be6ce9d275600e1735a4945ff6f4b66a46a2b2310a6f3a93aaddc0d9a1c193808400000000c0"; + assertThat(commandOutput.toString()).contains(expectedRlpString); + assertThat(commandErrorOutput.toString()).isEmpty(); + } + + @Test + public void encodeWithOutputFileMustWriteInThisFile() throws Exception { + + final File file = File.createTempFile("ibftExtraData", "rlp"); + + final String jsonInput = + "[\"be068f726a13c8d46c44be6ce9d275600e1735a4\", \"5ff6f4b66a46a2b2310a6f3a93aaddc0d9a1c193\"]"; + + // set stdin + final ByteArrayInputStream stdIn = new ByteArrayInputStream(jsonInput.getBytes(UTF_8)); + + parseCommand(stdIn, RLP_SUBCOMMAND_NAME, RLP_ENCODE_SUBCOMMAND_NAME, "--to", file.getPath()); + + final String expectedRlpString = + "0xf853a00000000000000000000000000000000000000000000000000000000000000000ea94be068f726a13c8d" + + "46c44be6ce9d275600e1735a4945ff6f4b66a46a2b2310a6f3a93aaddc0d9a1c193808400000000c0"; + + assertThat(contentOf(file)).contains(expectedRlpString); + + assertThat(commandOutput.toString()).isEmpty(); + assertThat(commandErrorOutput.toString()).isEmpty(); + } + + @Test + public void encodeWithInputFilePathMustReadFromThisFile() throws Exception { + + final File tempJsonFile = temp.newFile("test.json"); + try (final BufferedWriter fileWriter = Files.newBufferedWriter(tempJsonFile.toPath(), UTF_8)) { + + fileWriter.write( + "[\"be068f726a13c8d46c44be6ce9d275600e1735a4\", \"5ff6f4b66a46a2b2310a6f3a93aaddc0d9a1c193\"]"); + + fileWriter.flush(); + + parseCommand( + RLP_SUBCOMMAND_NAME, RLP_ENCODE_SUBCOMMAND_NAME, "--from", tempJsonFile.getPath()); + + final String expectedRlpString = + "0xf853a00000000000000000000000000000000000000000000000000000000000000000ea94be068f726a13c8d" + + "46c44be6ce9d275600e1735a4945ff6f4b66a46a2b2310a6f3a93aaddc0d9a1c193808400000000c0"; + assertThat(commandOutput.toString()).contains(expectedRlpString); + assertThat(commandErrorOutput.toString()).isEmpty(); + } + } + + @Test + public void encodeWithInvalidInputMustRaiseAnError() throws Exception { + + final File tempJsonFile = temp.newFile("invalid_test.json"); + try (final BufferedWriter fileWriter = Files.newBufferedWriter(tempJsonFile.toPath(), UTF_8)) { + + fileWriter.write("{\"property\":0}"); + + fileWriter.flush(); + + parseCommand( + RLP_SUBCOMMAND_NAME, RLP_ENCODE_SUBCOMMAND_NAME, "--from", tempJsonFile.getPath()); + + assertThat(commandOutput.toString()).isEmpty(); + assertThat(commandErrorOutput.toString()) + .startsWith( + "Unable to map the JSON data with selected type. Please check JSON input format."); + } + } + + @Test + public void encodeWithEmptyInputMustRaiseAnError() throws Exception { + + final File tempJsonFile = temp.newFile("empty.json"); + + parseCommand(RLP_SUBCOMMAND_NAME, RLP_ENCODE_SUBCOMMAND_NAME, "--from", tempJsonFile.getPath()); + + assertThat(commandOutput.toString()).isEmpty(); + assertThat(commandErrorOutput.toString()) + .startsWith("An error occurred while trying to read the JSON data."); + } + + @Test + public void encodeWithEmptyStdInputMustRaiseAnError() throws Exception { + + // set empty stdin + final String jsonInput = ""; + final ByteArrayInputStream stdIn = new ByteArrayInputStream(jsonInput.getBytes(UTF_8)); + + parseCommand(stdIn, RLP_SUBCOMMAND_NAME, RLP_ENCODE_SUBCOMMAND_NAME); + + assertThat(commandOutput.toString()).isEmpty(); + assertThat(commandErrorOutput.toString()) + .startsWith("An error occurred while trying to read the JSON data."); + } + + @After + public void restoreStdin() { + System.setIn(System.in); + } +} From b58694d1bba290a4a9980fdf487a50f05e3eddfa Mon Sep 17 00:00:00 2001 From: Adrian Sutton Date: Tue, 26 Feb 2019 11:05:10 +1000 Subject: [PATCH 2/2] Add missing JavaDoc tags. (#963) --- .../src/main/java/tech/pegasys/pantheon/cli/rlp/JSONToRLP.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pantheon/src/main/java/tech/pegasys/pantheon/cli/rlp/JSONToRLP.java b/pantheon/src/main/java/tech/pegasys/pantheon/cli/rlp/JSONToRLP.java index 7349748b1b..70696a8479 100644 --- a/pantheon/src/main/java/tech/pegasys/pantheon/cli/rlp/JSONToRLP.java +++ b/pantheon/src/main/java/tech/pegasys/pantheon/cli/rlp/JSONToRLP.java @@ -22,7 +22,9 @@ interface JSONToRLP { /** * Encodes the object into an RLP value. * + * @param json the JSON to convert to RLP * @return the RLP encoded object. + * @throws IOException if an error occurs while reading data */ BytesValue encode(String json) throws IOException; }