From ee8a1db85fc1ce5923a68e092b3d1473ff06424d Mon Sep 17 00:00:00 2001 From: Nicolas MASSART Date: Wed, 20 Feb 2019 12:42:06 +0100 Subject: [PATCH 1/7] wip --- .../pegasys/pantheon/cli/RLPSubCommand.java | 132 ++++++++++++++++++ .../tech/pegasys/pantheon/cli/RLPType.java | 5 + .../pantheon/cli/RLPSubCommandTest.java | 14 ++ 3 files changed, 151 insertions(+) create mode 100644 pantheon/src/main/java/tech/pegasys/pantheon/cli/RLPSubCommand.java create mode 100644 pantheon/src/main/java/tech/pegasys/pantheon/cli/RLPType.java create mode 100644 pantheon/src/test/java/tech/pegasys/pantheon/cli/RLPSubCommandTest.java diff --git a/pantheon/src/main/java/tech/pegasys/pantheon/cli/RLPSubCommand.java b/pantheon/src/main/java/tech/pegasys/pantheon/cli/RLPSubCommand.java new file mode 100644 index 0000000000..9cb004763d --- /dev/null +++ b/pantheon/src/main/java/tech/pegasys/pantheon/cli/RLPSubCommand.java @@ -0,0 +1,132 @@ +/* + * 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; + +import static com.google.common.base.Preconditions.checkNotNull; +import static tech.pegasys.pantheon.cli.DefaultCommandValues.MANDATORY_FILE_FORMAT_HELP; +import static tech.pegasys.pantheon.cli.PasswordSubCommand.COMMAND_NAME; + +import io.vertx.core.Vertx; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.PrintStream; +import java.nio.file.Path; +import java.util.Collections; +import java.util.Optional; +import org.springframework.security.crypto.bcrypt.BCrypt; +import picocli.CommandLine; +import picocli.CommandLine.Command; +import picocli.CommandLine.ExecutionException; +import picocli.CommandLine.Model.CommandSpec; +import picocli.CommandLine.Option; +import picocli.CommandLine.ParentCommand; +import picocli.CommandLine.Spec; +import tech.pegasys.pantheon.cli.PasswordSubCommand.HashSubCommand; +import tech.pegasys.pantheon.cli.RLPSubCommand.EncodeSubCommand; +import tech.pegasys.pantheon.consensus.common.ConsensusHelpers; +import tech.pegasys.pantheon.consensus.ibft.IbftExtraData; +import tech.pegasys.pantheon.metrics.prometheus.MetricsConfiguration; +import tech.pegasys.pantheon.metrics.prometheus.MetricsService; + +@Command( + name = COMMAND_NAME, + description = "This command provides password related actions.", + mixinStandardHelpOptions = true, + subcommands = {EncodeSubCommand.class}) +class RLPSubCommand implements Runnable { + + static final String COMMAND_NAME = "rlp"; + + @SuppressWarnings("unused") + @ParentCommand + private PantheonCommand parentCommand; + + @SuppressWarnings("unused") + @Spec + private CommandSpec spec; + + final PrintStream out; + + RLPSubCommand(final PrintStream out) { + this.out = out; + } + + @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 from a file into an RLP hex string.", + mixinStandardHelpOptions = true) + static class EncodeSubCommand implements Runnable { + @SuppressWarnings("unused") + @ParentCommand + private BlocksSubCommand parentCommand; // Picocli injects reference to parent command + + @Option( + names = "--type", + required = true, + description = "Type of the data, possible values are ${COMPLETION-CANDIDATES}. " + + "(default: ${DEFAULT-VALUE})" ) + private final RLPType type = RLPType.IBFT_EXTRA_DATA; + + @Option( + names = "--from", + required = true, + paramLabel = MANDATORY_FILE_FORMAT_HELP, + description = "File containing blocks to import", + arity = "1..1") + private final File jsonSourceFile = null; + + @Override + public void run() { + + checkNotNull(parentCommand); + + try { + // As jsonSourceFile even if initialized as null is injected by PicoCLI and param is + // mandatory + // So we are sure it's always not null, we can remove the warning + //noinspection ConstantConditions + final Path path = jsonSourceFile.toPath(); + + parentCommand.blockImporter.importBlockchain( + path, parentCommand.parentCommand.buildController()); + + + final IbftExtraData extraData = + new IbftExtraData( + ConsensusHelpers.zeroLeftPad(vanityData, IbftExtraData.EXTRA_VANITY_LENGTH), + Collections.emptyList(), + toVote(proposal), + round, + validators); + + return extraData.encode(); + } catch (final FileNotFoundException e) { + throw new ExecutionException( + new CommandLine(this), "Could not find file to import: " + blocksImportFile); + } catch (final IOException e) { + throw new ExecutionException( + new CommandLine(this), "Unable to import blocks from " + blocksImportFile, e); + } + } +} diff --git a/pantheon/src/main/java/tech/pegasys/pantheon/cli/RLPType.java b/pantheon/src/main/java/tech/pegasys/pantheon/cli/RLPType.java new file mode 100644 index 0000000000..0af7358e06 --- /dev/null +++ b/pantheon/src/main/java/tech/pegasys/pantheon/cli/RLPType.java @@ -0,0 +1,5 @@ +package tech.pegasys.pantheon.cli; + +enum RLPType { + IBFT_EXTRA_DATA; +} diff --git a/pantheon/src/test/java/tech/pegasys/pantheon/cli/RLPSubCommandTest.java b/pantheon/src/test/java/tech/pegasys/pantheon/cli/RLPSubCommandTest.java new file mode 100644 index 0000000000..1d5bc22580 --- /dev/null +++ b/pantheon/src/test/java/tech/pegasys/pantheon/cli/RLPSubCommandTest.java @@ -0,0 +1,14 @@ +package tech.pegasys.pantheon.cli; + +import static org.junit.Assert.*; + +public class RLPSubCommandTest { +// [ "0000000000000000000000000000000000000000000000000000000000000000", +// [ "be068f726a13c8d46c44be6ce9d275600e1735a4", +// "5ff6f4b66a46a2b2310a6f3a93aaddc0d9a1c193" ], +// "", +// "00000000", +// [] +// ] + +} \ No newline at end of file From 04e281652f4e09dc6d69101dbe53f132e97ed096 Mon Sep 17 00:00:00 2001 From: Nicolas MASSART Date: Fri, 22 Feb 2019 19:42:25 +0100 Subject: [PATCH 2/7] Add RLP encoding subcommand for ibftExtraData to be inserted in genesis file This is intentionally not a generic command as anyway RPL encoding is specific to each type of data. It was coded with efficiency in mind and as there's only one encodable type for the moment, the --type option is not provided. We'll add the option later if needed and the current type will be the default one. However a RLPEncodable interface is added to handle the RLP encode behaviour. JSON annotations were added for type mapping. made the file output optional and write to standard output by default, same for standard input. Some mnore tests are required, this is a preview version. --- acceptance-tests/build.gradle | 1 + .../consensus/ibft/IbftExtraData.java | 23 ++- .../pantheon/ethereum/rlp/RLPEncodable.java | 20 +++ .../java/tech/pegasys/pantheon/Pantheon.java | 1 + .../pegasys/pantheon/cli/PantheonCommand.java | 6 +- .../pantheon/cli/PasswordSubCommand.java | 2 +- .../pegasys/pantheon/cli/RLPSubCommand.java | 156 ++++++++++++------ .../tech/pegasys/pantheon/cli/RLPType.java | 5 - .../pantheon/cli/CommandTestAbstract.java | 6 + .../pantheon/cli/RLPSubCommandTest.java | 130 +++++++++++++-- 10 files changed, 270 insertions(+), 80 deletions(-) create mode 100644 ethereum/rlp/src/main/java/tech/pegasys/pantheon/ethereum/rlp/RLPEncodable.java delete mode 100644 pantheon/src/main/java/tech/pegasys/pantheon/cli/RLPType.java diff --git a/acceptance-tests/build.gradle b/acceptance-tests/build.gradle index 1a6e05e91b..377aa96f08 100644 --- a/acceptance-tests/build.gradle +++ b/acceptance-tests/build.gradle @@ -25,6 +25,7 @@ dependencies { testImplementation project(':ethereum:blockcreation') testImplementation project(':ethereum:jsonrpc') testImplementation project(':ethereum:permissioning') + testImplementation project(':ethereum:rlp') testImplementation project(':metrics') testImplementation project(':pantheon') testImplementation project(':util') diff --git a/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/IbftExtraData.java b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/IbftExtraData.java index aac6b87d79..0b158c5f45 100644 --- a/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/IbftExtraData.java +++ b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/IbftExtraData.java @@ -18,6 +18,7 @@ import tech.pegasys.pantheon.ethereum.core.Address; import tech.pegasys.pantheon.ethereum.rlp.BytesValueRLPInput; import tech.pegasys.pantheon.ethereum.rlp.BytesValueRLPOutput; +import tech.pegasys.pantheon.ethereum.rlp.RLPEncodable; import tech.pegasys.pantheon.ethereum.rlp.RLPInput; import tech.pegasys.pantheon.util.bytes.BytesValue; @@ -27,11 +28,14 @@ import java.util.Optional; import java.util.StringJoiner; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + /** * Represents the data structure stored in the extraData field of the BlockHeader used when * operating under an IBFT 2.0 consensus mechanism. */ -public class IbftExtraData { +public class IbftExtraData implements RLPEncodable { public static final int EXTRA_VANITY_LENGTH = 32; @@ -79,6 +83,7 @@ public static IbftExtraData decode(final BytesValue input) { return new IbftExtraData(vanityData, seals, vote, round, validators); } + @Override public BytesValue encode() { return encode(EncodingType.ALL); } @@ -120,15 +125,15 @@ private BytesValue encode(final EncodingType encodingType) { return encoder.encoded(); } + @JsonCreator + public static IbftExtraData createGenesisExtraData( + @JsonProperty("validators") final Collection

validators) { + return new IbftExtraData( + BytesValue.wrap(new byte[32]), Collections.emptyList(), Optional.empty(), 0, validators); + } + public static String createGenesisExtraDataString(final List
validators) { - final IbftExtraData extraData = - new IbftExtraData( - BytesValue.wrap(new byte[32]), - Collections.emptyList(), - Optional.empty(), - 0, - validators); - return extraData.encode().toString(); + return createGenesisExtraData(validators).encode().toString(); } // Accessors diff --git a/ethereum/rlp/src/main/java/tech/pegasys/pantheon/ethereum/rlp/RLPEncodable.java b/ethereum/rlp/src/main/java/tech/pegasys/pantheon/ethereum/rlp/RLPEncodable.java new file mode 100644 index 0000000000..e1717f0ada --- /dev/null +++ b/ethereum/rlp/src/main/java/tech/pegasys/pantheon/ethereum/rlp/RLPEncodable.java @@ -0,0 +1,20 @@ +/* + * 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.ethereum.rlp; + +import tech.pegasys.pantheon.util.bytes.BytesValue; + +public interface RLPEncodable { + + BytesValue encode(); +} 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/PantheonCommand.java b/pantheon/src/main/java/tech/pegasys/pantheon/cli/PantheonCommand.java index c1b99b8962..a28d28bb95 100644 --- a/pantheon/src/main/java/tech/pegasys/pantheon/cli/PantheonCommand.java +++ b/pantheon/src/main/java/tech/pegasys/pantheon/cli/PantheonCommand.java @@ -58,6 +58,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; @@ -103,7 +104,7 @@ public class PantheonCommand implements DefaultCommandValues, Runnable { private final Logger logger; - private CommandLine commandLine; + CommandLine commandLine; public static class RpcApisConverter implements ITypeConverter { @@ -474,6 +475,7 @@ public PantheonCommand( public void parse( final AbstractParseResultHandler> resultHandler, final DefaultExceptionHandler> exceptionHandler, + final InputStream in, final String... args) { commandLine = new CommandLine(this); @@ -492,6 +494,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/PasswordSubCommand.java b/pantheon/src/main/java/tech/pegasys/pantheon/cli/PasswordSubCommand.java index 5bc8e93566..3794b87590 100644 --- a/pantheon/src/main/java/tech/pegasys/pantheon/cli/PasswordSubCommand.java +++ b/pantheon/src/main/java/tech/pegasys/pantheon/cli/PasswordSubCommand.java @@ -43,7 +43,7 @@ class PasswordSubCommand implements Runnable { @Spec private CommandSpec spec; - final PrintStream out; + private final PrintStream out; PasswordSubCommand(final PrintStream out) { this.out = out; diff --git a/pantheon/src/main/java/tech/pegasys/pantheon/cli/RLPSubCommand.java b/pantheon/src/main/java/tech/pegasys/pantheon/cli/RLPSubCommand.java index 9cb004763d..cdc3755a4e 100644 --- a/pantheon/src/main/java/tech/pegasys/pantheon/cli/RLPSubCommand.java +++ b/pantheon/src/main/java/tech/pegasys/pantheon/cli/RLPSubCommand.java @@ -13,40 +13,48 @@ package tech.pegasys.pantheon.cli; 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 static tech.pegasys.pantheon.cli.PasswordSubCommand.COMMAND_NAME; -import io.vertx.core.Vertx; +import tech.pegasys.pantheon.cli.RLPSubCommand.EncodeSubCommand; +import tech.pegasys.pantheon.consensus.ibft.IbftExtraData; +import tech.pegasys.pantheon.ethereum.rlp.RLPEncodable; +import tech.pegasys.pantheon.util.bytes.BytesValue; + +import java.io.BufferedReader; +import java.io.BufferedWriter; import java.io.File; -import java.io.FileNotFoundException; 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.Collections; -import java.util.Optional; -import org.springframework.security.crypto.bcrypt.BCrypt; -import picocli.CommandLine; +import java.util.Scanner; + +import io.vertx.core.json.DecodeException; +import io.vertx.core.json.JsonObject; +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; -import tech.pegasys.pantheon.cli.PasswordSubCommand.HashSubCommand; -import tech.pegasys.pantheon.cli.RLPSubCommand.EncodeSubCommand; -import tech.pegasys.pantheon.consensus.common.ConsensusHelpers; -import tech.pegasys.pantheon.consensus.ibft.IbftExtraData; -import tech.pegasys.pantheon.metrics.prometheus.MetricsConfiguration; -import tech.pegasys.pantheon.metrics.prometheus.MetricsService; @Command( - name = COMMAND_NAME, - description = "This command provides password related actions.", + name = RLPSubCommand.COMMAND_NAME, + description = "This command provides RLP data related actions.", mixinStandardHelpOptions = true, subcommands = {EncodeSubCommand.class}) class RLPSubCommand implements Runnable { static final String COMMAND_NAME = "rlp"; + private static final Logger LOG = LogManager.getLogger(); + + private final PrintStream out; + private final InputStream in; @SuppressWarnings("unused") @ParentCommand @@ -56,10 +64,9 @@ class RLPSubCommand implements Runnable { @Spec private CommandSpec spec; - final PrintStream out; - - RLPSubCommand(final PrintStream out) { + RLPSubCommand(final PrintStream out, final InputStream in) { this.out = out; + this.in = in; } @Override @@ -74,59 +81,100 @@ public void run() { */ @Command( name = "encode", - description = "This command encodes a JSON typed data from a file into an RLP hex string.", + description = "This command encodes a JSON typed data into an RLP hex string.", mixinStandardHelpOptions = true) static class EncodeSubCommand implements Runnable { + @SuppressWarnings("unused") @ParentCommand - private BlocksSubCommand parentCommand; // Picocli injects reference to parent command + private RLPSubCommand parentCommand; // Picocli injects reference to parent command - @Option( - names = "--type", - required = true, - description = "Type of the data, possible values are ${COMPLETION-CANDIDATES}. " - + "(default: ${DEFAULT-VALUE})" ) - private final RLPType type = RLPType.IBFT_EXTRA_DATA; + @SuppressWarnings("unused") + @Spec + private CommandSpec spec; @Option( names = "--from", - required = true, paramLabel = MANDATORY_FILE_FORMAT_HELP, - description = "File containing blocks to import", + 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 RPL string to.", arity = "1..1") - private final File jsonSourceFile = null; + private File rlpTargetFile = null; @Override public void run() { - checkNotNull(parentCommand); + readInput(); + } - try { - // As jsonSourceFile even if initialized as null is injected by PicoCLI and param is - // mandatory - // So we are sure it's always not null, we can remove the warning - //noinspection ConstantConditions - final Path path = jsonSourceFile.toPath(); - - parentCommand.blockImporter.importBlockchain( - path, parentCommand.parentCommand.buildController()); - - - final IbftExtraData extraData = - new IbftExtraData( - ConsensusHelpers.zeroLeftPad(vanityData, IbftExtraData.EXTRA_VANITY_LENGTH), - Collections.emptyList(), - toVote(proposal), - round, - validators); + 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"))); + } + } + } + encode(jsonData.toString()); + } - return extraData.encode(); - } catch (final FileNotFoundException e) { + private void encode(final String jsonInput) { + // map the json to the object matching the type option + // the object must be an RLPEncodable object + if (jsonInput == null || jsonInput.isEmpty()) { throw new ExecutionException( - new CommandLine(this), "Could not find file to import: " + blocksImportFile); - } catch (final IOException e) { - throw new ExecutionException( - new CommandLine(this), "Unable to import blocks from " + blocksImportFile, e); + spec.commandLine(), "An error occurred while trying to read the JSON data."); + } else { + try { + JsonObject jsonObject = new JsonObject(jsonInput); + RLPEncodable objectToEncode = jsonObject.mapTo(IbftExtraData.class); + // encode and write the value + writeOutput(objectToEncode.encode()); + } catch (DecodeException e) { + throw new ParameterException( + spec.commandLine(), "Unable to load the JSON data. Please check JSON input format."); + } catch (IllegalArgumentException e) { + throw new ParameterException( + spec.commandLine(), + "Unable to map the JSON data with IbftExtraData type. Please check JSON input format."); + } + } + } + + private void writeOutput(final BytesValue rlpEncodedOutput) { + // write the encoded result to stdout or a file if the option is specified + 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"); + } + } else { + parentCommand.out.println(rlpEncodedOutput); + } } } } diff --git a/pantheon/src/main/java/tech/pegasys/pantheon/cli/RLPType.java b/pantheon/src/main/java/tech/pegasys/pantheon/cli/RLPType.java deleted file mode 100644 index 0af7358e06..0000000000 --- a/pantheon/src/main/java/tech/pegasys/pantheon/cli/RLPType.java +++ /dev/null @@ -1,5 +0,0 @@ -package tech.pegasys.pantheon.cli; - -enum RLPType { - IBFT_EXTRA_DATA; -} 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 6ae52d453a..2ba83bc4d7 100644 --- a/pantheon/src/test/java/tech/pegasys/pantheon/cli/CommandTestAbstract.java +++ b/pantheon/src/test/java/tech/pegasys/pantheon/cli/CommandTestAbstract.java @@ -31,6 +31,7 @@ import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; +import java.io.InputStream; import java.io.PrintStream; import java.net.URI; import java.nio.file.Path; @@ -142,6 +143,10 @@ public void displayOutput() throws IOException { } CommandLine.Model.CommandSpec parseCommand(final String... args) { + return parseCommand(System.in, args); + } + + CommandLine.Model.CommandSpec parseCommand(final InputStream in, final String... args) { final TestPantheonCommand pantheonCommand = new TestPantheonCommand( @@ -155,6 +160,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/RLPSubCommandTest.java b/pantheon/src/test/java/tech/pegasys/pantheon/cli/RLPSubCommandTest.java index 1d5bc22580..d9c16028c1 100644 --- a/pantheon/src/test/java/tech/pegasys/pantheon/cli/RLPSubCommandTest.java +++ b/pantheon/src/test/java/tech/pegasys/pantheon/cli/RLPSubCommandTest.java @@ -1,14 +1,124 @@ +/* + * 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; -import static org.junit.Assert.*; +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.contentOf; -public class RLPSubCommandTest { -// [ "0000000000000000000000000000000000000000000000000000000000000000", -// [ "be068f726a13c8d46c44be6ce9d275600e1735a4", -// "5ff6f4b66a46a2b2310a6f3a93aaddc0d9a1c193" ], -// "", -// "00000000", -// [] -// ] +import java.io.ByteArrayInputStream; +import java.io.File; -} \ No newline at end of file +import org.junit.After; +import org.junit.Test; +import picocli.CommandLine.Model.CommandSpec; + +public class RLPSubCommandTest extends CommandTestAbstract { + // [ "0000000000000000000000000000000000000000000000000000000000000000", + // [ "be068f726a13c8d46c44be6ce9d275600e1735a4", + // "5ff6f4b66a46a2b2310a6f3a93aaddc0d9a1c193" ], + // "", + // "00000000", + // [] + // ] + + 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 RLP_SUBCOMMAND_NAME = "rlp"; + private static final String RLP_ENCODE_SUBCOMMAND_NAME = "encode"; + + // RLP sub-command + @Test + public void rlpSubCommandExistAnbHaveSubCommands() { + 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 callingRLPEncodeSubCommandWithoutPathMustWriteToStandardOutput() { + + String jsonInput = + "{\"validators\":[ \"be068f726a13c8d46c44be6ce9d275600e1735a4\",\n" + + "\"5ff6f4b66a46a2b2310a6f3a93aaddc0d9a1c193\" ]}"; + + // set stdin + ByteArrayInputStream stdIn = new ByteArrayInputStream(jsonInput.getBytes(UTF_8)); + + parseCommand(stdIn, RLP_SUBCOMMAND_NAME, RLP_ENCODE_SUBCOMMAND_NAME); + + String expectedRlpString = + "0xf853a00000000000000000000000000000000000000000000000000000000000000000ea94be068f726a13c8d" + + "46c44be6ce9d275600e1735a4945ff6f4b66a46a2b2310a6f3a93aaddc0d9a1c193808400000000c0"; + assertThat(commandOutput.toString()).contains(expectedRlpString); + assertThat(commandErrorOutput.toString()).isEmpty(); + } + + @Test + public void callingRLPEncodeSubCommandWithFilePathMustWriteInThisFile() throws Exception { + + final File file = File.createTempFile("ibftExtraData", "rlp"); + + String jsonInput = + "{\"validators\":[ \"be068f726a13c8d46c44be6ce9d275600e1735a4\",\n" + + "\"5ff6f4b66a46a2b2310a6f3a93aaddc0d9a1c193\" ]}"; + + // set stdin + ByteArrayInputStream stdIn = new ByteArrayInputStream(jsonInput.getBytes(UTF_8)); + + parseCommand(stdIn, RLP_SUBCOMMAND_NAME, RLP_ENCODE_SUBCOMMAND_NAME, "--to", file.getPath()); + + String expectedRlpString = + "0xf853a00000000000000000000000000000000000000000000000000000000000000000ea94be068f726a13c8d" + + "46c44be6ce9d275600e1735a4945ff6f4b66a46a2b2310a6f3a93aaddc0d9a1c193808400000000c0"; + + assertThat(contentOf(file)).contains(expectedRlpString); + + assertThat(commandOutput.toString()).isEmpty(); + assertThat(commandErrorOutput.toString()).isEmpty(); + } + + @After + public void restoreStdin() { + System.setIn(System.in); + } +} From 0a1db470fe26e1bd6ddc74f57fba7f190629aae7 Mon Sep 17 00:00:00 2001 From: Nicolas MASSART Date: Mon, 25 Feb 2019 12:29:13 +0100 Subject: [PATCH 3/7] Updated code to use an adapter instead of modifying the deep classes with JSON @ Also found a way to make the enum able to return a type in a generic way using an interface so the `--type` option can be used. We still have only one type but at least it's ready for two. Still some unit tests to add in the next commit. --- acceptance-tests/build.gradle | 1 - .../consensus/ibft/IbftExtraData.java | 19 ++----- .../pantheon/cli/DefaultCommandValues.java | 2 +- .../pegasys/pantheon/cli/PantheonCommand.java | 1 + .../cli/rlp/IbftExtraDataCLIAdapter.java | 50 +++++++++++++++++++ .../pegasys/pantheon/cli/rlp/JSONtoRLP.java | 7 +-- .../pantheon/cli/{ => rlp}/RLPSubCommand.java | 37 ++++++++------ .../pegasys/pantheon/cli/rlp/RLPType.java | 22 ++++++++ .../pantheon/cli/rlp/RLPTypeProvider.java | 17 +++++++ .../pantheon/cli/RLPSubCommandTest.java | 42 ++++++++++++---- 10 files changed, 156 insertions(+), 42 deletions(-) create mode 100644 pantheon/src/main/java/tech/pegasys/pantheon/cli/rlp/IbftExtraDataCLIAdapter.java rename ethereum/rlp/src/main/java/tech/pegasys/pantheon/ethereum/rlp/RLPEncodable.java => pantheon/src/main/java/tech/pegasys/pantheon/cli/rlp/JSONtoRLP.java (86%) rename pantheon/src/main/java/tech/pegasys/pantheon/cli/{ => rlp}/RLPSubCommand.java (82%) create mode 100644 pantheon/src/main/java/tech/pegasys/pantheon/cli/rlp/RLPType.java create mode 100644 pantheon/src/main/java/tech/pegasys/pantheon/cli/rlp/RLPTypeProvider.java diff --git a/acceptance-tests/build.gradle b/acceptance-tests/build.gradle index 377aa96f08..1a6e05e91b 100644 --- a/acceptance-tests/build.gradle +++ b/acceptance-tests/build.gradle @@ -25,7 +25,6 @@ dependencies { testImplementation project(':ethereum:blockcreation') testImplementation project(':ethereum:jsonrpc') testImplementation project(':ethereum:permissioning') - testImplementation project(':ethereum:rlp') testImplementation project(':metrics') testImplementation project(':pantheon') testImplementation project(':util') diff --git a/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/IbftExtraData.java b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/IbftExtraData.java index 0b158c5f45..34b8f8a203 100644 --- a/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/IbftExtraData.java +++ b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/IbftExtraData.java @@ -18,7 +18,6 @@ import tech.pegasys.pantheon.ethereum.core.Address; import tech.pegasys.pantheon.ethereum.rlp.BytesValueRLPInput; import tech.pegasys.pantheon.ethereum.rlp.BytesValueRLPOutput; -import tech.pegasys.pantheon.ethereum.rlp.RLPEncodable; import tech.pegasys.pantheon.ethereum.rlp.RLPInput; import tech.pegasys.pantheon.util.bytes.BytesValue; @@ -28,14 +27,11 @@ import java.util.Optional; import java.util.StringJoiner; -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; - /** * Represents the data structure stored in the extraData field of the BlockHeader used when * operating under an IBFT 2.0 consensus mechanism. */ -public class IbftExtraData implements RLPEncodable { +public class IbftExtraData { public static final int EXTRA_VANITY_LENGTH = 32; @@ -83,7 +79,6 @@ public static IbftExtraData decode(final BytesValue input) { return new IbftExtraData(vanityData, seals, vote, round, validators); } - @Override public BytesValue encode() { return encode(EncodingType.ALL); } @@ -125,15 +120,11 @@ private BytesValue encode(final EncodingType encodingType) { return encoder.encoded(); } - @JsonCreator - public static IbftExtraData createGenesisExtraData( - @JsonProperty("validators") final Collection
validators) { - return new IbftExtraData( - BytesValue.wrap(new byte[32]), Collections.emptyList(), Optional.empty(), 0, validators); - } - public static String createGenesisExtraDataString(final List
validators) { - return createGenesisExtraData(validators).encode().toString(); + return new IbftExtraData( + BytesValue.wrap(new byte[32]), Collections.emptyList(), Optional.empty(), 0, validators) + .encode() + .toString(); } // Accessors 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 a28d28bb95..c9e99969a1 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; 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..30947dcfd1 --- /dev/null +++ b/pantheon/src/main/java/tech/pegasys/pantheon/cli/rlp/IbftExtraDataCLIAdapter.java @@ -0,0 +1,50 @@ +/* + * 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.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +public class IbftExtraDataCLIAdapter implements JSONtoRLP { + + private final Collection validators; + + @JsonCreator + IbftExtraDataCLIAdapter(@JsonProperty("validators") final Collection validators) { + this.validators = validators; + } + + @Override + public BytesValue encode() { + List
validators = + this.validators.stream().map(Address::fromHexString).collect(Collectors.toList()); + return new IbftExtraData( + BytesValue.wrap(new byte[32]), Collections.emptyList(), Optional.empty(), 0, validators) + .encode(); + } + + @Override + public Class getType() { + return this.getClass(); + } +} diff --git a/ethereum/rlp/src/main/java/tech/pegasys/pantheon/ethereum/rlp/RLPEncodable.java b/pantheon/src/main/java/tech/pegasys/pantheon/cli/rlp/JSONtoRLP.java similarity index 86% rename from ethereum/rlp/src/main/java/tech/pegasys/pantheon/ethereum/rlp/RLPEncodable.java rename to pantheon/src/main/java/tech/pegasys/pantheon/cli/rlp/JSONtoRLP.java index e1717f0ada..906f1ac32a 100644 --- a/ethereum/rlp/src/main/java/tech/pegasys/pantheon/ethereum/rlp/RLPEncodable.java +++ b/pantheon/src/main/java/tech/pegasys/pantheon/cli/rlp/JSONtoRLP.java @@ -10,11 +10,12 @@ * 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.ethereum.rlp; +package tech.pegasys.pantheon.cli.rlp; import tech.pegasys.pantheon.util.bytes.BytesValue; -public interface RLPEncodable { - +interface JSONtoRLP { BytesValue encode(); + + Class getType(); } diff --git a/pantheon/src/main/java/tech/pegasys/pantheon/cli/RLPSubCommand.java b/pantheon/src/main/java/tech/pegasys/pantheon/cli/rlp/RLPSubCommand.java similarity index 82% rename from pantheon/src/main/java/tech/pegasys/pantheon/cli/RLPSubCommand.java rename to pantheon/src/main/java/tech/pegasys/pantheon/cli/rlp/RLPSubCommand.java index cdc3755a4e..a386cfcb6a 100644 --- a/pantheon/src/main/java/tech/pegasys/pantheon/cli/RLPSubCommand.java +++ b/pantheon/src/main/java/tech/pegasys/pantheon/cli/rlp/RLPSubCommand.java @@ -10,15 +10,14 @@ * 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; +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.RLPSubCommand.EncodeSubCommand; -import tech.pegasys.pantheon.consensus.ibft.IbftExtraData; -import tech.pegasys.pantheon.ethereum.rlp.RLPEncodable; +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; @@ -48,9 +47,9 @@ description = "This command provides RLP data related actions.", mixinStandardHelpOptions = true, subcommands = {EncodeSubCommand.class}) -class RLPSubCommand implements Runnable { +public class RLPSubCommand implements Runnable { - static final String COMMAND_NAME = "rlp"; + public static final String COMMAND_NAME = "rlp"; private static final Logger LOG = LogManager.getLogger(); private final PrintStream out; @@ -64,7 +63,7 @@ class RLPSubCommand implements Runnable { @Spec private CommandSpec spec; - RLPSubCommand(final PrintStream out, final InputStream in) { + public RLPSubCommand(final PrintStream out, final InputStream in) { this.out = out; this.in = in; } @@ -93,6 +92,13 @@ static class EncodeSubCommand implements Runnable { @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, @@ -103,7 +109,7 @@ static class EncodeSubCommand implements Runnable { @Option( names = "--to", paramLabel = MANDATORY_FILE_FORMAT_HELP, - description = "File to write encoded RPL string to.", + description = "File to write encoded RLP string to.", arity = "1..1") private File rlpTargetFile = null; @@ -140,23 +146,25 @@ private void readInput() { private void encode(final String jsonInput) { // map the json to the object matching the type option - // the object must be an RLPEncodable object + // the object must be an JSONToRLP object if (jsonInput == null || jsonInput.isEmpty()) { throw new ExecutionException( spec.commandLine(), "An error occurred while trying to read the JSON data."); } else { try { JsonObject jsonObject = new JsonObject(jsonInput); - RLPEncodable objectToEncode = jsonObject.mapTo(IbftExtraData.class); - // encode and write the value + JSONtoRLP objectToEncode = jsonObject.mapTo(type.getType()); + // // encode and write the value writeOutput(objectToEncode.encode()); } catch (DecodeException e) { throw new ParameterException( - spec.commandLine(), "Unable to load the JSON data. Please check JSON input format."); + spec.commandLine(), + "Unable to load the JSON data. Please check JSON input format. " + e.getMessage()); } catch (IllegalArgumentException e) { throw new ParameterException( spec.commandLine(), - "Unable to map the JSON data with IbftExtraData type. Please check JSON input format."); + "Unable to map the JSON data with IbftExtraData type. Please check JSON input format. " + + e.getMessage()); } } } @@ -170,7 +178,8 @@ private void writeOutput(final BytesValue rlpEncodedOutput) { fileWriter.write(rlpEncodedOutput.toString()); } catch (final IOException e) { throw new ParameterException( - spec.commandLine(), "An error occurred while trying to write the RLP string"); + 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..0fa57ae279 --- /dev/null +++ b/pantheon/src/main/java/tech/pegasys/pantheon/cli/rlp/RLPType.java @@ -0,0 +1,22 @@ +/* + * 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; + +public enum RLPType implements RLPTypeProvider { + IBFT_EXTRA_DATA { + @Override + public Class getType() { + return IbftExtraDataCLIAdapter.class; + } + }; +} diff --git a/pantheon/src/main/java/tech/pegasys/pantheon/cli/rlp/RLPTypeProvider.java b/pantheon/src/main/java/tech/pegasys/pantheon/cli/rlp/RLPTypeProvider.java new file mode 100644 index 0000000000..9842579c46 --- /dev/null +++ b/pantheon/src/main/java/tech/pegasys/pantheon/cli/rlp/RLPTypeProvider.java @@ -0,0 +1,17 @@ +/* + * 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; + +public interface RLPTypeProvider { + Class getType(); +} diff --git a/pantheon/src/test/java/tech/pegasys/pantheon/cli/RLPSubCommandTest.java b/pantheon/src/test/java/tech/pegasys/pantheon/cli/RLPSubCommandTest.java index d9c16028c1..98e079cce6 100644 --- a/pantheon/src/test/java/tech/pegasys/pantheon/cli/RLPSubCommandTest.java +++ b/pantheon/src/test/java/tech/pegasys/pantheon/cli/RLPSubCommandTest.java @@ -24,13 +24,6 @@ import picocli.CommandLine.Model.CommandSpec; public class RLPSubCommandTest extends CommandTestAbstract { - // [ "0000000000000000000000000000000000000000000000000000000000000000", - // [ "be068f726a13c8d46c44be6ce9d275600e1735a4", - // "5ff6f4b66a46a2b2310a6f3a93aaddc0d9a1c193" ], - // "", - // "00000000", - // [] - // ] private static final String EXPECTED_RLP_USAGE = "Usage: pantheon rlp [-hV] [COMMAND]" @@ -45,6 +38,23 @@ public class RLPSubCommandTest extends CommandTestAbstract { + 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"; @@ -74,11 +84,18 @@ public void callingRPLSubCommandHelpMustDisplayUsage() { } // 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 callingRLPEncodeSubCommandWithoutPathMustWriteToStandardOutput() { String jsonInput = - "{\"validators\":[ \"be068f726a13c8d46c44be6ce9d275600e1735a4\",\n" + "{\"validators\":[\"be068f726a13c8d46c44be6ce9d275600e1735a4\",\n" + "\"5ff6f4b66a46a2b2310a6f3a93aaddc0d9a1c193\" ]}"; // set stdin @@ -99,7 +116,9 @@ public void callingRLPEncodeSubCommandWithFilePathMustWriteInThisFile() throws E final File file = File.createTempFile("ibftExtraData", "rlp"); String jsonInput = - "{\"validators\":[ \"be068f726a13c8d46c44be6ce9d275600e1735a4\",\n" + // "[\"be068f726a13c8d46c44be6ce9d275600e1735a4\",\n" + // + "\"5ff6f4b66a46a2b2310a6f3a93aaddc0d9a1c193\" ]"; + "{\"validators\":[\"be068f726a13c8d46c44be6ce9d275600e1735a4\",\n" + "\"5ff6f4b66a46a2b2310a6f3a93aaddc0d9a1c193\" ]}"; // set stdin @@ -117,6 +136,11 @@ public void callingRLPEncodeSubCommandWithFilePathMustWriteInThisFile() throws E assertThat(commandErrorOutput.toString()).isEmpty(); } + // TODO + // - test with input from file + // - test with invalid inputs + // - test with no input + @After public void restoreStdin() { System.setIn(System.in); From 2da0791868126096abf64bc3d891bf03adb47c25 Mon Sep 17 00:00:00 2001 From: Nicolas MASSART Date: Mon, 25 Feb 2019 14:32:13 +0100 Subject: [PATCH 4/7] Added CLI ref doc --- docs/Reference/Pantheon-CLI-Syntax.md | 89 ++++++++++++++++++- .../pantheon/cli/RLPSubCommandTest.java | 2 - 2 files changed, 88 insertions(+), 3 deletions(-) diff --git a/docs/Reference/Pantheon-CLI-Syntax.md b/docs/Reference/Pantheon-CLI-Syntax.md index b674ae907a..4ec3f63eb4 100644 --- a/docs/Reference/Pantheon-CLI-Syntax.md +++ b/docs/Reference/Pantheon-CLI-Syntax.md @@ -1056,4 +1056,91 @@ $ 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 all the validator + addresses 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":"", + "$id":"http://tech.pegasys.pantheon/cli_rlp_ibft_extra_data.json", + "type":"object", + "title":"IBFT extra data", + "description":"JSON format used as input to generate an IBFT extra data RLP string", + "default":null, + "definitions":{ + + }, + "required":[ + "validators" + ], + "properties":{ + "validators":{ + "$id":"#/properties/validators", + "type":"array", + "title":"Validators addresses list", + "description":"List of validator addresses to use in the extra data", + "default":null, + "items":{ + "$id":"#/properties/validators/items", + "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" + {"validators":[ + "be068f726a13c8d46c44be6ce9d275600e1735a4", + "5ff6f4b66a46a2b2310a6f3a93aaddc0d9a1c193" + ]} + ``` + + ``` tab="RLP output" + 0xf853a00000000000000000000000000000000000000000000000000000000000000000ea94be068f726a13c8d46c44be6ce9d275600e1735a4945ff6f4b66a46a2b2310a6f3a93aaddc0d9a1c193808400000000c0 + ``` \ No newline at end of file diff --git a/pantheon/src/test/java/tech/pegasys/pantheon/cli/RLPSubCommandTest.java b/pantheon/src/test/java/tech/pegasys/pantheon/cli/RLPSubCommandTest.java index 98e079cce6..57c6558fba 100644 --- a/pantheon/src/test/java/tech/pegasys/pantheon/cli/RLPSubCommandTest.java +++ b/pantheon/src/test/java/tech/pegasys/pantheon/cli/RLPSubCommandTest.java @@ -116,8 +116,6 @@ public void callingRLPEncodeSubCommandWithFilePathMustWriteInThisFile() throws E final File file = File.createTempFile("ibftExtraData", "rlp"); String jsonInput = - // "[\"be068f726a13c8d46c44be6ce9d275600e1735a4\",\n" - // + "\"5ff6f4b66a46a2b2310a6f3a93aaddc0d9a1c193\" ]"; "{\"validators\":[\"be068f726a13c8d46c44be6ce9d275600e1735a4\",\n" + "\"5ff6f4b66a46a2b2310a6f3a93aaddc0d9a1c193\" ]}"; From 3fa8550f615e6e2584e4e1493b221008c53d7d7a Mon Sep 17 00:00:00 2001 From: Nicolas MASSART Date: Mon, 25 Feb 2019 14:56:40 +0100 Subject: [PATCH 5/7] Add more test and change an error type to parameter error --- .../pantheon/cli/rlp/RLPSubCommand.java | 2 +- .../pantheon/cli/RLPSubCommandTest.java | 92 ++++++++++++++++--- 2 files changed, 81 insertions(+), 13 deletions(-) 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 index a386cfcb6a..22e017428d 100644 --- a/pantheon/src/main/java/tech/pegasys/pantheon/cli/rlp/RLPSubCommand.java +++ b/pantheon/src/main/java/tech/pegasys/pantheon/cli/rlp/RLPSubCommand.java @@ -148,7 +148,7 @@ private void encode(final String jsonInput) { // map the json to the object matching the type option // the object must be an JSONToRLP object if (jsonInput == null || jsonInput.isEmpty()) { - throw new ExecutionException( + throw new ParameterException( spec.commandLine(), "An error occurred while trying to read the JSON data."); } else { try { diff --git a/pantheon/src/test/java/tech/pegasys/pantheon/cli/RLPSubCommandTest.java b/pantheon/src/test/java/tech/pegasys/pantheon/cli/RLPSubCommandTest.java index 57c6558fba..cbbebf125a 100644 --- a/pantheon/src/test/java/tech/pegasys/pantheon/cli/RLPSubCommandTest.java +++ b/pantheon/src/test/java/tech/pegasys/pantheon/cli/RLPSubCommandTest.java @@ -16,8 +16,10 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.contentOf; +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; @@ -61,7 +63,7 @@ public class RLPSubCommandTest extends CommandTestAbstract { // RLP sub-command @Test public void rlpSubCommandExistAnbHaveSubCommands() { - CommandSpec spec = parseCommand(); + final CommandSpec spec = parseCommand(); assertThat(spec.subcommands()).containsKeys(RLP_SUBCOMMAND_NAME); assertThat(spec.subcommands().get(RLP_SUBCOMMAND_NAME).getSubcommands()) .containsKeys(RLP_ENCODE_SUBCOMMAND_NAME); @@ -94,16 +96,16 @@ public void callingRPLEncodeSubCommandHelpMustDisplayUsage() { @Test public void callingRLPEncodeSubCommandWithoutPathMustWriteToStandardOutput() { - String jsonInput = + final String jsonInput = "{\"validators\":[\"be068f726a13c8d46c44be6ce9d275600e1735a4\",\n" + "\"5ff6f4b66a46a2b2310a6f3a93aaddc0d9a1c193\" ]}"; // set stdin - ByteArrayInputStream stdIn = new ByteArrayInputStream(jsonInput.getBytes(UTF_8)); + final ByteArrayInputStream stdIn = new ByteArrayInputStream(jsonInput.getBytes(UTF_8)); parseCommand(stdIn, RLP_SUBCOMMAND_NAME, RLP_ENCODE_SUBCOMMAND_NAME); - String expectedRlpString = + final String expectedRlpString = "0xf853a00000000000000000000000000000000000000000000000000000000000000000ea94be068f726a13c8d" + "46c44be6ce9d275600e1735a4945ff6f4b66a46a2b2310a6f3a93aaddc0d9a1c193808400000000c0"; assertThat(commandOutput.toString()).contains(expectedRlpString); @@ -111,20 +113,20 @@ public void callingRLPEncodeSubCommandWithoutPathMustWriteToStandardOutput() { } @Test - public void callingRLPEncodeSubCommandWithFilePathMustWriteInThisFile() throws Exception { + public void callingRLPEncodeSubCommandWithOutputFileMustWriteInThisFile() throws Exception { final File file = File.createTempFile("ibftExtraData", "rlp"); - String jsonInput = + final String jsonInput = "{\"validators\":[\"be068f726a13c8d46c44be6ce9d275600e1735a4\",\n" + "\"5ff6f4b66a46a2b2310a6f3a93aaddc0d9a1c193\" ]}"; // set stdin - ByteArrayInputStream stdIn = new ByteArrayInputStream(jsonInput.getBytes(UTF_8)); + final ByteArrayInputStream stdIn = new ByteArrayInputStream(jsonInput.getBytes(UTF_8)); parseCommand(stdIn, RLP_SUBCOMMAND_NAME, RLP_ENCODE_SUBCOMMAND_NAME, "--to", file.getPath()); - String expectedRlpString = + final String expectedRlpString = "0xf853a00000000000000000000000000000000000000000000000000000000000000000ea94be068f726a13c8d" + "46c44be6ce9d275600e1735a4945ff6f4b66a46a2b2310a6f3a93aaddc0d9a1c193808400000000c0"; @@ -134,10 +136,76 @@ public void callingRLPEncodeSubCommandWithFilePathMustWriteInThisFile() throws E assertThat(commandErrorOutput.toString()).isEmpty(); } - // TODO - // - test with input from file - // - test with invalid inputs - // - test with no input + @Test + public void callingRLPEncodeSubCommandWithInputFilePathMustReadFromThisFile() throws Exception { + + final File tempJsonFile = temp.newFile("test.json"); + try (final BufferedWriter fileWriter = Files.newBufferedWriter(tempJsonFile.toPath(), UTF_8)) { + + fileWriter.write( + "{\"validators\":[\n" + + " \"be068f726a13c8d46c44be6ce9d275600e1735a4\",\n" + + " \"5ff6f4b66a46a2b2310a6f3a93aaddc0d9a1c193\"\n" + + "]}"); + + 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 callingRLPEncodeSubCommandWithInvalidInputMustRaiseAnError() throws Exception { + + final File tempJsonFile = temp.newFile("invalid_test.json"); + try (final BufferedWriter fileWriter = Files.newBufferedWriter(tempJsonFile.toPath(), UTF_8)) { + + fileWriter.write("{\"validators\":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 IbftExtraData type. Please check JSON input format."); + } + } + + @Test + public void callingRLPEncodeSubCommandWithEmptyInputMustRaiseAnError() 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 callingRLPEncodeSubCommandWithEmptyStdInputMustRaiseAnError() 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() { From 94396405594f0c2d2c74cfcb7dbd46d611e59e09 Mon Sep 17 00:00:00 2001 From: Nicolas MASSART Date: Mon, 25 Feb 2019 15:39:18 +0100 Subject: [PATCH 6/7] remove some useless changes and added doc moved the provider interface into the enum as only used by it for impl purpose --- .../consensus/ibft/IbftExtraData.java | 12 ++++++--- .../pegasys/pantheon/cli/PantheonCommand.java | 2 +- .../pantheon/cli/PasswordSubCommand.java | 2 +- .../cli/rlp/IbftExtraDataCLIAdapter.java | 14 ++++++----- .../rlp/{JSONtoRLP.java => JSONToRLP.java} | 16 ++++++++++-- .../pantheon/cli/rlp/RLPSubCommand.java | 25 ++++++++++++++++--- .../pegasys/pantheon/cli/rlp/RLPType.java | 9 ++++++- .../pantheon/cli/rlp/RLPTypeProvider.java | 17 ------------- .../pantheon/cli/CommandTestAbstract.java | 8 +++--- .../cli/{ => rlp}/RLPSubCommandTest.java | 4 ++- 10 files changed, 68 insertions(+), 41 deletions(-) rename pantheon/src/main/java/tech/pegasys/pantheon/cli/rlp/{JSONtoRLP.java => JSONToRLP.java} (64%) delete mode 100644 pantheon/src/main/java/tech/pegasys/pantheon/cli/rlp/RLPTypeProvider.java rename pantheon/src/test/java/tech/pegasys/pantheon/cli/{ => rlp}/RLPSubCommandTest.java (98%) diff --git a/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/IbftExtraData.java b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/IbftExtraData.java index 34b8f8a203..aac6b87d79 100644 --- a/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/IbftExtraData.java +++ b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/IbftExtraData.java @@ -121,10 +121,14 @@ private BytesValue encode(final EncodingType encodingType) { } public static String createGenesisExtraDataString(final List
validators) { - return new IbftExtraData( - BytesValue.wrap(new byte[32]), Collections.emptyList(), Optional.empty(), 0, validators) - .encode() - .toString(); + final IbftExtraData extraData = + new IbftExtraData( + BytesValue.wrap(new byte[32]), + Collections.emptyList(), + Optional.empty(), + 0, + validators); + return extraData.encode().toString(); } // Accessors 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 c9e99969a1..0d8d553ab4 100644 --- a/pantheon/src/main/java/tech/pegasys/pantheon/cli/PantheonCommand.java +++ b/pantheon/src/main/java/tech/pegasys/pantheon/cli/PantheonCommand.java @@ -105,7 +105,7 @@ public class PantheonCommand implements DefaultCommandValues, Runnable { private final Logger logger; - CommandLine commandLine; + private CommandLine commandLine; public static class RpcApisConverter implements ITypeConverter { diff --git a/pantheon/src/main/java/tech/pegasys/pantheon/cli/PasswordSubCommand.java b/pantheon/src/main/java/tech/pegasys/pantheon/cli/PasswordSubCommand.java index 3794b87590..5bc8e93566 100644 --- a/pantheon/src/main/java/tech/pegasys/pantheon/cli/PasswordSubCommand.java +++ b/pantheon/src/main/java/tech/pegasys/pantheon/cli/PasswordSubCommand.java @@ -43,7 +43,7 @@ class PasswordSubCommand implements Runnable { @Spec private CommandSpec spec; - private final PrintStream out; + final PrintStream out; PasswordSubCommand(final PrintStream out) { this.out = out; 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 index 30947dcfd1..d9db9a8b26 100644 --- a/pantheon/src/main/java/tech/pegasys/pantheon/cli/rlp/IbftExtraDataCLIAdapter.java +++ b/pantheon/src/main/java/tech/pegasys/pantheon/cli/rlp/IbftExtraDataCLIAdapter.java @@ -25,26 +25,28 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; -public class IbftExtraDataCLIAdapter implements JSONtoRLP { +/** + * Adapter to convert a typed JSON to an IbftExtraData object This adapter handles the JSON to RLP + * encoding + */ +public class IbftExtraDataCLIAdapter implements JSONToRLP { - private final Collection validators; + private final List
validators; @JsonCreator IbftExtraDataCLIAdapter(@JsonProperty("validators") final Collection validators) { - this.validators = validators; + this.validators = validators.stream().map(Address::fromHexString).collect(Collectors.toList()); } @Override public BytesValue encode() { - List
validators = - this.validators.stream().map(Address::fromHexString).collect(Collectors.toList()); return new IbftExtraData( BytesValue.wrap(new byte[32]), Collections.emptyList(), Optional.empty(), 0, validators) .encode(); } @Override - public Class getType() { + public Class getType() { return this.getClass(); } } 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 similarity index 64% rename from pantheon/src/main/java/tech/pegasys/pantheon/cli/rlp/JSONtoRLP.java rename to pantheon/src/main/java/tech/pegasys/pantheon/cli/rlp/JSONToRLP.java index 906f1ac32a..85bd680a2c 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 @@ -14,8 +14,20 @@ import tech.pegasys.pantheon.util.bytes.BytesValue; -interface JSONtoRLP { +/** 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(); - Class getType(); + /** + * Gives the type of the object that's being encoded + * + * @return the type of the current object that must be a type implementing this interface + */ + Class getType(); } 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 index 22e017428d..dbdaeb0c9e 100644 --- a/pantheon/src/main/java/tech/pegasys/pantheon/cli/rlp/RLPSubCommand.java +++ b/pantheon/src/main/java/tech/pegasys/pantheon/cli/rlp/RLPSubCommand.java @@ -119,6 +119,10 @@ public void run() { 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. @@ -141,20 +145,29 @@ private void readInput() { } } } + + // 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) { // map the json to the object matching the type option - // the object must be an JSONToRLP object + // the object must be a JSONToRLP object if (jsonInput == null || jsonInput.isEmpty()) { throw new ParameterException( spec.commandLine(), "An error occurred while trying to read the JSON data."); } else { try { JsonObject jsonObject = new JsonObject(jsonInput); - JSONtoRLP objectToEncode = jsonObject.mapTo(type.getType()); - // // encode and write the value + JSONToRLP objectToEncode = jsonObject.mapTo(type.getType()); + + // encode and write the value writeOutput(objectToEncode.encode()); } catch (DecodeException e) { throw new ParameterException( @@ -169,8 +182,12 @@ private void encode(final String jsonInput) { } } + /** + * 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) { - // write the encoded result to stdout or a file if the option is specified if (rlpTargetFile != null) { final Path targetPath = rlpTargetFile.toPath(); 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 index 0fa57ae279..f89eb493cd 100644 --- a/pantheon/src/main/java/tech/pegasys/pantheon/cli/rlp/RLPType.java +++ b/pantheon/src/main/java/tech/pegasys/pantheon/cli/rlp/RLPType.java @@ -12,11 +12,18 @@ */ package tech.pegasys.pantheon.cli.rlp; +/** Type of the RLP data to encode/decode */ public enum RLPType implements RLPTypeProvider { + // Enum is used to enable the listing of the possible values in PicoCLI. IBFT_EXTRA_DATA { @Override - public Class getType() { + public Class getType() { return IbftExtraDataCLIAdapter.class; } }; } + +/** Enables the behaviour of being a type provider on the enum value */ +interface RLPTypeProvider { + Class getType(); +} diff --git a/pantheon/src/main/java/tech/pegasys/pantheon/cli/rlp/RLPTypeProvider.java b/pantheon/src/main/java/tech/pegasys/pantheon/cli/rlp/RLPTypeProvider.java deleted file mode 100644 index 9842579c46..0000000000 --- a/pantheon/src/main/java/tech/pegasys/pantheon/cli/rlp/RLPTypeProvider.java +++ /dev/null @@ -1,17 +0,0 @@ -/* - * 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; - -public interface RLPTypeProvider { - Class getType(); -} 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 2ba83bc4d7..f2a0c4e125 100644 --- a/pantheon/src/test/java/tech/pegasys/pantheon/cli/CommandTestAbstract.java +++ b/pantheon/src/test/java/tech/pegasys/pantheon/cli/CommandTestAbstract.java @@ -60,10 +60,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; @@ -142,11 +142,11 @@ public void displayOutput() throws IOException { commandErrorOutput.close(); } - CommandLine.Model.CommandSpec parseCommand(final String... args) { + protected CommandLine.Model.CommandSpec parseCommand(final String... args) { return parseCommand(System.in, args); } - CommandLine.Model.CommandSpec parseCommand(final InputStream in, final String... args) { + protected CommandLine.Model.CommandSpec parseCommand(final InputStream in, final String... args) { final TestPantheonCommand pantheonCommand = new TestPantheonCommand( diff --git a/pantheon/src/test/java/tech/pegasys/pantheon/cli/RLPSubCommandTest.java b/pantheon/src/test/java/tech/pegasys/pantheon/cli/rlp/RLPSubCommandTest.java similarity index 98% rename from pantheon/src/test/java/tech/pegasys/pantheon/cli/RLPSubCommandTest.java rename to pantheon/src/test/java/tech/pegasys/pantheon/cli/rlp/RLPSubCommandTest.java index cbbebf125a..f072548e13 100644 --- a/pantheon/src/test/java/tech/pegasys/pantheon/cli/RLPSubCommandTest.java +++ b/pantheon/src/test/java/tech/pegasys/pantheon/cli/rlp/RLPSubCommandTest.java @@ -10,12 +10,14 @@ * 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; +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; From be17af403c54b605ddbca832c39a2cb7d3a2816c Mon Sep 17 00:00:00 2001 From: Nicolas MASSART Date: Mon, 25 Feb 2019 19:23:51 +0100 Subject: [PATCH 7/7] updated to enable specific implementation of json mapping for each type update doc accordingly --- docs/Reference/Pantheon-CLI-Syntax.md | 59 +++++++------------ .../cli/rlp/IbftExtraDataCLIAdapter.java | 27 ++++----- .../pegasys/pantheon/cli/rlp/JSONToRLP.java | 11 +--- .../pantheon/cli/rlp/RLPSubCommand.java | 20 +++---- .../pegasys/pantheon/cli/rlp/RLPType.java | 23 ++++---- .../pantheon/cli/rlp/RLPSubCommandTest.java | 27 ++++----- 6 files changed, 66 insertions(+), 101 deletions(-) diff --git a/docs/Reference/Pantheon-CLI-Syntax.md b/docs/Reference/Pantheon-CLI-Syntax.md index 4ec3f63eb4..d513ba66f4 100644 --- a/docs/Reference/Pantheon-CLI-Syntax.md +++ b/docs/Reference/Pantheon-CLI-Syntax.md @@ -1084,8 +1084,8 @@ 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 all the validator - addresses that you want to insert in your genesis. + 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. @@ -1097,48 +1097,33 @@ This data is used to build the IBFT 2.0 genesis file. ```json { - "$schema":"", - "$id":"http://tech.pegasys.pantheon/cli_rlp_ibft_extra_data.json", - "type":"object", - "title":"IBFT extra data", - "description":"JSON format used as input to generate an IBFT extra data RLP string", - "default":null, - "definitions":{ - - }, - "required":[ - "validators" - ], - "properties":{ - "validators":{ - "$id":"#/properties/validators", - "type":"array", - "title":"Validators addresses list", - "description":"List of validator addresses to use in the extra data", - "default":null, - "items":{ - "$id":"#/properties/validators/items", - "type":"string", - "title":"Validator address", - "description":"The validator node address", - "default":"", - "examples":[ - "be068f726a13c8d46c44be6ce9d275600e1735a4", - "5ff6f4b66a46a2b2310a6f3a93aaddc0d9a1c193" - ], - "pattern":"^([0-9a-f]{40})$" - } - } - } + "$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" - {"validators":[ + [ "be068f726a13c8d46c44be6ce9d275600e1735a4", "5ff6f4b66a46a2b2310a6f3a93aaddc0d9a1c193" - ]} + ] ``` ``` tab="RLP output" 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 index d9db9a8b26..9230d67600 100644 --- a/pantheon/src/main/java/tech/pegasys/pantheon/cli/rlp/IbftExtraDataCLIAdapter.java +++ b/pantheon/src/main/java/tech/pegasys/pantheon/cli/rlp/IbftExtraDataCLIAdapter.java @@ -16,14 +16,14 @@ 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.List; import java.util.Optional; import java.util.stream.Collectors; -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; +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 @@ -31,22 +31,17 @@ */ public class IbftExtraDataCLIAdapter implements JSONToRLP { - private final List
validators; + @Override + public BytesValue encode(final String json) throws IOException { + ObjectMapper mapper = new ObjectMapper(); + TypeReference> typeRef = new TypeReference>() {}; + Collection validatorAddresse = mapper.readValue(json, typeRef); - @JsonCreator - IbftExtraDataCLIAdapter(@JsonProperty("validators") final Collection validators) { - this.validators = validators.stream().map(Address::fromHexString).collect(Collectors.toList()); - } + Collection
addresses = + validatorAddresse.stream().map(Address::fromHexString).collect(Collectors.toList()); - @Override - public BytesValue encode() { return new IbftExtraData( - BytesValue.wrap(new byte[32]), Collections.emptyList(), Optional.empty(), 0, validators) + BytesValue.wrap(new byte[32]), Collections.emptyList(), Optional.empty(), 0, addresses) .encode(); } - - @Override - public Class getType() { - return this.getClass(); - } } 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 85bd680a2c..7349748b1b 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 @@ -14,6 +14,8 @@ import tech.pegasys.pantheon.util.bytes.BytesValue; +import java.io.IOException; + /** Behaviour of objects that can be encoded from JSON to RLP */ interface JSONToRLP { @@ -22,12 +24,5 @@ interface JSONToRLP { * * @return the RLP encoded object. */ - BytesValue encode(); - - /** - * Gives the type of the object that's being encoded - * - * @return the type of the current object that must be a type implementing this interface - */ - Class getType(); + 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 index dbdaeb0c9e..ebfbfd3716 100644 --- a/pantheon/src/main/java/tech/pegasys/pantheon/cli/rlp/RLPSubCommand.java +++ b/pantheon/src/main/java/tech/pegasys/pantheon/cli/rlp/RLPSubCommand.java @@ -30,8 +30,7 @@ import java.nio.file.Path; import java.util.Scanner; -import io.vertx.core.json.DecodeException; -import io.vertx.core.json.JsonObject; +import com.fasterxml.jackson.databind.exc.MismatchedInputException; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import picocli.CommandLine.Command; @@ -157,27 +156,22 @@ private void readInput() { * @param jsonInput the JSON string data to encode */ private void encode(final String jsonInput) { - // map the json to the object matching the type option - // the object must be a JSONToRLP object if (jsonInput == null || jsonInput.isEmpty()) { throw new ParameterException( spec.commandLine(), "An error occurred while trying to read the JSON data."); } else { try { - JsonObject jsonObject = new JsonObject(jsonInput); - JSONToRLP objectToEncode = jsonObject.mapTo(type.getType()); - // encode and write the value - writeOutput(objectToEncode.encode()); - } catch (DecodeException e) { + writeOutput(type.getAdapter().encode(jsonInput)); + } catch (MismatchedInputException e) { throw new ParameterException( spec.commandLine(), - "Unable to load the JSON data. Please check JSON input format. " + e.getMessage()); - } catch (IllegalArgumentException e) { + "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 map the JSON data with IbftExtraData type. Please check JSON input format. " - + e.getMessage()); + "Unable to load the JSON data. Please check JSON input format. " + e); } } } 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 index f89eb493cd..c2115278db 100644 --- a/pantheon/src/main/java/tech/pegasys/pantheon/cli/rlp/RLPType.java +++ b/pantheon/src/main/java/tech/pegasys/pantheon/cli/rlp/RLPType.java @@ -13,17 +13,18 @@ package tech.pegasys.pantheon.cli.rlp; /** Type of the RLP data to encode/decode */ -public enum RLPType implements RLPTypeProvider { +public enum RLPType { // Enum is used to enable the listing of the possible values in PicoCLI. - IBFT_EXTRA_DATA { - @Override - public Class getType() { - return IbftExtraDataCLIAdapter.class; - } - }; -} + IBFT_EXTRA_DATA(new IbftExtraDataCLIAdapter()); + + private final JSONToRLP adapter; + + RLPType(final JSONToRLP adapter) { + + this.adapter = adapter; + } -/** Enables the behaviour of being a type provider on the enum value */ -interface RLPTypeProvider { - Class getType(); + public JSONToRLP getAdapter() { + return adapter; + } } 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 index f072548e13..93c1bae29c 100644 --- a/pantheon/src/test/java/tech/pegasys/pantheon/cli/rlp/RLPSubCommandTest.java +++ b/pantheon/src/test/java/tech/pegasys/pantheon/cli/rlp/RLPSubCommandTest.java @@ -96,11 +96,10 @@ public void callingRPLEncodeSubCommandHelpMustDisplayUsage() { } @Test - public void callingRLPEncodeSubCommandWithoutPathMustWriteToStandardOutput() { + public void encodeWithoutPathMustWriteToStandardOutput() { final String jsonInput = - "{\"validators\":[\"be068f726a13c8d46c44be6ce9d275600e1735a4\",\n" - + "\"5ff6f4b66a46a2b2310a6f3a93aaddc0d9a1c193\" ]}"; + "[\"be068f726a13c8d46c44be6ce9d275600e1735a4\", \"5ff6f4b66a46a2b2310a6f3a93aaddc0d9a1c193\"]"; // set stdin final ByteArrayInputStream stdIn = new ByteArrayInputStream(jsonInput.getBytes(UTF_8)); @@ -115,13 +114,12 @@ public void callingRLPEncodeSubCommandWithoutPathMustWriteToStandardOutput() { } @Test - public void callingRLPEncodeSubCommandWithOutputFileMustWriteInThisFile() throws Exception { + public void encodeWithOutputFileMustWriteInThisFile() throws Exception { final File file = File.createTempFile("ibftExtraData", "rlp"); final String jsonInput = - "{\"validators\":[\"be068f726a13c8d46c44be6ce9d275600e1735a4\",\n" - + "\"5ff6f4b66a46a2b2310a6f3a93aaddc0d9a1c193\" ]}"; + "[\"be068f726a13c8d46c44be6ce9d275600e1735a4\", \"5ff6f4b66a46a2b2310a6f3a93aaddc0d9a1c193\"]"; // set stdin final ByteArrayInputStream stdIn = new ByteArrayInputStream(jsonInput.getBytes(UTF_8)); @@ -139,16 +137,13 @@ public void callingRLPEncodeSubCommandWithOutputFileMustWriteInThisFile() throws } @Test - public void callingRLPEncodeSubCommandWithInputFilePathMustReadFromThisFile() throws Exception { + public void encodeWithInputFilePathMustReadFromThisFile() throws Exception { final File tempJsonFile = temp.newFile("test.json"); try (final BufferedWriter fileWriter = Files.newBufferedWriter(tempJsonFile.toPath(), UTF_8)) { fileWriter.write( - "{\"validators\":[\n" - + " \"be068f726a13c8d46c44be6ce9d275600e1735a4\",\n" - + " \"5ff6f4b66a46a2b2310a6f3a93aaddc0d9a1c193\"\n" - + "]}"); + "[\"be068f726a13c8d46c44be6ce9d275600e1735a4\", \"5ff6f4b66a46a2b2310a6f3a93aaddc0d9a1c193\"]"); fileWriter.flush(); @@ -164,12 +159,12 @@ public void callingRLPEncodeSubCommandWithInputFilePathMustReadFromThisFile() th } @Test - public void callingRLPEncodeSubCommandWithInvalidInputMustRaiseAnError() throws Exception { + 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("{\"validators\":0}"); + fileWriter.write("{\"property\":0}"); fileWriter.flush(); @@ -179,12 +174,12 @@ public void callingRLPEncodeSubCommandWithInvalidInputMustRaiseAnError() throws assertThat(commandOutput.toString()).isEmpty(); assertThat(commandErrorOutput.toString()) .startsWith( - "Unable to map the JSON data with IbftExtraData type. Please check JSON input format."); + "Unable to map the JSON data with selected type. Please check JSON input format."); } } @Test - public void callingRLPEncodeSubCommandWithEmptyInputMustRaiseAnError() throws Exception { + public void encodeWithEmptyInputMustRaiseAnError() throws Exception { final File tempJsonFile = temp.newFile("empty.json"); @@ -196,7 +191,7 @@ public void callingRLPEncodeSubCommandWithEmptyInputMustRaiseAnError() throws Ex } @Test - public void callingRLPEncodeSubCommandWithEmptyStdInputMustRaiseAnError() throws Exception { + public void encodeWithEmptyStdInputMustRaiseAnError() throws Exception { // set empty stdin final String jsonInput = "";