From c05b0999eeca8d68fe85c63bfd28f5e24a701094 Mon Sep 17 00:00:00 2001 From: Jason Frame Date: Fri, 12 Feb 2021 13:19:24 +1000 Subject: [PATCH] qbft message RLP reference tests (#1860) Signed-off-by: Jason Frame jasonwframe@gmail.com --- .gitmodules | 4 + consensus/qbft/build.gradle | 77 +++++++++++ .../consensus/qbt/support/CommitMessage.java | 76 +++++++++++ .../consensus/qbt/support/PrepareMessage.java | 76 +++++++++++ .../qbt/support/ProposalMessage.java | 109 ++++++++++++++++ .../qbt/support/RlpTestCaseMessage.java | 35 +++++ .../qbt/support/RlpTestCaseSpec.java | 39 ++++++ .../qbt/support/RoundChangeMessage.java | 121 ++++++++++++++++++ .../consensus/qbt/test/MessageRlpTest.java | 64 +++++++++ consensus/qbft/src/reference-test/resources | 1 + .../qbft/messagewrappers/CommitTest.java | 2 +- .../besu/testutil/JsonTestParameters.java | 4 +- 12 files changed, 606 insertions(+), 2 deletions(-) create mode 100644 consensus/qbft/src/reference-test/java/org/hyperledger/besu/consensus/qbt/support/CommitMessage.java create mode 100644 consensus/qbft/src/reference-test/java/org/hyperledger/besu/consensus/qbt/support/PrepareMessage.java create mode 100644 consensus/qbft/src/reference-test/java/org/hyperledger/besu/consensus/qbt/support/ProposalMessage.java create mode 100644 consensus/qbft/src/reference-test/java/org/hyperledger/besu/consensus/qbt/support/RlpTestCaseMessage.java create mode 100644 consensus/qbft/src/reference-test/java/org/hyperledger/besu/consensus/qbt/support/RlpTestCaseSpec.java create mode 100644 consensus/qbft/src/reference-test/java/org/hyperledger/besu/consensus/qbt/support/RoundChangeMessage.java create mode 100644 consensus/qbft/src/reference-test/java/org/hyperledger/besu/consensus/qbt/test/MessageRlpTest.java create mode 160000 consensus/qbft/src/reference-test/resources diff --git a/.gitmodules b/.gitmodules index 0efd88e099..1b67e0567a 100644 --- a/.gitmodules +++ b/.gitmodules @@ -2,3 +2,7 @@ path = ethereum/referencetests/src/test/resources url = https://github.com/ethereum/tests.git ignore = all +[submodule "qbft-ref-tests"] + path = consensus/qbft/src/reference-test/resources + url = https://github.com/ConsenSys/qbft-tests.git + ignore = all diff --git a/consensus/qbft/build.gradle b/consensus/qbft/build.gradle index 876f2c69c5..b041512f48 100644 --- a/consensus/qbft/build.gradle +++ b/consensus/qbft/build.gradle @@ -27,6 +27,32 @@ jar { } } +sourceSets { + referenceTest { + java { + compileClasspath += main.output + runtimeClasspath += main.output + srcDir file('src/reference-test/java') + } + resources.srcDir file('src/reference-test/resources') + } +} + +task referenceTests(type: Test, dependsOn: ["compileTestJava"]) { + group = "verification" + description = "Runs the Besu QBFT reference tests" + + jvmArgs = [ + '--add-opens', + 'java.base/java.util=ALL-UNNAMED', + '--add-opens', + 'java.base/java.util.concurrent=ALL-UNNAMED' + ] + testClassesDirs = sourceSets.referenceTest.output.classesDirs + classpath = sourceSets.referenceTest.runtimeClasspath + outputs.upToDateWhen { false } +} + dependencies { implementation project(':config') implementation project(':consensus:common') @@ -69,5 +95,56 @@ dependencies { integrationTestImplementation 'org.assertj:assertj-core' integrationTestImplementation 'org.mockito:mockito-core' + referenceTestImplementation 'junit:junit' + referenceTestImplementation 'org.assertj:assertj-core' + referenceTestImplementation 'org.mockito:mockito-core' + referenceTestImplementation 'com.fasterxml.jackson.core:jackson-databind' + referenceTestImplementation 'com.fasterxml.jackson.datatype:jackson-datatype-jdk8' + referenceTestImplementation 'org.apache.tuweni:bytes' + referenceTestImplementation project(':crypto') + referenceTestImplementation project(path: ':crypto', configuration: 'testSupportArtifacts') + referenceTestImplementation project(':consensus:common') + referenceTestImplementation project(':ethereum:core') + referenceTestImplementation project(':ethereum:rlp') + referenceTestImplementation project(':config') + referenceTestImplementation project(':testutil') + testSupportImplementation 'org.mockito:mockito-core' } + +task ('validateReferenceTestSubmodule') { + description = "Checks that the reference tests submodule is not accidentially changed" + doLast { + def expectedHash = '2fe00d76d97e35aa2df6449b5f567c41cf6f58fc' + def result = new ByteArrayOutputStream() + try { + exec { + commandLine 'git', 'submodule' + standardOutput = result + errorOutput = result + } + } catch (Exception ignore) { + // Ignore it. We want to fail in a friendly fashion if they don't have git installed. + // The CI servers have git and that is the only critical place for this failure + expectedHash = '' + } + if (!result.toString().contains(expectedHash)) { + throw new GradleException("""For the QBFT Reference Tests the git commit did not match what was expected. + +If this is a deliberate change where you are updating the reference tests +then update "expectedHash" in `consensus/qbft/build.gradle` as the +commit hash for this task. +Expected hash : ${expectedHash} +Full git output : ${result} + +If this is accidental you can correct the reference test versions with the +following commands: + pushd consensus/qbft/src/reference-test/resources + git checkout ${expectedHash} + cd .. + git add resources + popd""") + } + } +} +processResources.dependsOn('validateReferenceTestSubmodule') diff --git a/consensus/qbft/src/reference-test/java/org/hyperledger/besu/consensus/qbt/support/CommitMessage.java b/consensus/qbft/src/reference-test/java/org/hyperledger/besu/consensus/qbt/support/CommitMessage.java new file mode 100644 index 0000000000..e0f34d49c0 --- /dev/null +++ b/consensus/qbft/src/reference-test/java/org/hyperledger/besu/consensus/qbt/support/CommitMessage.java @@ -0,0 +1,76 @@ +/* + * Copyright 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. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.consensus.qbt.support; + +import org.hyperledger.besu.consensus.common.bft.ConsensusRoundIdentifier; +import org.hyperledger.besu.consensus.common.bft.messagewrappers.BftMessage; +import org.hyperledger.besu.consensus.common.bft.payload.SignedData; +import org.hyperledger.besu.consensus.qbft.messagewrappers.Commit; +import org.hyperledger.besu.consensus.qbft.payload.CommitPayload; +import org.hyperledger.besu.crypto.SECP256K1.Signature; +import org.hyperledger.besu.ethereum.core.Hash; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import org.apache.tuweni.bytes.Bytes; + +public class CommitMessage implements RlpTestCaseMessage { + private final UnsignedCommit unsignedCommit; + private final String signature; + + @JsonCreator + public CommitMessage( + @JsonProperty("unsignedCommit") final UnsignedCommit unsignedCommit, + @JsonProperty("signature") final String signature) { + this.unsignedCommit = unsignedCommit; + this.signature = signature; + } + + @Override + public BftMessage fromRlp(final Bytes rlp) { + return Commit.decode(rlp); + } + + @Override + public BftMessage toBftMessage() { + final CommitPayload commitPayload = + new CommitPayload( + new ConsensusRoundIdentifier(unsignedCommit.sequence, unsignedCommit.round), + Hash.fromHexStringLenient(unsignedCommit.digest), + Signature.decode(Bytes.fromHexString(unsignedCommit.commitSeal))); + final SignedData signedCommitPayload = + SignedData.create(commitPayload, Signature.decode(Bytes.fromHexString(signature))); + return new Commit(signedCommitPayload); + } + + public static class UnsignedCommit { + private final long sequence; + private final int round; + private final String commitSeal; + private final String digest; + + @JsonCreator + public UnsignedCommit( + @JsonProperty("sequence") final long sequence, + @JsonProperty("round") final int round, + @JsonProperty("commitSeal") final String commitSeal, + @JsonProperty("digest") final String digest) { + this.sequence = sequence; + this.round = round; + this.commitSeal = commitSeal; + this.digest = digest; + } + } +} diff --git a/consensus/qbft/src/reference-test/java/org/hyperledger/besu/consensus/qbt/support/PrepareMessage.java b/consensus/qbft/src/reference-test/java/org/hyperledger/besu/consensus/qbt/support/PrepareMessage.java new file mode 100644 index 0000000000..f5a13b4293 --- /dev/null +++ b/consensus/qbft/src/reference-test/java/org/hyperledger/besu/consensus/qbt/support/PrepareMessage.java @@ -0,0 +1,76 @@ +/* + * Copyright 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. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.consensus.qbt.support; + +import org.hyperledger.besu.consensus.common.bft.ConsensusRoundIdentifier; +import org.hyperledger.besu.consensus.common.bft.messagewrappers.BftMessage; +import org.hyperledger.besu.consensus.common.bft.payload.SignedData; +import org.hyperledger.besu.consensus.qbft.messagewrappers.Prepare; +import org.hyperledger.besu.consensus.qbft.payload.PreparePayload; +import org.hyperledger.besu.crypto.SECP256K1.Signature; +import org.hyperledger.besu.ethereum.core.Hash; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import org.apache.tuweni.bytes.Bytes; + +public class PrepareMessage implements RlpTestCaseMessage { + private final UnsignedPrepare unsignedPrepare; + private final String signature; + + @JsonCreator + public PrepareMessage( + @JsonProperty("unsignedPrepare") final UnsignedPrepare unsignedPrepare, + @JsonProperty("signature") final String signature) { + this.unsignedPrepare = unsignedPrepare; + this.signature = signature; + } + + @Override + public BftMessage fromRlp(final Bytes rlp) { + return Prepare.decode(rlp); + } + + @Override + public BftMessage toBftMessage() { + return new Prepare(toSignedPreparePayload(this)); + } + + public static SignedData toSignedPreparePayload( + final PrepareMessage prepareMessage) { + final UnsignedPrepare unsignedPrepare = prepareMessage.unsignedPrepare; + return SignedData.create( + new PreparePayload( + new ConsensusRoundIdentifier(unsignedPrepare.sequence, unsignedPrepare.round), + Hash.fromHexString(unsignedPrepare.digest)), + Signature.decode(Bytes.fromHexString(prepareMessage.signature))); + } + + public static class UnsignedPrepare { + private final long sequence; + private final int round; + private final String digest; + + @JsonCreator + public UnsignedPrepare( + @JsonProperty("sequence") final long sequence, + @JsonProperty("round") final int round, + @JsonProperty("digest") final String digest) { + this.sequence = sequence; + this.round = round; + this.digest = digest; + } + } +} diff --git a/consensus/qbft/src/reference-test/java/org/hyperledger/besu/consensus/qbt/support/ProposalMessage.java b/consensus/qbft/src/reference-test/java/org/hyperledger/besu/consensus/qbt/support/ProposalMessage.java new file mode 100644 index 0000000000..9fd9c8369d --- /dev/null +++ b/consensus/qbft/src/reference-test/java/org/hyperledger/besu/consensus/qbt/support/ProposalMessage.java @@ -0,0 +1,109 @@ +/* + * Copyright 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. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.consensus.qbt.support; + +import org.hyperledger.besu.consensus.common.bft.BftBlockHeaderFunctions; +import org.hyperledger.besu.consensus.common.bft.ConsensusRoundIdentifier; +import org.hyperledger.besu.consensus.common.bft.messagewrappers.BftMessage; +import org.hyperledger.besu.consensus.common.bft.payload.SignedData; +import org.hyperledger.besu.consensus.qbft.messagewrappers.Proposal; +import org.hyperledger.besu.consensus.qbft.payload.PreparePayload; +import org.hyperledger.besu.consensus.qbft.payload.ProposalPayload; +import org.hyperledger.besu.consensus.qbft.payload.RoundChangePayload; +import org.hyperledger.besu.consensus.qbt.support.RoundChangeMessage.SignedRoundChange; +import org.hyperledger.besu.crypto.SECP256K1.Signature; +import org.hyperledger.besu.ethereum.core.Block; +import org.hyperledger.besu.ethereum.rlp.RLP; + +import java.util.List; +import java.util.stream.Collectors; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import org.apache.tuweni.bytes.Bytes; + +public class ProposalMessage implements RlpTestCaseMessage { + private final SignedProposal signedProposal; + private final List roundChanges; + + @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, defaultImpl = PrepareMessage.class) + private final List prepares; + + public ProposalMessage( + @JsonProperty("signedProposal") final SignedProposal signedProposal, + @JsonProperty("roundChanges") final List roundChanges, + @JsonProperty("prepares") final List prepares) { + this.signedProposal = signedProposal; + this.roundChanges = roundChanges; + this.prepares = prepares; + } + + @Override + public BftMessage fromRlp(final Bytes rlp) { + return Proposal.decode(rlp); + } + + @Override + public BftMessage toBftMessage() { + final List> signedRoundChanges = + roundChanges.stream() + .map(SignedRoundChange::toSignedRoundChangePayload) + .collect(Collectors.toList()); + final List> signedPrepares = + prepares.stream().map(PrepareMessage::toSignedPreparePayload).collect(Collectors.toList()); + final Block block = + Block.readFrom( + RLP.input(Bytes.fromHexString(signedProposal.unsignedProposal.block)), + BftBlockHeaderFunctions.forCommittedSeal()); + final ProposalPayload proposalPayload = + new ProposalPayload( + new ConsensusRoundIdentifier( + signedProposal.unsignedProposal.sequence, signedProposal.unsignedProposal.round), + block); + final SignedData signedProposalPayload = + SignedData.create( + proposalPayload, Signature.decode(Bytes.fromHexString(signedProposal.signature))); + return new Proposal(signedProposalPayload, signedRoundChanges, signedPrepares); + } + + public static class SignedProposal { + private final UnsignedProposal unsignedProposal; + private final String signature; + + public SignedProposal( + @JsonProperty("unsignedProposal") final UnsignedProposal unsignedProposal, + @JsonProperty("signature") final String signature) { + this.unsignedProposal = unsignedProposal; + this.signature = signature; + } + } + + public static class UnsignedProposal { + private final long sequence; + private final int round; + private final String block; + + @JsonCreator + public UnsignedProposal( + @JsonProperty("sequence") final long sequence, + @JsonProperty("round") final int round, + @JsonProperty("block") final String block) { + this.sequence = sequence; + this.round = round; + this.block = block; + } + } +} diff --git a/consensus/qbft/src/reference-test/java/org/hyperledger/besu/consensus/qbt/support/RlpTestCaseMessage.java b/consensus/qbft/src/reference-test/java/org/hyperledger/besu/consensus/qbt/support/RlpTestCaseMessage.java new file mode 100644 index 0000000000..434dae131b --- /dev/null +++ b/consensus/qbft/src/reference-test/java/org/hyperledger/besu/consensus/qbt/support/RlpTestCaseMessage.java @@ -0,0 +1,35 @@ +/* + * Copyright 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. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.consensus.qbt.support; + +import org.hyperledger.besu.consensus.common.bft.messagewrappers.BftMessage; + +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import org.apache.tuweni.bytes.Bytes; + +@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type") +@JsonSubTypes({ + @JsonSubTypes.Type(value = CommitMessage.class, name = "commit"), + @JsonSubTypes.Type(value = PrepareMessage.class, name = "prepare"), + @JsonSubTypes.Type(value = RoundChangeMessage.class, name = "roundChange"), + @JsonSubTypes.Type(value = ProposalMessage.class, name = "proposal"), +}) +public interface RlpTestCaseMessage { + + BftMessage fromRlp(Bytes rlp); + + BftMessage toBftMessage(); +} diff --git a/consensus/qbft/src/reference-test/java/org/hyperledger/besu/consensus/qbt/support/RlpTestCaseSpec.java b/consensus/qbft/src/reference-test/java/org/hyperledger/besu/consensus/qbt/support/RlpTestCaseSpec.java new file mode 100644 index 0000000000..b19fa568bc --- /dev/null +++ b/consensus/qbft/src/reference-test/java/org/hyperledger/besu/consensus/qbt/support/RlpTestCaseSpec.java @@ -0,0 +1,39 @@ +/* + * Copyright 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. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.consensus.qbt.support; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +public class RlpTestCaseSpec { + private final RlpTestCaseMessage message; + private final String rlp; + + @JsonCreator + public RlpTestCaseSpec( + @JsonProperty("message") final RlpTestCaseMessage message, + @JsonProperty("rlp") final String rlp) { + this.message = message; + this.rlp = rlp; + } + + public RlpTestCaseMessage getMessage() { + return message; + } + + public String getRlp() { + return rlp; + } +} diff --git a/consensus/qbft/src/reference-test/java/org/hyperledger/besu/consensus/qbt/support/RoundChangeMessage.java b/consensus/qbft/src/reference-test/java/org/hyperledger/besu/consensus/qbt/support/RoundChangeMessage.java new file mode 100644 index 0000000000..84836e2160 --- /dev/null +++ b/consensus/qbft/src/reference-test/java/org/hyperledger/besu/consensus/qbt/support/RoundChangeMessage.java @@ -0,0 +1,121 @@ +/* + * Copyright 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. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.consensus.qbt.support; + +import static org.hyperledger.besu.consensus.common.bft.BftBlockHeaderFunctions.forCommittedSeal; + +import org.hyperledger.besu.consensus.common.bft.ConsensusRoundIdentifier; +import org.hyperledger.besu.consensus.common.bft.messagewrappers.BftMessage; +import org.hyperledger.besu.consensus.common.bft.payload.SignedData; +import org.hyperledger.besu.consensus.qbft.messagewrappers.RoundChange; +import org.hyperledger.besu.consensus.qbft.payload.PreparePayload; +import org.hyperledger.besu.consensus.qbft.payload.PreparedRoundMetadata; +import org.hyperledger.besu.consensus.qbft.payload.RoundChangePayload; +import org.hyperledger.besu.crypto.SECP256K1.Signature; +import org.hyperledger.besu.ethereum.core.Block; +import org.hyperledger.besu.ethereum.core.Hash; +import org.hyperledger.besu.ethereum.rlp.RLP; +import org.hyperledger.besu.ethereum.rlp.RLPInput; + +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.annotation.JsonTypeInfo; +import org.apache.tuweni.bytes.Bytes; + +public class RoundChangeMessage implements RlpTestCaseMessage { + private final SignedRoundChange signedRoundChange; + private final Optional block; + + @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, defaultImpl = PrepareMessage.class) + private final List prepares; + + public RoundChangeMessage( + @JsonProperty("signedRoundChange") final SignedRoundChange signedRoundChange, + @JsonProperty("block") final Optional block, + @JsonProperty("prepares") final List prepares) { + this.signedRoundChange = signedRoundChange; + this.block = block; + this.prepares = prepares; + } + + @Override + public BftMessage fromRlp(final Bytes rlp) { + return RoundChange.decode(rlp); + } + + @Override + public BftMessage toBftMessage() { + final Optional blockRlp = this.block.map(s -> RLP.input(Bytes.fromHexString(s))); + final Optional block = blockRlp.map(r -> Block.readFrom(r, forCommittedSeal())); + final List> signedPrepares = + prepares.stream().map(PrepareMessage::toSignedPreparePayload).collect(Collectors.toList()); + return new RoundChange( + SignedRoundChange.toSignedRoundChangePayload(signedRoundChange), block, signedPrepares); + } + + public static class UnsignedRoundChange { + private final long sequence; + private final int round; + private final Optional preparedValue; + private final Optional preparedRound; + + @JsonCreator + public UnsignedRoundChange( + @JsonProperty("sequence") final long sequence, + @JsonProperty("round") final int round, + @JsonProperty("preparedValue") final Optional preparedValue, + @JsonProperty("preparedRound") final Optional preparedRound) { + this.sequence = sequence; + this.round = round; + this.preparedValue = preparedValue; + this.preparedRound = preparedRound; + } + } + + public static class SignedRoundChange { + private final UnsignedRoundChange unsignedRoundChange; + private final String signature; + + public SignedRoundChange( + @JsonProperty("unsignedRoundChange") final UnsignedRoundChange unsignedRoundChange, + @JsonProperty("signature") final String signature) { + this.unsignedRoundChange = unsignedRoundChange; + this.signature = signature; + } + + public static SignedData toSignedRoundChangePayload( + final SignedRoundChange signedRoundChange) { + final UnsignedRoundChange unsignedRoundChange = signedRoundChange.unsignedRoundChange; + final Optional preparedRoundMetadata = + unsignedRoundChange.preparedRound.isPresent() + && unsignedRoundChange.preparedValue.isPresent() + ? Optional.of( + new PreparedRoundMetadata( + Hash.fromHexString(unsignedRoundChange.preparedValue.get()), + unsignedRoundChange.preparedRound.get())) + : Optional.empty(); + final RoundChangePayload roundChangePayload = + new RoundChangePayload( + new ConsensusRoundIdentifier(unsignedRoundChange.sequence, unsignedRoundChange.round), + preparedRoundMetadata); + return SignedData.create( + roundChangePayload, Signature.decode(Bytes.fromHexString(signedRoundChange.signature))); + } + } +} diff --git a/consensus/qbft/src/reference-test/java/org/hyperledger/besu/consensus/qbt/test/MessageRlpTest.java b/consensus/qbft/src/reference-test/java/org/hyperledger/besu/consensus/qbt/test/MessageRlpTest.java new file mode 100644 index 0000000000..871e0cb2cf --- /dev/null +++ b/consensus/qbft/src/reference-test/java/org/hyperledger/besu/consensus/qbt/test/MessageRlpTest.java @@ -0,0 +1,64 @@ +/* + * Copyright 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. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.consensus.qbt.test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assume.assumeTrue; + +import org.hyperledger.besu.consensus.common.bft.messagewrappers.BftMessage; +import org.hyperledger.besu.consensus.qbt.support.RlpTestCaseSpec; +import org.hyperledger.besu.testutil.JsonTestParameters; + +import java.util.Collection; + +import org.apache.tuweni.bytes.Bytes; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; + +@RunWith(Parameterized.class) +public class MessageRlpTest { + + private static final String TEST_CONFIG_PATH = "MessageRLPTests/"; + private final RlpTestCaseSpec spec; + + @Parameters(name = "Name: {0}") + public static Collection getTestParametersForConfig() { + return JsonTestParameters.create(RlpTestCaseSpec.class).generate(TEST_CONFIG_PATH); + } + + @Test + public void encode() { + final Bytes expectedRlp = Bytes.fromHexString(spec.getRlp()); + assertThat(spec.getMessage().toBftMessage().encode()).isEqualTo(expectedRlp); + } + + @Test + public void decode() { + final BftMessage expectedBftMessage = spec.getMessage().toBftMessage(); + final BftMessage decodedBftMessage = + spec.getMessage().fromRlp(Bytes.fromHexString(spec.getRlp())); + assertThat(decodedBftMessage) + .usingRecursiveComparison() + .usingOverriddenEquals() + .isEqualTo(expectedBftMessage); + } + + public MessageRlpTest(final String name, final RlpTestCaseSpec spec, final boolean runTest) { + this.spec = spec; + assumeTrue("Test was blacklisted", runTest); + } +} diff --git a/consensus/qbft/src/reference-test/resources b/consensus/qbft/src/reference-test/resources new file mode 160000 index 0000000000..2fe00d76d9 --- /dev/null +++ b/consensus/qbft/src/reference-test/resources @@ -0,0 +1 @@ +Subproject commit 2fe00d76d97e35aa2df6449b5f567c41cf6f58fc diff --git a/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/messagewrappers/CommitTest.java b/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/messagewrappers/CommitTest.java index b9817d3129..016b13d27f 100644 --- a/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/messagewrappers/CommitTest.java +++ b/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/messagewrappers/CommitTest.java @@ -35,7 +35,7 @@ import org.junit.Test; public class CommitTest { @Test - public void canRoundTripAPrepareMessage() { + public void canRoundTripACommitMessage() { final NodeKey nodeKey = NodeKeyUtils.generate(); final Address addr = Util.publicKeyToAddress(nodeKey.getPublicKey()); diff --git a/testutil/src/main/java/org/hyperledger/besu/testutil/JsonTestParameters.java b/testutil/src/main/java/org/hyperledger/besu/testutil/JsonTestParameters.java index 42a2dc7893..e9b710e9bc 100644 --- a/testutil/src/main/java/org/hyperledger/besu/testutil/JsonTestParameters.java +++ b/testutil/src/main/java/org/hyperledger/besu/testutil/JsonTestParameters.java @@ -39,6 +39,7 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.databind.JavaType; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.datatype.jdk8.Jdk8Module; /** * Utility class for generating JUnit test parameters from json files. Each set of test parameters @@ -87,7 +88,8 @@ public class JsonTestParameters { void generate(String name, S mappedType, Collector collector); } - private static final ObjectMapper objectMapper = new ObjectMapper(); + private static final ObjectMapper objectMapper = + new ObjectMapper().registerModule(new Jdk8Module()); // The type to which the json file is directly mapped private final Class jsonFileMappedType;