diff --git a/CHANGELOG.md b/CHANGELOG.md index 0d7fdc02931..045968989cc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -40,6 +40,7 @@ - Expose transaction count by type metrics for the layered txpool [#6903](https://github.com/hyperledger/besu/pull/6903) - Expose bad block events via the BesuEvents plugin API [#6848](https://github.com/hyperledger/besu/pull/6848) - Add RPC errors metric [#6919](https://github.com/hyperledger/besu/pull/6919/) +- Add `rlp decode` subcommand to decode IBFT/QBFT extraData to validator list [#6895](https://github.com/hyperledger/besu/pull/6895) ### Bug fixes - Fix txpool dump/restore race condition [#6665](https://github.com/hyperledger/besu/pull/6665) 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 759d8066340..77d6fbca56d 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 @@ -32,6 +32,7 @@ import java.io.PrintWriter; import java.nio.file.Files; import java.nio.file.Path; +import java.util.NoSuchElementException; import java.util.Scanner; import com.fasterxml.jackson.databind.exc.MismatchedInputException; @@ -273,12 +274,16 @@ private void readInput() { // 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."); + throw new ExecutionException(spec.commandLine(), "Unable to read input file."); } } else { // get data from standard input try (Scanner scanner = new Scanner(parentCommand.in, UTF_8.name())) { inputData = scanner.nextLine(); + } catch (NoSuchElementException e) { + throw new ParameterException( + spec.commandLine(), + "Unable to read input data." + e); } } diff --git a/besu/src/test/java/org/hyperledger/besu/cli/subcommands/rlp/RLPSubCommandTest.java b/besu/src/test/java/org/hyperledger/besu/cli/subcommands/rlp/RLPSubCommandTest.java index 8c9b9aad932..6671382ebd7 100644 --- a/besu/src/test/java/org/hyperledger/besu/cli/subcommands/rlp/RLPSubCommandTest.java +++ b/besu/src/test/java/org/hyperledger/besu/cli/subcommands/rlp/RLPSubCommandTest.java @@ -68,6 +68,7 @@ public class RLPSubCommandTest extends CommandTestAbstract { private static final String RLP_SUBCOMMAND_NAME = "rlp"; private static final String RLP_ENCODE_SUBCOMMAND_NAME = "encode"; + private static final String RLP_DECODE_SUBCOMMAND_NAME = "decode"; private static final String RLP_QBFT_TYPE = "QBFT_EXTRA_DATA"; // RLP sub-command @@ -259,6 +260,166 @@ public void encodeWithEmptyStdInputMustRaiseAnError() throws Exception { .startsWith("An error occurred while trying to read the JSON data."); } + @Test + public void decodeWithoutPathMustWriteToStandardOutput() { + + final String inputData = + "0xf853a00000000000000000000000000000000000000000000000000000000000000000ea94be068f726a13c8d" + + "46c44be6ce9d275600e1735a4945ff6f4b66a46a2b2310a6f3a93aaddc0d9a1c193808400000000c0"; + + // set stdin + final ByteArrayInputStream stdIn = new ByteArrayInputStream(inputData.getBytes(UTF_8)); + + parseCommand(stdIn, RLP_SUBCOMMAND_NAME, RLP_DECODE_SUBCOMMAND_NAME); + + final String expectedValidatorString = + "[0xbe068f726a13c8d46c44be6ce9d275600e1735a4, 0x5ff6f4b66a46a2b2310a6f3a93aaddc0d9a1c193]"; + assertThat(commandOutput.toString(UTF_8)).contains(expectedValidatorString); + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + } + + @Test + public void decodeQBFTWithoutPathMustWriteToStandardOutput() { + + final String inputData = + "0xf84fa00000000000000000000000000000000000000000000000000000000000000000ea94241f804efb46f71acaa" + + "5be94a62f7798e89c3724946cdf72da457453063ea92e7fa5ac30afbcec28cdc080c0"; + + // set stdin + final ByteArrayInputStream stdIn = new ByteArrayInputStream(inputData.getBytes(UTF_8)); + + parseCommand(stdIn, RLP_SUBCOMMAND_NAME, RLP_DECODE_SUBCOMMAND_NAME, "--type", RLP_QBFT_TYPE); + + final String expectedValidatorString = + "[0x241f804efb46f71acaa5be94a62f7798e89c3724, 0x6cdf72da457453063ea92e7fa5ac30afbcec28cd]"; + assertThat(commandOutput.toString(UTF_8)).contains(expectedValidatorString); + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + } + + @Test + public void decodeWithOutputFileMustWriteInThisFile() throws Exception { + + final File file = File.createTempFile("ibftValidators", "rlp"); + + final String inputData = + "0xf853a00000000000000000000000000000000000000000000000000000000000000000ea94be068f726a13c8d" + + "46c44be6ce9d275600e1735a4945ff6f4b66a46a2b2310a6f3a93aaddc0d9a1c193808400000000c0"; + + // set stdin + final ByteArrayInputStream stdIn = new ByteArrayInputStream(inputData.getBytes(UTF_8)); + + parseCommand(stdIn, RLP_SUBCOMMAND_NAME, RLP_DECODE_SUBCOMMAND_NAME, "--to", file.getPath()); + + final String expectedValidatorString = + "[0xbe068f726a13c8d46c44be6ce9d275600e1735a4, 0x5ff6f4b66a46a2b2310a6f3a93aaddc0d9a1c193]"; + + assertThat(contentOf(file)).contains(expectedValidatorString); + + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + } + + @Test + public void decodeWithInputFilePathMustReadFromThisFile(final @TempDir Path dir) + throws Exception { + final Path tempJsonFile = Files.createTempFile(dir, "input", "json"); + try (final BufferedWriter fileWriter = Files.newBufferedWriter(tempJsonFile, UTF_8)) { + + fileWriter.write( + "0xf853a00000000000000000000000000000000000000000000000000000000000000000ea94be068f726a13c8d46c44be6ce9d275600e1735a4945ff6f4b66a46a2b2310a6f3a93aaddc0d9a1c193808400000000c0"); + + fileWriter.flush(); + + parseCommand( + RLP_SUBCOMMAND_NAME, + RLP_DECODE_SUBCOMMAND_NAME, + "--from", + tempJsonFile.toFile().getAbsolutePath()); + + final String expectedValidatorString = + "[0xbe068f726a13c8d46c44be6ce9d275600e1735a4, 0x5ff6f4b66a46a2b2310a6f3a93aaddc0d9a1c193]"; + + assertThat(commandOutput.toString(UTF_8)).contains(expectedValidatorString); + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + } + } + + @Test + public void decodeWithInputFilePathToOutputFile(final @TempDir Path dir) + throws Exception { + final Path tempInputFile = Files.createTempFile(dir, "input", "json"); + final File tempOutputFile = File.createTempFile("ibftValidators", "rlp"); + try (final BufferedWriter fileWriter = Files.newBufferedWriter(tempInputFile, UTF_8)) { + + fileWriter.write( + "0xf853a00000000000000000000000000000000000000000000000000000000000000000ea94be068f726a13c8d46c44be6ce9d275600e1735a4945ff6f4b66a46a2b2310a6f3a93aaddc0d9a1c193808400000000c0"); + + fileWriter.flush(); + + parseCommand( + RLP_SUBCOMMAND_NAME, + RLP_DECODE_SUBCOMMAND_NAME, + "--from", + tempInputFile.toFile().getAbsolutePath(), + "--to", + tempOutputFile.getPath() + ); + + final String expectedValidatorString = + "[0xbe068f726a13c8d46c44be6ce9d275600e1735a4, 0x5ff6f4b66a46a2b2310a6f3a93aaddc0d9a1c193]"; + + assertThat(contentOf(tempOutputFile)).contains(expectedValidatorString); + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + } + } + + @Test + public void decodeWithEmptyStdInputMustRaiseAnError() throws Exception { + + // set empty stdin + final String jsonInput = ""; + final ByteArrayInputStream stdIn = new ByteArrayInputStream(jsonInput.getBytes(UTF_8)); + + parseCommand(stdIn, RLP_SUBCOMMAND_NAME, RLP_DECODE_SUBCOMMAND_NAME); + + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)) + .startsWith("Unable to read input data."); + } + + @Test + public void decodeWithInputFilePathMustThrowErrorFileNotExist(final @TempDir Path dir) + throws Exception { + + final String nonExistingFileName = "/incorrectPath/wrongFile.json"; + + parseCommand( + RLP_SUBCOMMAND_NAME, + RLP_DECODE_SUBCOMMAND_NAME, + "--from", + nonExistingFileName); + + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)) + .contains( + "Unable to read input file"); + } + + @Test + public void decodeWithEmptyInputMustRaiseAnError(final @TempDir Path dir) throws Exception { + final Path emptyFile = Files.createTempFile(dir, "empty", "json"); + parseCommand( + RLP_SUBCOMMAND_NAME, + RLP_DECODE_SUBCOMMAND_NAME, + "--from", + emptyFile.toFile().getAbsolutePath()); + + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)) + .startsWith("An error occurred while trying to read the input data."); + } + @AfterEach public void restoreStdin() { System.setIn(System.in);