From 88e4b60e959a07c6c01e27eeb9788852ee274b97 Mon Sep 17 00:00:00 2001 From: Brindrajsinh-Chauhan Date: Sat, 6 Apr 2024 18:58:13 -0400 Subject: [PATCH] add rlp decode subcommand --- .../rlp/IbftExtraDataCLIAdapter.java | 10 ++ .../besu/cli/subcommands/rlp/JSONToRLP.java | 11 ++ .../rlp/QbftExtraDataCLIAdapter.java | 10 ++ .../cli/subcommands/rlp/RLPSubCommand.java | 126 +++++++++++++++++- 4 files changed, 156 insertions(+), 1 deletion(-) diff --git a/besu/src/main/java/org/hyperledger/besu/cli/subcommands/rlp/IbftExtraDataCLIAdapter.java b/besu/src/main/java/org/hyperledger/besu/cli/subcommands/rlp/IbftExtraDataCLIAdapter.java index 68d88478df2..c136d0f8da4 100644 --- a/besu/src/main/java/org/hyperledger/besu/cli/subcommands/rlp/IbftExtraDataCLIAdapter.java +++ b/besu/src/main/java/org/hyperledger/besu/cli/subcommands/rlp/IbftExtraDataCLIAdapter.java @@ -14,6 +14,7 @@ */ package org.hyperledger.besu.cli.subcommands.rlp; +import org.hyperledger.besu.consensus.common.bft.BftExtraData; import org.hyperledger.besu.consensus.ibft.IbftExtraDataCodec; import org.hyperledger.besu.datatypes.Address; @@ -43,4 +44,13 @@ private Bytes fromJsonAddresses(final String jsonAddresses) throws IOException { return IbftExtraDataCodec.encodeFromAddresses( validatorAddresses.stream().map(Address::fromHexString).collect(Collectors.toList())); } + + @Override + public BftExtraData decode(final String rlpInput) throws IOException { + return fromRLPInput(rlpInput); + } + + private BftExtraData fromRLPInput(final String rlpInput) throws IOException { + return new IbftExtraDataCodec().decodeRaw(Bytes.fromHexString(rlpInput)); + } } diff --git a/besu/src/main/java/org/hyperledger/besu/cli/subcommands/rlp/JSONToRLP.java b/besu/src/main/java/org/hyperledger/besu/cli/subcommands/rlp/JSONToRLP.java index 101b442e8cf..ebf05cb24c4 100644 --- a/besu/src/main/java/org/hyperledger/besu/cli/subcommands/rlp/JSONToRLP.java +++ b/besu/src/main/java/org/hyperledger/besu/cli/subcommands/rlp/JSONToRLP.java @@ -14,6 +14,8 @@ */ package org.hyperledger.besu.cli.subcommands.rlp; +import org.hyperledger.besu.consensus.common.bft.BftExtraData; + import java.io.IOException; import org.apache.tuweni.bytes.Bytes; @@ -29,4 +31,13 @@ interface JSONToRLP { * @throws IOException if an error occurs while reading data */ Bytes encode(String json) throws IOException; + + /** + * Decodes the input RLP value into a validators list Object. + * + * @param inputData the RLP hex string to convert to validators list + * @return the decoded BFT ExtraData object. + * @throws IOException if an error occurs while reading data + */ + BftExtraData decode(String inputData) throws IOException; } diff --git a/besu/src/main/java/org/hyperledger/besu/cli/subcommands/rlp/QbftExtraDataCLIAdapter.java b/besu/src/main/java/org/hyperledger/besu/cli/subcommands/rlp/QbftExtraDataCLIAdapter.java index 9caea69d936..cf02819b2b9 100644 --- a/besu/src/main/java/org/hyperledger/besu/cli/subcommands/rlp/QbftExtraDataCLIAdapter.java +++ b/besu/src/main/java/org/hyperledger/besu/cli/subcommands/rlp/QbftExtraDataCLIAdapter.java @@ -14,6 +14,7 @@ */ package org.hyperledger.besu.cli.subcommands.rlp; +import org.hyperledger.besu.consensus.common.bft.BftExtraData; import org.hyperledger.besu.consensus.qbft.QbftExtraDataCodec; import org.hyperledger.besu.datatypes.Address; @@ -40,4 +41,13 @@ private Bytes fromJsonAddresses(final String jsonAddresses) throws IOException { return QbftExtraDataCodec.encodeFromAddresses( validatorAddresses.stream().map(Address::fromHexString).collect(Collectors.toList())); } + + @Override + public BftExtraData decode(final String rlpInput) throws IOException { + return fromRLPInput(rlpInput); + } + + private BftExtraData fromRLPInput(final String rlpInput) throws IOException { + return new QbftExtraDataCodec().decodeRaw(Bytes.fromHexString(rlpInput)); + } } diff --git a/besu/src/main/java/org/hyperledger/besu/cli/subcommands/rlp/RLPSubCommand.java b/besu/src/main/java/org/hyperledger/besu/cli/subcommands/rlp/RLPSubCommand.java index 834e238987b..759d8066340 100644 --- a/besu/src/main/java/org/hyperledger/besu/cli/subcommands/rlp/RLPSubCommand.java +++ b/besu/src/main/java/org/hyperledger/besu/cli/subcommands/rlp/RLPSubCommand.java @@ -19,8 +19,10 @@ import org.hyperledger.besu.cli.BesuCommand; import org.hyperledger.besu.cli.DefaultCommandValues; +import org.hyperledger.besu.cli.subcommands.rlp.RLPSubCommand.DecodeSubCommand; import org.hyperledger.besu.cli.subcommands.rlp.RLPSubCommand.EncodeSubCommand; import org.hyperledger.besu.cli.util.VersionProvider; +import org.hyperledger.besu.consensus.common.bft.BftExtraData; import java.io.BufferedReader; import java.io.BufferedWriter; @@ -48,7 +50,7 @@ description = "This command provides RLP data related actions.", mixinStandardHelpOptions = true, versionProvider = VersionProvider.class, - subcommands = {EncodeSubCommand.class}) + subcommands = {EncodeSubCommand.class, DecodeSubCommand.class}) public class RLPSubCommand implements Runnable { /** The constant COMMAND_NAME. */ @@ -207,4 +209,126 @@ private void writeOutput(final Bytes rlpEncodedOutput) { } } } + + /** + * RLP decode sub-command + * + *

Decode a RLP hex string into a validator list. + */ + @Command( + name = "decode", + description = "This command decodes a JSON typed RLP hex string into validator list.", + mixinStandardHelpOptions = true, + versionProvider = VersionProvider.class) + static class DecodeSubCommand 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 Decode, possible values are ${COMPLETION-CANDIDATES}. (default: ${DEFAULT-VALUE})", + arity = "1..1") + private final RLPType type = RLPType.IBFT_EXTRA_DATA; + + @Option( + names = "--from", + paramLabel = DefaultCommandValues.MANDATORY_FILE_FORMAT_HELP, + description = "File containing JSON object to decode", + arity = "1..1") + private final File jsonSourceFile = null; + + @Option( + names = "--to", + paramLabel = DefaultCommandValues.MANDATORY_FILE_FORMAT_HELP, + description = "File to write decoded RLP string to.", + arity = "1..1") + private final 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 #decode(String)} this data + */ + private void readInput() { + // if we have an output file defined, print to it + // otherwise print to defined output, usually standard output. + final String inputData; + + if (jsonSourceFile != null) { + try { + BufferedReader reader = Files.newBufferedReader(jsonSourceFile.toPath(), UTF_8); + + // Read only the first line if there are many lines + inputData = reader.readLine(); + } catch (IOException e) { + throw new ExecutionException(spec.commandLine(), "Unable to read JSON file."); + } + } else { + // get data from standard input + try (Scanner scanner = new Scanner(parentCommand.in, UTF_8.name())) { + inputData = scanner.nextLine(); + } + } + + decode(inputData); + } + + /** + * Decodes the string input into an validator data based on the {@link #type} then goes to + * {@link #writeOutput(BftExtraData)} this data to file or stdout + * + * @param inputData the string data to decode + */ + private void decode(final String inputData) { + if (inputData == null || inputData.isEmpty()) { + throw new ParameterException( + spec.commandLine(), "An error occurred while trying to read the input data."); + } else { + try { + // decode and write the value + writeOutput(type.getAdapter().decode(inputData)); + } catch (MismatchedInputException e) { + throw new ParameterException( + spec.commandLine(), + "Unable to map the input data with selected type. Please check input format. " + e); + } catch (IOException e) { + throw new ParameterException( + spec.commandLine(), "Unable to load the input data. Please check input format. " + e); + } + } + } + + /** + * write the decoded result to stdout or a file if the option is specified + * + * @param bftExtraDataOutput the BFT extra data output to write to file or stdout + */ + private void writeOutput(final BftExtraData bftExtraDataOutput) { + if (rlpTargetFile != null) { + final Path targetPath = rlpTargetFile.toPath(); + + try (final BufferedWriter fileWriter = Files.newBufferedWriter(targetPath, UTF_8)) { + fileWriter.write(bftExtraDataOutput.getValidators().toString()); + } catch (final IOException e) { + throw new ParameterException( + spec.commandLine(), + "An error occurred while trying to write the validator list. " + e.getMessage()); + } + } else { + parentCommand.out.println(bftExtraDataOutput.getValidators().toString()); + } + } + } }