QBFT PKI-backed Block Creation (#2677)

Signed-off-by: Lucas Saldanha <lucascrsaldanha@gmail.com>
Co-authored-by: Jason Frame <jasonwframe@gmail.com>
pull/2748/head
Lucas Saldanha 3 years ago committed by GitHub
parent ed59386241
commit 021544403b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      CHANGELOG.md
  2. 6
      acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/bft/pki/PkiQbftAcceptanceTestParameterization.java
  3. 13
      besu/src/main/java/org/hyperledger/besu/controller/QbftBesuControllerBuilder.java
  4. 18
      consensus/common/src/test-support/java/org/hyperledger/besu/consensus/common/bft/BftContextBuilder.java
  5. 19
      consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/BftExtraDataFixture.java
  6. 3
      consensus/qbft/src/integration-test/java/org/hyperledger/besu/consensus/qbft/support/TestContextBuilder.java
  7. 4
      consensus/qbft/src/integration-test/java/org/hyperledger/besu/consensus/qbft/test/round/QbftRoundIntegrationTest.java
  8. 16
      consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/QbftContext.java
  9. 2
      consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/QbftExtraDataCodec.java
  10. 37
      consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/pki/PkiQbftBlockHashing.java
  11. 29
      consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/pki/PkiQbftBlockHeaderFunctions.java
  12. 97
      consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/pki/PkiQbftCreateBlockForProposalBehaviour.java
  13. 8
      consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/pki/PkiQbftExtraData.java
  14. 82
      consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/pki/PkiQbftExtraDataCodec.java
  15. 24
      consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/statemachine/CreateBlockForProposalBehaviour.java
  16. 45
      consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/statemachine/QbftRound.java
  17. 17
      consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/statemachine/QbftRoundFactory.java
  18. 65
      consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/validation/ProposalPayloadValidator.java
  19. 2
      consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/validation/ProposalValidator.java
  20. 5
      consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/QbftBlockHeaderValidationRulesetFactoryTest.java
  21. 15
      consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/headervalidationrules/QbftValidatorsValidationRuleTest.java
  22. 85
      consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/pki/PkiQbftBlockHashingTest.java
  23. 132
      consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/pki/PkiQbftCreateBlockForProposalBehaviourTest.java
  24. 155
      consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/pki/PkiQbftExtraDataCodecTest.java
  25. 6
      consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/statemachine/QbftBlockHeightManagerTest.java
  26. 4
      consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/statemachine/QbftRoundTest.java
  27. 142
      consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/validation/ProposalPayloadValidatorTest.java
  28. 4
      consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/validation/ProposalValidatorTest.java
  29. 4
      consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/validation/RoundChangeMessageValidatorTest.java
  30. 4
      pki/src/main/java/org/hyperledger/besu/pki/cms/CmsValidator.java
  31. 7
      pki/src/test/java/org/hyperledger/besu/pki/cms/CmsCreationAndValidationTest.java

@ -18,6 +18,7 @@
- \[EXPERIMENTAL\] Added support for using DNS host name in place of IP address in onchain node permissioning rules [#2667](https://github.com/hyperledger/besu/pull/2667)
- Implement EIP-3607 Reject transactions from senders with deployed code. [#2676](https://github.com/hyperledger/besu/pull/2676)
- Ignore all unknown fields when supplied to eth_estimateGas or eth_call. [\#2690](https://github.com/hyperledger/besu/pull/2690)
- \[EXPERIMENTAL\] Added support for QBFT with PKI-backed Block Creation. [#2647](https://github.com/hyperledger/besu/issues/2647)
### Bug Fixes
- Consider effective price and effective priority fee in transaction replacement rules [\#2529](https://github.com/hyperledger/besu/issues/2529)

@ -29,6 +29,12 @@ public class PkiQbftAcceptanceTestParameterization {
final List<Object[]> ret = new ArrayList<>();
ret.addAll(
List.of(
new Object[] {
"qbft-pki",
new PkiQbftAcceptanceTestParameterization(
BesuNodeFactory::createPkiQbftNode,
BesuNodeFactory::createPkiQbftNodeWithValidators)
},
new Object[] {
"qbft-tls-jks",
new PkiQbftAcceptanceTestParameterization(

@ -42,12 +42,12 @@ import org.hyperledger.besu.consensus.common.bft.statemachine.FutureMessageBuffe
import org.hyperledger.besu.consensus.common.validator.ValidatorProvider;
import org.hyperledger.besu.consensus.common.validator.blockbased.BlockValidatorProvider;
import org.hyperledger.besu.consensus.qbft.QbftBlockHeaderValidationRulesetFactory;
import org.hyperledger.besu.consensus.qbft.QbftContext;
import org.hyperledger.besu.consensus.qbft.QbftExtraDataCodec;
import org.hyperledger.besu.consensus.qbft.QbftGossip;
import org.hyperledger.besu.consensus.qbft.blockcreation.QbftBlockCreatorFactory;
import org.hyperledger.besu.consensus.qbft.jsonrpc.QbftJsonRpcMethods;
import org.hyperledger.besu.consensus.qbft.payload.MessageFactory;
import org.hyperledger.besu.consensus.qbft.pki.PkiQbftContext;
import org.hyperledger.besu.consensus.qbft.pki.PkiQbftExtraDataCodec;
import org.hyperledger.besu.consensus.qbft.protocol.Istanbul100SubProtocol;
import org.hyperledger.besu.consensus.qbft.statemachine.QbftBlockHeightManagerFactory;
@ -299,15 +299,8 @@ public class QbftBesuControllerBuilder extends BftBesuControllerBuilder {
validatorProvider = new TransactionValidatorProvider(blockchain, validatorContractController);
}
if (pkiBlockCreationConfiguration.isPresent()) {
return new PkiQbftContext(
validatorProvider,
epochManager,
bftBlockInterface().get(),
pkiBlockCreationConfiguration.get());
} else {
return new BftContext(validatorProvider, epochManager, bftBlockInterface().get());
}
return new QbftContext(
validatorProvider, epochManager, bftBlockInterface().get(), pkiBlockCreationConfiguration);
}
private BftValidatorOverrides convertBftForks(final List<BftFork> bftForks) {

@ -40,7 +40,14 @@ public class BftContextBuilder {
public static BftContext setupContextWithBftExtraData(
final Collection<Address> validators, final BftExtraData bftExtraData) {
final BftContext bftContext = mock(BftContext.class, withSettings().lenient());
return setupContextWithBftExtraData(BftContext.class, validators, bftExtraData);
}
public static <T extends BftContext> T setupContextWithBftExtraData(
final Class<T> contextClazz,
final Collection<Address> validators,
final BftExtraData bftExtraData) {
final T bftContext = mock(contextClazz, withSettings().lenient());
final ValidatorProvider mockValidatorProvider =
mock(ValidatorProvider.class, withSettings().lenient());
final BftBlockInterface mockBftBlockInterface =
@ -54,7 +61,14 @@ public class BftContextBuilder {
public static BftContext setupContextWithBftExtraDataEncoder(
final Collection<Address> validators, final BftExtraDataCodec bftExtraDataCodec) {
final BftContext bftContext = mock(BftContext.class, withSettings().lenient());
return setupContextWithBftExtraDataEncoder(BftContext.class, validators, bftExtraDataCodec);
}
public static <T extends BftContext> T setupContextWithBftExtraDataEncoder(
final Class<T> contextClazz,
final Collection<Address> validators,
final BftExtraDataCodec bftExtraDataCodec) {
final T bftContext = mock(contextClazz, withSettings().lenient());
final ValidatorProvider mockValidatorProvider =
mock(ValidatorProvider.class, withSettings().lenient());
when(bftContext.getValidatorProvider()).thenReturn(mockValidatorProvider);

@ -15,12 +15,15 @@
package org.hyperledger.besu.consensus.common.bft;
import static java.util.Collections.emptyList;
import static java.util.Collections.singletonList;
import org.hyperledger.besu.crypto.NodeKey;
import org.hyperledger.besu.crypto.NodeKeyUtils;
import org.hyperledger.besu.crypto.SECPSignature;
import org.hyperledger.besu.ethereum.core.Address;
import org.hyperledger.besu.ethereum.core.BlockHeader;
import org.hyperledger.besu.ethereum.core.Hash;
import org.hyperledger.besu.ethereum.core.Util;
import java.util.List;
import java.util.Optional;
@ -31,6 +34,22 @@ import org.apache.tuweni.bytes.Bytes;
public class BftExtraDataFixture {
public static BftExtraData createExtraData(
final BlockHeader header, final BftExtraDataCodec bftExtraDataCodec) {
final NodeKey proposerNodeKey = NodeKeyUtils.generate();
final Address proposerAddress = Util.publicKeyToAddress(proposerNodeKey.getPublicKey());
final List<Address> validators = singletonList(proposerAddress);
return createExtraData(
header,
Bytes.wrap(new byte[BftExtraDataCodec.EXTRA_VANITY_LENGTH]),
Optional.of(Vote.authVote(Address.fromHexString("1"))),
validators,
singletonList(proposerNodeKey),
0x2A,
bftExtraDataCodec);
}
public static BftExtraData createExtraData(
final BlockHeader header,
final Bytes vanityData,

@ -50,6 +50,7 @@ import org.hyperledger.besu.consensus.common.bft.statemachine.FutureMessageBuffe
import org.hyperledger.besu.consensus.common.validator.ValidatorProvider;
import org.hyperledger.besu.consensus.common.validator.blockbased.BlockValidatorProvider;
import org.hyperledger.besu.consensus.qbft.QbftBlockHeaderValidationRulesetFactory;
import org.hyperledger.besu.consensus.qbft.QbftContext;
import org.hyperledger.besu.consensus.qbft.QbftExtraDataCodec;
import org.hyperledger.besu.consensus.qbft.QbftGossip;
import org.hyperledger.besu.consensus.qbft.blockcreation.QbftBlockCreatorFactory;
@ -400,7 +401,7 @@ public class TestContextBuilder {
new ProtocolContext(
blockChain,
worldStateArchive,
new BftContext(validatorProvider, epochManager, blockInterface));
new QbftContext(validatorProvider, epochManager, blockInterface, Optional.empty()));
final GasPricePendingTransactionsSorter pendingTransactions =
new GasPricePendingTransactionsSorter(

@ -29,6 +29,7 @@ import org.hyperledger.besu.consensus.common.bft.ConsensusRoundIdentifier;
import org.hyperledger.besu.consensus.common.bft.RoundTimer;
import org.hyperledger.besu.consensus.common.bft.blockcreation.BftBlockCreator;
import org.hyperledger.besu.consensus.common.bft.inttest.StubValidatorMulticaster;
import org.hyperledger.besu.consensus.qbft.QbftContext;
import org.hyperledger.besu.consensus.qbft.QbftExtraDataCodec;
import org.hyperledger.besu.consensus.qbft.network.QbftMessageTransmitter;
import org.hyperledger.besu.consensus.qbft.payload.MessageFactory;
@ -115,7 +116,8 @@ public class QbftRoundIntegrationTest {
new ProtocolContext(
blockChain,
worldStateArchive,
setupContextWithBftExtraDataEncoder(emptyList(), qbftExtraDataEncoder));
setupContextWithBftExtraDataEncoder(
QbftContext.class, emptyList(), qbftExtraDataEncoder));
}
@Test

@ -12,28 +12,30 @@
*
* SPDX-License-Identifier: Apache-2.0
*/
package org.hyperledger.besu.consensus.qbft.pki;
package org.hyperledger.besu.consensus.qbft;
import org.hyperledger.besu.consensus.common.EpochManager;
import org.hyperledger.besu.consensus.common.bft.BftBlockInterface;
import org.hyperledger.besu.consensus.common.bft.BftContext;
import org.hyperledger.besu.consensus.common.validator.ValidatorProvider;
import org.hyperledger.besu.consensus.qbft.pki.PkiBlockCreationConfiguration;
import java.util.Optional;
public class PkiQbftContext extends BftContext {
public class QbftContext extends BftContext {
private final PkiBlockCreationConfiguration pkiBlockCreationConfiguration;
private final Optional<PkiBlockCreationConfiguration> pkiBlockCreationConfiguration;
public PkiQbftContext(
public QbftContext(
final ValidatorProvider validatorProvider,
final EpochManager epochManager,
final BftBlockInterface blockInterface,
final PkiBlockCreationConfiguration pkiBlockCreationConfiguration) {
final Optional<PkiBlockCreationConfiguration> pkiBlockCreationConfiguration) {
super(validatorProvider, epochManager, blockInterface);
this.pkiBlockCreationConfiguration = pkiBlockCreationConfiguration;
}
public PkiBlockCreationConfiguration getPkiBlockCreationConfiguration() {
public Optional<PkiBlockCreationConfiguration> getPkiBlockCreationConfiguration() {
return pkiBlockCreationConfiguration;
}
}

@ -87,7 +87,7 @@ public class QbftExtraDataCodec extends BftExtraDataCodec {
final List<SECPSignature> seals =
rlpInput.readList(
rlp -> SignatureAlgorithmFactory.getInstance().decodeSignature(rlp.readBytes()));
rlpInput.leaveList();
rlpInput.leaveListLenient();
return new BftExtraData(vanityData, seals, vote, round, validators);
}

@ -0,0 +1,37 @@
/*
* 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.qbft.pki;
import org.hyperledger.besu.consensus.common.bft.BftBlockHashing;
import org.hyperledger.besu.consensus.common.bft.BftExtraData;
import org.hyperledger.besu.ethereum.core.BlockHeader;
import org.hyperledger.besu.ethereum.core.Hash;
public class PkiQbftBlockHashing {
private final PkiQbftExtraDataCodec extraDataCodec;
public PkiQbftBlockHashing(final PkiQbftExtraDataCodec extraDataCodec) {
this.extraDataCodec = extraDataCodec;
}
public Hash calculateHashOfBftBlockForCmsSignature(final BlockHeader header) {
final BftExtraData bftExtraData = extraDataCodec.decode(header);
return Hash.hash(
BftBlockHashing.serializeHeader(
header, () -> extraDataCodec.encodeWithoutCms(bftExtraData), extraDataCodec));
}
}

@ -0,0 +1,29 @@
/*
* 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.qbft.pki;
import org.hyperledger.besu.consensus.common.bft.BftBlockHeaderFunctions;
import org.hyperledger.besu.ethereum.core.BlockHeaderFunctions;
public class PkiQbftBlockHeaderFunctions {
public static BlockHeaderFunctions forCmsSignature(
final PkiQbftExtraDataCodec bftExtraDataCodec) {
return new BftBlockHeaderFunctions(
h -> new PkiQbftBlockHashing(bftExtraDataCodec).calculateHashOfBftBlockForCmsSignature(h),
bftExtraDataCodec);
}
}

@ -0,0 +1,97 @@
/*
* 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.qbft.pki;
import static com.google.common.base.Preconditions.checkArgument;
import org.hyperledger.besu.consensus.common.bft.BftBlockHeaderFunctions;
import org.hyperledger.besu.consensus.common.bft.BftExtraData;
import org.hyperledger.besu.consensus.common.bft.BftExtraDataCodec;
import org.hyperledger.besu.consensus.qbft.statemachine.CreateBlockForProposalBehaviour;
import org.hyperledger.besu.ethereum.blockcreation.BlockCreator;
import org.hyperledger.besu.ethereum.core.Block;
import org.hyperledger.besu.ethereum.core.BlockHeader;
import org.hyperledger.besu.ethereum.core.BlockHeaderBuilder;
import org.hyperledger.besu.ethereum.core.Hash;
import org.hyperledger.besu.pki.cms.CmsCreator;
import com.google.common.annotations.VisibleForTesting;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.tuweni.bytes.Bytes;
public class PkiQbftCreateBlockForProposalBehaviour implements CreateBlockForProposalBehaviour {
private static final Logger LOG = LogManager.getLogger();
private final BlockCreator blockCreator;
private final CmsCreator cmsCreator;
private final PkiQbftExtraDataCodec bftExtraDataCodec;
public PkiQbftCreateBlockForProposalBehaviour(
final BlockCreator blockCreator,
final PkiBlockCreationConfiguration pkiBlockCreationConfiguration,
final BftExtraDataCodec bftExtraDataCodec) {
this(
blockCreator,
new CmsCreator(
pkiBlockCreationConfiguration.getKeyStore(),
pkiBlockCreationConfiguration.getCertificateAlias()),
bftExtraDataCodec);
}
@VisibleForTesting
PkiQbftCreateBlockForProposalBehaviour(
final BlockCreator blockCreator,
final CmsCreator cmsCreator,
final BftExtraDataCodec bftExtraDataCodec) {
this.blockCreator = blockCreator;
this.cmsCreator = cmsCreator;
checkArgument(
bftExtraDataCodec instanceof PkiQbftExtraDataCodec,
"PkiQbftCreateBlockForProposalBehaviour must use PkiQbftExtraDataCodec");
this.bftExtraDataCodec = (PkiQbftExtraDataCodec) bftExtraDataCodec;
}
@Override
public Block create(final long headerTimeStampSeconds) {
final Block block = blockCreator.createBlock(headerTimeStampSeconds);
return replaceCmsInBlock(block);
}
private Block replaceCmsInBlock(final Block block) {
final BlockHeader blockHeader = block.getHeader();
final Hash hashWithoutCms =
PkiQbftBlockHeaderFunctions.forCmsSignature(bftExtraDataCodec).hash(block.getHeader());
final Bytes cms = cmsCreator.create(hashWithoutCms);
final BftExtraData previousExtraData = bftExtraDataCodec.decode(blockHeader);
final BftExtraData substituteExtraData = new PkiQbftExtraData(previousExtraData, cms);
final Bytes substituteExtraDataBytes = bftExtraDataCodec.encode(substituteExtraData);
final BlockHeaderBuilder headerBuilder = BlockHeaderBuilder.fromHeader(blockHeader);
headerBuilder
.extraData(substituteExtraDataBytes)
.blockHeaderFunctions(BftBlockHeaderFunctions.forCommittedSeal(bftExtraDataCodec));
final BlockHeader newHeader = headerBuilder.buildBlockHeader();
LOG.debug("Created CMS with signed hash {} for block {}", hashWithoutCms, newHeader.getHash());
return new Block(newHeader, block.getBody());
}
}

@ -27,7 +27,7 @@ import org.apache.tuweni.bytes.Bytes;
public class PkiQbftExtraData extends BftExtraData {
private final Optional<Bytes> cms;
private final Bytes cms;
public PkiQbftExtraData(
final Bytes vanityData,
@ -35,12 +35,12 @@ public class PkiQbftExtraData extends BftExtraData {
final Optional<Vote> vote,
final int round,
final Collection<Address> validators,
final Optional<Bytes> cms) {
final Bytes cms) {
super(vanityData, seals, vote, round, validators);
this.cms = cms;
}
PkiQbftExtraData(final BftExtraData bftExtraData, final Optional<Bytes> cms) {
PkiQbftExtraData(final BftExtraData bftExtraData, final Bytes cms) {
this(
bftExtraData.getVanityData(),
bftExtraData.getSeals(),
@ -50,7 +50,7 @@ public class PkiQbftExtraData extends BftExtraData {
cms);
}
public Optional<Bytes> getCms() {
public Bytes getCms() {
return cms;
}
}

@ -1,13 +1,16 @@
/*
* Copyright ConsenSys AG.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* 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
* 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
@ -20,37 +23,35 @@ import org.hyperledger.besu.ethereum.rlp.BytesValueRLPInput;
import org.hyperledger.besu.ethereum.rlp.BytesValueRLPOutput;
import org.hyperledger.besu.ethereum.rlp.RLPInput;
import java.util.Optional;
import java.util.List;
import org.apache.tuweni.bytes.Bytes;
/*
The PkiQbftExtraData encoding format is different from the "regular" QbftExtraData encoding.
We have an "envelope" list, with two elements: the extra data and the cms message.
The RLP encoding format is as follows: ["extra_data", ["cms"]]
The PkiQbftExtraData encoding format is different from the "regular" QbftExtraData encoding. We
have an extra bytes element in the end of the list.
*/
public class PkiQbftExtraDataCodec extends QbftExtraDataCodec {
public static final int QBFT_EXTRA_DATA_LIST_SIZE = 5;
@Override
public BftExtraData decodeRaw(final Bytes input) {
if (input.isEmpty()) {
throw new IllegalArgumentException("Invalid Bytes supplied - Bft Extra Data required.");
}
final RLPInput rlpInput = new BytesValueRLPInput(input, false);
rlpInput.enterList();
final BftExtraData bftExtraData = super.decodeRaw(input);
// Consume all the ExtraData input from the envelope list, and decode it using the QBFT decoder
final Bytes extraDataListAsBytes = rlpInput.currentListAsBytes();
final BftExtraData bftExtraData = super.decodeRaw(extraDataListAsBytes);
final RLPInput rlpInput = new BytesValueRLPInput(input, false);
final Optional<Bytes> cms;
if (rlpInput.nextIsList() && rlpInput.nextSize() == 0) {
cms = Optional.empty();
final Bytes cms;
final List<RLPInput> elements = rlpInput.readList(RLPInput::readAsRlp);
if (elements.size() > QBFT_EXTRA_DATA_LIST_SIZE) {
final RLPInput cmsElement = elements.get(elements.size() - 1);
cms = cmsElement.readBytes();
} else {
rlpInput.enterList();
cms = Optional.of(rlpInput.readBytes());
rlpInput.leaveList();
cms = Bytes.EMPTY;
}
return new PkiQbftExtraData(bftExtraData, cms);
@ -58,33 +59,30 @@ public class PkiQbftExtraDataCodec extends QbftExtraDataCodec {
@Override
protected Bytes encode(final BftExtraData bftExtraData, final EncodingType encodingType) {
if (!(bftExtraData instanceof PkiQbftExtraData)) {
throw new IllegalStateException(
"PkiQbftExtraDataCodec must be used only with PkiQbftExtraData");
}
final PkiQbftExtraData extraData = (PkiQbftExtraData) bftExtraData;
final BytesValueRLPOutput encoder = new BytesValueRLPOutput();
encoder.startList(); // start envelope list
final Bytes encodedQbftExtraData = super.encode(bftExtraData, encodingType);
encoder.writeRaw(encodedQbftExtraData);
return encode(bftExtraData, encodingType, true);
}
if (encodingType == EncodingType.ALL) {
if (extraData.getCms().isPresent()) {
Bytes cmsBytes = extraData.getCms().get();
encoder.startList();
encoder.writeBytes(cmsBytes);
encoder.endList();
} else {
encoder.writeEmptyList();
}
} else {
encoder.writeEmptyList();
private Bytes encode(
final BftExtraData bftExtraData, final EncodingType encodingType, final boolean includeCms) {
final Bytes encoded = super.encode(bftExtraData, encodingType);
if (!(bftExtraData instanceof PkiQbftExtraData) || !includeCms) {
return encoded;
}
encoder.endList(); // end envelope list
final BytesValueRLPOutput rlpOutput = new BytesValueRLPOutput();
rlpOutput.startList();
// Read through extraData RLP list elements and write them to the new RLP output
new BytesValueRLPInput(encoded, false)
.readList(RLPInput::readAsRlp).stream()
.map(RLPInput::raw)
.forEach(rlpOutput::writeRLPBytes);
rlpOutput.writeBytes(((PkiQbftExtraData) bftExtraData).getCms());
rlpOutput.endList();
return rlpOutput.encoded();
}
return encoder.encoded();
public Bytes encodeWithoutCms(final BftExtraData bftExtraData) {
return encode(bftExtraData, EncodingType.ALL, false);
}
}

@ -0,0 +1,24 @@
/*
* 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.qbft.statemachine;
import org.hyperledger.besu.ethereum.core.Block;
@FunctionalInterface
public interface CreateBlockForProposalBehaviour {
Block create(long headerTimeStampSeconds);
}

@ -49,6 +49,7 @@ import org.hyperledger.besu.util.Subscribers;
import java.util.List;
import java.util.Optional;
import com.google.common.annotations.VisibleForTesting;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@ -58,15 +59,17 @@ public class QbftRound {
private static final Logger LOG = LogManager.getLogger();
private final Subscribers<MinedBlockObserver> observers;
private final RoundState roundState;
private final BftBlockCreator blockCreator;
private final ProtocolContext protocolContext;
protected final RoundState roundState;
protected final BftBlockCreator blockCreator;
protected final ProtocolContext protocolContext;
private final BlockImporter blockImporter;
private final NodeKey nodeKey;
private final MessageFactory messageFactory; // used only to create stored local msgs
private final QbftMessageTransmitter transmitter;
private final BftExtraDataCodec bftExtraDataCodec;
protected final BftExtraDataCodec bftExtraDataCodec;
protected CreateBlockForProposalBehaviour createBlockForProposalBehaviour;
@VisibleForTesting
public QbftRound(
final RoundState roundState,
final BftBlockCreator blockCreator,
@ -78,6 +81,32 @@ public class QbftRound {
final QbftMessageTransmitter transmitter,
final RoundTimer roundTimer,
final BftExtraDataCodec bftExtraDataCodec) {
this(
roundState,
blockCreator,
protocolContext,
blockImporter,
observers,
nodeKey,
messageFactory,
transmitter,
roundTimer,
bftExtraDataCodec,
blockCreator::createBlock);
}
public QbftRound(
final RoundState roundState,
final BftBlockCreator blockCreator,
final ProtocolContext protocolContext,
final BlockImporter blockImporter,
final Subscribers<MinedBlockObserver> observers,
final NodeKey nodeKey,
final MessageFactory messageFactory,
final QbftMessageTransmitter transmitter,
final RoundTimer roundTimer,
final BftExtraDataCodec bftExtraDataCodec,
final CreateBlockForProposalBehaviour createBlockForProposalBehaviour) {
this.roundState = roundState;
this.blockCreator = blockCreator;
this.protocolContext = protocolContext;
@ -87,6 +116,7 @@ public class QbftRound {
this.messageFactory = messageFactory;
this.transmitter = transmitter;
this.bftExtraDataCodec = bftExtraDataCodec;
this.createBlockForProposalBehaviour = createBlockForProposalBehaviour;
roundTimer.startTimer(getRoundIdentifier());
}
@ -96,8 +126,9 @@ public class QbftRound {
}
public void createAndSendProposalMessage(final long headerTimeStampSeconds) {
final Block block = blockCreator.createBlock(headerTimeStampSeconds);
LOG.debug("Creating proposed block. round={}", roundState.getRoundIdentifier());
final Block block = createBlockForProposalBehaviour.create(headerTimeStampSeconds);
LOG.trace("Creating proposed block blockHeader={}", block.getHeader());
updateStateWithProposalAndTransmit(block, emptyList(), emptyList());
}
@ -110,7 +141,7 @@ public class QbftRound {
Block blockToPublish;
if (bestPreparedCertificate.isEmpty()) {
LOG.debug("Sending proposal with new block. round={}", roundState.getRoundIdentifier());
blockToPublish = blockCreator.createBlock(headerTimestamp);
blockToPublish = createBlockForProposalBehaviour.create(headerTimestamp);
} else {
LOG.debug(
"Sending proposal from PreparedCertificate. round={}", roundState.getRoundIdentifier());
@ -123,7 +154,7 @@ public class QbftRound {
bestPreparedCertificate.map(PreparedCertificate::getPrepares).orElse(emptyList()));
}
private void updateStateWithProposalAndTransmit(
protected void updateStateWithProposalAndTransmit(
final Block block,
final List<SignedData<RoundChangePayload>> roundChanges,
final List<SignedData<PreparePayload>> prepares) {

@ -19,8 +19,10 @@ import org.hyperledger.besu.consensus.common.bft.ConsensusRoundIdentifier;
import org.hyperledger.besu.consensus.common.bft.blockcreation.BftBlockCreator;
import org.hyperledger.besu.consensus.common.bft.blockcreation.BftBlockCreatorFactory;
import org.hyperledger.besu.consensus.common.bft.statemachine.BftFinalState;
import org.hyperledger.besu.consensus.qbft.QbftContext;
import org.hyperledger.besu.consensus.qbft.network.QbftMessageTransmitter;
import org.hyperledger.besu.consensus.qbft.payload.MessageFactory;
import org.hyperledger.besu.consensus.qbft.pki.PkiQbftCreateBlockForProposalBehaviour;
import org.hyperledger.besu.consensus.qbft.validation.MessageValidatorFactory;
import org.hyperledger.besu.ethereum.ProtocolContext;
import org.hyperledger.besu.ethereum.chain.MinedBlockObserver;
@ -80,6 +82,18 @@ public class QbftRoundFactory {
final QbftMessageTransmitter messageTransmitter =
new QbftMessageTransmitter(messageFactory, finalState.getValidatorMulticaster());
final QbftContext qbftContext = protocolContext.getConsensusState(QbftContext.class);
final CreateBlockForProposalBehaviour createBlockForProposalBehaviour;
if (qbftContext.getPkiBlockCreationConfiguration().isPresent()) {
createBlockForProposalBehaviour =
new PkiQbftCreateBlockForProposalBehaviour(
blockCreator,
qbftContext.getPkiBlockCreationConfiguration().get(),
bftExtraDataCodec);
} else {
createBlockForProposalBehaviour = blockCreator::createBlock;
}
return new QbftRound(
roundState,
blockCreator,
@ -90,6 +104,7 @@ public class QbftRoundFactory {
messageFactory,
messageTransmitter,
finalState.getRoundTimer(),
bftExtraDataCodec);
bftExtraDataCodec,
createBlockForProposalBehaviour);
}
}

@ -14,18 +14,27 @@
*/
package org.hyperledger.besu.consensus.qbft.validation;
import org.hyperledger.besu.consensus.common.bft.BftBlockInterface;
import org.hyperledger.besu.consensus.common.bft.BftExtraDataCodec;
import org.hyperledger.besu.consensus.common.bft.ConsensusRoundIdentifier;
import org.hyperledger.besu.consensus.common.bft.payload.SignedData;
import org.hyperledger.besu.consensus.qbft.QbftContext;
import org.hyperledger.besu.consensus.qbft.payload.ProposalPayload;
import org.hyperledger.besu.consensus.qbft.pki.PkiQbftBlockHeaderFunctions;
import org.hyperledger.besu.consensus.qbft.pki.PkiQbftExtraData;
import org.hyperledger.besu.consensus.qbft.pki.PkiQbftExtraDataCodec;
import org.hyperledger.besu.ethereum.BlockValidator;
import org.hyperledger.besu.ethereum.BlockValidator.BlockProcessingOutputs;
import org.hyperledger.besu.ethereum.ProtocolContext;
import org.hyperledger.besu.ethereum.core.Address;
import org.hyperledger.besu.ethereum.core.Block;
import org.hyperledger.besu.ethereum.core.Hash;
import org.hyperledger.besu.ethereum.mainnet.HeaderValidationMode;
import org.hyperledger.besu.pki.cms.CmsValidator;
import java.util.Optional;
import com.google.common.annotations.VisibleForTesting;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@ -38,16 +47,41 @@ public class ProposalPayloadValidator {
private final ConsensusRoundIdentifier targetRound;
private final BlockValidator blockValidator;
private final ProtocolContext protocolContext;
private final BftExtraDataCodec bftExtraDataCodec;
private final Optional<CmsValidator> cmsValidator;
public ProposalPayloadValidator(
final Address expectedProposer,
final ConsensusRoundIdentifier targetRound,
final BlockValidator blockValidator,
final ProtocolContext protocolContext) {
final ProtocolContext protocolContext,
final BftExtraDataCodec bftExtraDataCodec) {
this(
expectedProposer,
targetRound,
blockValidator,
protocolContext,
bftExtraDataCodec,
protocolContext
.getConsensusState(QbftContext.class)
.getPkiBlockCreationConfiguration()
.map(config -> new CmsValidator(config.getTrustStore())));
}
@VisibleForTesting
public ProposalPayloadValidator(
final Address expectedProposer,
final ConsensusRoundIdentifier targetRound,
final BlockValidator blockValidator,
final ProtocolContext protocolContext,
final BftExtraDataCodec bftExtraDataCodec,
final Optional<CmsValidator> cmsValidator) {
this.expectedProposer = expectedProposer;
this.targetRound = targetRound;
this.blockValidator = blockValidator;
this.protocolContext = protocolContext;
this.bftExtraDataCodec = bftExtraDataCodec;
this.cmsValidator = cmsValidator;
}
public boolean validate(final SignedData<ProposalPayload> signedPayload) {
@ -74,6 +108,13 @@ public class ProposalPayloadValidator {
return false;
}
if (cmsValidator.isPresent()) {
return validateCms(
block,
protocolContext.getConsensusState(QbftContext.class).getBlockInterface(),
cmsValidator.get());
}
return true;
}
@ -89,4 +130,26 @@ public class ProposalPayloadValidator {
return true;
}
private boolean validateCms(
final Block block,
final BftBlockInterface bftBlockInterface,
final CmsValidator cmsValidator) {
final PkiQbftExtraData pkiExtraData =
(PkiQbftExtraData) bftBlockInterface.getExtraData(block.getHeader());
final Hash hashWithoutCms =
PkiQbftBlockHeaderFunctions.forCmsSignature((PkiQbftExtraDataCodec) bftExtraDataCodec)
.hash(block.getHeader());
LOG.debug("Validating CMS with signed hash {} in block {}", hashWithoutCms, block.getHash());
if (!cmsValidator.validate(pkiExtraData.getCms(), hashWithoutCms)) {
LOG.info("{}: invalid CMS in block {}", ERROR_PREFIX, block.getHash());
return false;
} else {
LOG.trace("Valid CMS in block {}", block.getHash());
return true;
}
}
}

@ -77,7 +77,7 @@ public class ProposalValidator {
final ProposalPayloadValidator payloadValidator =
new ProposalPayloadValidator(
expectedProposer, roundIdentifier, blockValidator, protocolContext);
expectedProposer, roundIdentifier, blockValidator, protocolContext, bftExtraDataCodec);
if (!payloadValidator.validate(msg.getSignedPayload())) {
LOG.info("{}: invalid proposal payload in proposal message", ERROR_PREFIX);

@ -47,7 +47,10 @@ public class QbftBlockHeaderValidationRulesetFactoryTest {
private ProtocolContext protocolContext(final Collection<Address> validators) {
return new ProtocolContext(
null, null, setupContextWithBftExtraDataEncoder(validators, new QbftExtraDataCodec()));
null,
null,
setupContextWithBftExtraDataEncoder(
QbftContext.class, validators, new QbftExtraDataCodec()));
}
@Test

@ -21,6 +21,7 @@ import static org.mockito.Mockito.when;
import org.hyperledger.besu.consensus.common.bft.BftExtraData;
import org.hyperledger.besu.consensus.common.bft.Vote;
import org.hyperledger.besu.consensus.qbft.QbftContext;
import org.hyperledger.besu.ethereum.ProtocolContext;
import org.hyperledger.besu.ethereum.core.Address;
import org.hyperledger.besu.ethereum.core.AddressHelpers;
@ -43,7 +44,9 @@ public class QbftValidatorsValidationRuleTest {
new QbftValidatorsValidationRule(true);
final ProtocolContext context =
new ProtocolContext(
null, null, setupContextWithBftExtraData(Collections.emptyList(), bftExtraData));
null,
null,
setupContextWithBftExtraData(QbftContext.class, Collections.emptyList(), bftExtraData));
when(bftExtraData.getValidators()).thenReturn(Collections.emptyList());
when(bftExtraData.getVote()).thenReturn(Optional.empty());
assertThat(qbftValidatorsValidationRule.validate(blockHeader, null, context)).isTrue();
@ -58,7 +61,8 @@ public class QbftValidatorsValidationRuleTest {
AddressHelpers.ofValue(1), AddressHelpers.ofValue(2), AddressHelpers.ofValue(3));
final ProtocolContext context =
new ProtocolContext(null, null, setupContextWithBftExtraData(validators, bftExtraData));
new ProtocolContext(
null, null, setupContextWithBftExtraData(QbftContext.class, validators, bftExtraData));
when(bftExtraData.getValidators()).thenReturn(validators);
assertThat(qbftValidatorsValidationRule.validate(blockHeader, null, context)).isTrue();
}
@ -72,7 +76,8 @@ public class QbftValidatorsValidationRuleTest {
AddressHelpers.ofValue(1), AddressHelpers.ofValue(2), AddressHelpers.ofValue(3));
final ProtocolContext context =
new ProtocolContext(null, null, setupContextWithBftExtraData(validators, bftExtraData));
new ProtocolContext(
null, null, setupContextWithBftExtraData(QbftContext.class, validators, bftExtraData));
when(bftExtraData.getValidators()).thenReturn(validators);
assertThat(qbftValidatorsValidationRule.validate(blockHeader, null, context)).isFalse();
}
@ -83,7 +88,9 @@ public class QbftValidatorsValidationRuleTest {
new QbftValidatorsValidationRule(true);
final ProtocolContext context =
new ProtocolContext(
null, null, setupContextWithBftExtraData(Collections.emptyList(), bftExtraData));
null,
null,
setupContextWithBftExtraData(QbftContext.class, Collections.emptyList(), bftExtraData));
when(bftExtraData.getValidators()).thenReturn(Collections.emptyList());
when(bftExtraData.getVote()).thenReturn(Optional.of(mock(Vote.class)));
assertThat(qbftValidatorsValidationRule.validate(blockHeader, null, context)).isFalse();

@ -0,0 +1,85 @@
/*
* 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.qbft.pki;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import org.hyperledger.besu.consensus.common.bft.BftBlockHashing;
import org.hyperledger.besu.consensus.common.bft.BftExtraData;
import org.hyperledger.besu.consensus.common.bft.BftExtraDataFixture;
import org.hyperledger.besu.ethereum.core.BlockHeader;
import org.hyperledger.besu.ethereum.core.BlockHeaderTestFixture;
import org.hyperledger.besu.ethereum.core.Hash;
import org.apache.tuweni.bytes.Bytes;
import org.junit.Before;
import org.junit.Test;
public class PkiQbftBlockHashingTest {
private PkiQbftExtraDataCodec pkiExtraDataCodec = new PkiQbftExtraDataCodec();
private PkiQbftBlockHashing pkiQbftBlockHashing;
@Before
public void before() {
pkiExtraDataCodec = spy(new PkiQbftExtraDataCodec());
pkiQbftBlockHashing = new PkiQbftBlockHashing(pkiExtraDataCodec);
}
@Test
public void blockHashingUsesCorrectEncodingWithoutCmsMethodInCodec() {
final PkiQbftExtraData pkiQbftExtraData = createPkiQbftExtraData();
final BlockHeader headerWithExtraData =
new BlockHeaderTestFixture()
.number(1L)
.extraData(pkiExtraDataCodec.encode(pkiQbftExtraData))
.buildHeader();
// Expected hash using the extraData encoded by the encodeWithoutCms method of the codec
final Hash expectedHash =
Hash.hash(
BftBlockHashing.serializeHeader(
headerWithExtraData,
() -> pkiExtraDataCodec.encodeWithoutCms(pkiQbftExtraData),
pkiExtraDataCodec));
final Hash hash =
pkiQbftBlockHashing.calculateHashOfBftBlockForCmsSignature(headerWithExtraData);
assertThat(hash).isEqualTo(expectedHash);
/*
Verify that the encodeWithoutCms method was called twice, once when calculating the
expected hash and a second time as part of the hash calculation on
calculateHashOfBftBlockForCmsSignature
*/
verify(pkiExtraDataCodec, times(2)).encodeWithoutCms(any(PkiQbftExtraData.class));
}
private PkiQbftExtraData createPkiQbftExtraData() {
final BlockHeader blockHeader = new BlockHeaderTestFixture().buildHeader();
final BftExtraData extraData =
BftExtraDataFixture.createExtraData(blockHeader, pkiExtraDataCodec);
return new PkiQbftExtraData(extraData, Bytes.random(32));
}
}

@ -0,0 +1,132 @@
/*
* 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.qbft.pki;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.hyperledger.besu.consensus.common.bft.BftExtraDataFixture.createExtraData;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import org.hyperledger.besu.consensus.common.bft.BftBlockHeaderFunctions;
import org.hyperledger.besu.consensus.common.bft.BftExtraData;
import org.hyperledger.besu.consensus.qbft.QbftExtraDataCodec;
import org.hyperledger.besu.ethereum.blockcreation.BlockCreator;
import org.hyperledger.besu.ethereum.core.Block;
import org.hyperledger.besu.ethereum.core.BlockBody;
import org.hyperledger.besu.ethereum.core.BlockHeader;
import org.hyperledger.besu.ethereum.core.BlockHeaderTestFixture;
import org.hyperledger.besu.ethereum.core.Hash;
import org.hyperledger.besu.pki.cms.CmsCreator;
import java.util.Collections;
import org.apache.tuweni.bytes.Bytes;
import org.junit.Before;
import org.junit.Test;
public class PkiQbftCreateBlockForProposalBehaviourTest {
private final PkiQbftExtraDataCodec extraDataCodec = new PkiQbftExtraDataCodec();
private BlockCreator blockCreator;
private CmsCreator cmsCreator;
private PkiQbftCreateBlockForProposalBehaviour createBlockForProposalBehaviour;
private BlockHeaderTestFixture blockHeaderBuilder;
@Before
public void before() {
blockCreator = mock(BlockCreator.class);
cmsCreator = mock(CmsCreator.class);
createBlockForProposalBehaviour =
new PkiQbftCreateBlockForProposalBehaviour(blockCreator, cmsCreator, extraDataCodec);
blockHeaderBuilder = new BlockHeaderTestFixture();
}
@Test
public void createProposalBehaviourWithNonPkiCodecFails() {
assertThatThrownBy(
() ->
new PkiQbftCreateBlockForProposalBehaviour(
blockCreator, cmsCreator, new QbftExtraDataCodec()))
.isInstanceOf(IllegalArgumentException.class)
.hasMessageContaining(
"PkiQbftCreateBlockForProposalBehaviour must use PkiQbftExtraDataCodec");
}
@Test
public void cmsInProposedBlockHasValueCreatedByCmsCreator() {
createBlockBeingProposed();
final Bytes cms = Bytes.random(32);
when(cmsCreator.create(any(Bytes.class))).thenReturn(cms);
final Block proposedBlock = createBlockForProposalBehaviour.create(1L);
final PkiQbftExtraData proposedBlockExtraData =
(PkiQbftExtraData) extraDataCodec.decodeRaw(proposedBlock.getHeader().getExtraData());
assertThat(proposedBlockExtraData).isInstanceOf(PkiQbftExtraData.class);
assertThat(proposedBlockExtraData.getCms()).isEqualTo(cms);
}
@Test
public void cmsIsCreatedWithCorrectHashingFunction() {
final Block block = createBlockBeingProposed();
final Hash expectedHashForCmsCreation =
PkiQbftBlockHeaderFunctions.forCmsSignature(extraDataCodec).hash(block.getHeader());
when(cmsCreator.create(any(Bytes.class))).thenReturn(Bytes.random(32));
createBlockForProposalBehaviour.create(1L);
verify(cmsCreator).create(eq(expectedHashForCmsCreation));
}
@Test
public void proposedBlockHashUsesCommittedSealHeaderFunction() {
createBlockBeingProposed();
when(cmsCreator.create(any(Bytes.class))).thenReturn(Bytes.random(32));
final Block blockWithCms = createBlockForProposalBehaviour.create(1L);
final Hash expectedBlockHash =
BftBlockHeaderFunctions.forCommittedSeal(extraDataCodec).hash(blockWithCms.getHeader());
assertThat(blockWithCms.getHash()).isEqualTo(expectedBlockHash);
}
private Block createBlockBeingProposed() {
final BftExtraData originalExtraData =
createExtraData(blockHeaderBuilder.buildHeader(), extraDataCodec);
final BlockHeader blockHeaderWithExtraData =
blockHeaderBuilder.extraData(extraDataCodec.encode(originalExtraData)).buildHeader();
final Block block =
new Block(
blockHeaderWithExtraData,
new BlockBody(Collections.emptyList(), Collections.emptyList()));
when(blockCreator.createBlock(eq(1L))).thenReturn(block);
return block;
}
}

@ -1,5 +1,5 @@
/*
* Copyright ConsenSys AG.
* Copyright 2020 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
@ -18,6 +18,7 @@ package org.hyperledger.besu.consensus.qbft.pki;
import static org.assertj.core.api.Assertions.assertThat;
import static org.hyperledger.besu.consensus.qbft.QbftExtraDataCodecTestUtils.createNonEmptyVanityData;
import org.hyperledger.besu.consensus.common.bft.BftExtraData;
import org.hyperledger.besu.consensus.common.bft.Vote;
import org.hyperledger.besu.crypto.SECPSignature;
import org.hyperledger.besu.crypto.SignatureAlgorithm;
@ -41,25 +42,34 @@ public class PkiQbftExtraDataCodecTest {
private static final Supplier<SignatureAlgorithm> SIGNATURE_ALGORITHM =
Suppliers.memoize(SignatureAlgorithmFactory::getInstance);
private final String EMPTY_CMS_RAW_HEX_ENCODING_STRING =
"0xf8f3f8f0a00102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20ea94000000000000"
+ "0000000000000000000000000001940000000000000000000000000000000000000002d794000000000000"
+ "000000000000000000000000000181ff83fedcbaf886b84100000000000000000000000000000000000000"
+ "00000000000000000000000001000000000000000000000000000000000000000000000000000000000000"
+ "000a00b841000000000000000000000000000000000000000000000000000000000000000a000000000000"
+ "000000000000000000000000000000000000000000000000000100c0";
// Arbitrary bytes representing a non-empty CMS
private final Bytes cms = Bytes.fromHexString("0x01");
// Raw hex-encoded extra data with arbitrary CMS data (0x01)
private final String RAW_HEX_ENCODING_STRING =
"0xf8f4f8f0a00102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20ea94000000000000"
+ "0000000000000000000000000001940000000000000000000000000000000000000002d794000000000000"
+ "000000000000000000000000000181ff83fedcbaf886b84100000000000000000000000000000000000000"
+ "00000000000000000000000001000000000000000000000000000000000000000000000000000000000000"
+ "000a00b841000000000000000000000000000000000000000000000000000000000000000a000000000000"
+ "000000000000000000000000000000000000000000000000000100c101";
private final String RAW_EXCLUDE_COMMIT_SEALS_AND_ROUND_NUMBER_ENCODED_STRING =
"0xf867a00102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20ea940000000000000000"
+ "000000000000000000000001940000000000000000000000000000000000000002d7940000000000000000"
+ "00000000000000000000000181ff80c001";
private final String RAW_EXCLUDE_COMMIT_SEALS_ENCODED_STRING =
"0xf86aa00102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20ea940000000000000000"
+ "000000000000000000000001940000000000000000000000000000000000000002d7940000000000000000"
+ "00000000000000000000000181ff83fedcbac001";
private final String RAW_ALL_ENCODED_STRING =
"0xf8f1a00102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20ea940000000000000000"
+ "000000000000000000000001940000000000000000000000000000000000000002d7940000000000000000"
+ "00000000000000000000000181ff83fedcbaf886b841000000000000000000000000000000000000000000"
+ "0000000000000000000001000000000000000000000000000000000000000000000000000000000000000a"
+ "00b841000000000000000000000000000000000000000000000000000000000000000a0000000000000000"
+ "0000000000000000000000000000000000000000000000010001";
private final String RAW_QBFT_EXTRA_DATA =
"0xf8f0a00102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20ea940000000000000000"
+ "000000000000000000000001940000000000000000000000000000000000000002d7940000000000000000"
+ "00000000000000000000000181ff83fedcbaf886b841000000000000000000000000000000000000000000"
+ "0000000000000000000001000000000000000000000000000000000000000000000000000000000000000a"
+ "00b841000000000000000000000000000000000000000000000000000000000000000a0000000000000000"
+ "00000000000000000000000000000000000000000000000100";
private final PkiQbftExtraDataCodec bftExtraDataCodec = new PkiQbftExtraDataCodec();
@ -79,8 +89,6 @@ public class PkiQbftExtraDataCodecTest {
final Bytes vanity_data = Bytes.wrap(vanity_bytes);
final BytesValueRLPOutput encoder = new BytesValueRLPOutput();
encoder.startList(); // start envelope list
encoder.startList(); // start extra data list
// vanity data
encoder.writeBytes(vanity_data);
@ -95,13 +103,54 @@ public class PkiQbftExtraDataCodecTest {
encoder.writeIntScalar(round);
// committer seals
encoder.writeList(committerSeals, (committer, rlp) -> rlp.writeBytes(committer.encodedBytes()));
// cms
encoder.writeBytes(cms);
encoder.endList(); // end extra data list
encoder.startList(); // start cms list
encoder.writeBytes(cms);
encoder.endList(); // end cms list
final Bytes bufferToInject = encoder.encoded();
encoder.endList(); // end envelope list
final PkiQbftExtraData extraData =
(PkiQbftExtraData) bftExtraDataCodec.decodeRaw(bufferToInject);
assertThat(extraData.getVanityData()).isEqualTo(vanity_data);
assertThat(extraData.getRound()).isEqualTo(round);
assertThat(extraData.getSeals()).isEqualTo(committerSeals);
assertThat(extraData.getValidators()).isEqualTo(validators);
assertThat(extraData.getCms()).isEqualTo(cms);
}
@Test
public void decodingQbftExtraDataDelegatesToQbftCodec() {
final List<Address> validators =
Arrays.asList(Address.fromHexString("1"), Address.fromHexString("2"));
final int round = 0x00FEDCBA;
final List<SECPSignature> committerSeals =
Arrays.asList(
SIGNATURE_ALGORITHM.get().createSignature(BigInteger.ONE, BigInteger.TEN, (byte) 0),
SIGNATURE_ALGORITHM.get().createSignature(BigInteger.TEN, BigInteger.ONE, (byte) 0));
// Create randomised vanity data.
final byte[] vanity_bytes = createNonEmptyVanityData();
new Random().nextBytes(vanity_bytes);
final Bytes vanity_data = Bytes.wrap(vanity_bytes);
final BytesValueRLPOutput encoder = new BytesValueRLPOutput();
encoder.startList(); // start extra data list
// vanity data
encoder.writeBytes(vanity_data);
// validators
encoder.writeList(validators, (validator, rlp) -> rlp.writeBytes(validator));
// votes
encoder.startList();
encoder.writeBytes(Address.fromHexString("1"));
encoder.writeByte(Vote.ADD_BYTE_VALUE);
encoder.endList();
// rounds
encoder.writeIntScalar(round);
// committer seals
encoder.writeList(committerSeals, (committer, rlp) -> rlp.writeBytes(committer.encodedBytes()));
// Not including the CMS in the list (to generate a non-pki QBFT extra data)
encoder.endList(); // end extra data list
final Bytes bufferToInject = encoder.encoded();
@ -112,30 +161,70 @@ public class PkiQbftExtraDataCodecTest {
assertThat(extraData.getRound()).isEqualTo(round);
assertThat(extraData.getSeals()).isEqualTo(committerSeals);
assertThat(extraData.getValidators()).isEqualTo(validators);
assertThat(extraData.getCms()).hasValue(cms);
assertThat(extraData.getCms()).isEqualTo(Bytes.EMPTY);
}
/*
When encoding for blockchain, we ignore commit seals and round number, but we include the CMS
*/
@Test
public void encodingForBlockchainShouldIncludeCms() {
final Bytes expectedRawDecoding =
Bytes.fromHexString(RAW_EXCLUDE_COMMIT_SEALS_AND_ROUND_NUMBER_ENCODED_STRING);
final Bytes encoded =
bftExtraDataCodec.encodeWithoutCommitSealsAndRoundNumber(getDecodedExtraData(cms));
assertThat(encoded).isEqualTo(expectedRawDecoding);
}
@Test
public void encodingWithoutCommitSealsShouldIncludeCms() {
final Bytes expectedRawDecoding = Bytes.fromHexString(RAW_EXCLUDE_COMMIT_SEALS_ENCODED_STRING);
final Bytes encoded = bftExtraDataCodec.encodeWithoutCommitSeals(getDecodedExtraData(cms));
assertThat(encoded).isEqualTo(expectedRawDecoding);
}
@Test
public void encodingExtraDataWithEmptyCmsMatchesKnownRawHexString() {
final Bytes expectedRawDecoding = Bytes.fromHexString(EMPTY_CMS_RAW_HEX_ENCODING_STRING);
final Bytes encoded = bftExtraDataCodec.encode(getDecodedExtraDataWithEmptyCms());
public void encodingWithAllShouldIncludeCms() {
final Bytes expectedRawDecoding = Bytes.fromHexString(RAW_ALL_ENCODED_STRING);
final Bytes encoded = bftExtraDataCodec.encode(getDecodedExtraData(cms));
assertThat(encoded).isEqualTo(expectedRawDecoding);
}
/*
When encoding for proposal, we include commit seals and round number, but we ignore the CMS
*/
@Test
public void encodingMatchesKnownRawHexString() {
final Bytes expectedRawDecoding = Bytes.fromHexString(RAW_HEX_ENCODING_STRING);
final Bytes encoded = bftExtraDataCodec.encode(getDecodedExtraData(Optional.of(cms)));
public void encodingForCreatingCmsProposal() {
final Bytes expectedRawDecoding = Bytes.fromHexString(RAW_QBFT_EXTRA_DATA);
final Bytes encoded = bftExtraDataCodec.encodeWithoutCms(getDecodedExtraData(cms));
assertThat(encoded).isEqualTo(expectedRawDecoding);
}
private static PkiQbftExtraData getDecodedExtraDataWithEmptyCms() {
return getDecodedExtraData(Optional.empty());
/*
When encoding non-pki extra data, we delegate to the regular QBFT encoder
*/
@Test
public void encodingQbftExtraData() {
final Bytes expectedRawDecoding = Bytes.fromHexString(RAW_QBFT_EXTRA_DATA);
final PkiQbftExtraData pkiBftExtraData = getDecodedExtraData(cms);
final BftExtraData bftExtraData =
new BftExtraData(
pkiBftExtraData.getVanityData(),
pkiBftExtraData.getSeals(),
pkiBftExtraData.getVote(),
pkiBftExtraData.getRound(),
pkiBftExtraData.getValidators());
final Bytes encoded = bftExtraDataCodec.encode(bftExtraData);
assertThat(encoded).isEqualTo(expectedRawDecoding);
}
private static PkiQbftExtraData getDecodedExtraData(final Optional<Bytes> cms) {
private static PkiQbftExtraData getDecodedExtraData(final Bytes cms) {
final List<Address> validators =
Arrays.asList(Address.fromHexString("1"), Address.fromHexString("2"));
final Optional<Vote> vote = Optional.of(Vote.authVote(Address.fromHexString("1")));

@ -40,6 +40,7 @@ import org.hyperledger.besu.consensus.common.bft.blockcreation.BftBlockCreator;
import org.hyperledger.besu.consensus.common.bft.events.RoundExpiry;
import org.hyperledger.besu.consensus.common.bft.network.ValidatorMulticaster;
import org.hyperledger.besu.consensus.common.bft.statemachine.BftFinalState;
import org.hyperledger.besu.consensus.qbft.QbftContext;
import org.hyperledger.besu.consensus.qbft.QbftExtraDataCodec;
import org.hyperledger.besu.consensus.qbft.messagedata.RoundChangeMessageData;
import org.hyperledger.besu.consensus.qbft.messagewrappers.Commit;
@ -148,7 +149,10 @@ public class QbftBlockHeightManagerTest {
protocolContext =
new ProtocolContext(
null, null, setupContextWithBftExtraDataEncoder(validators, new QbftExtraDataCodec()));
null,
null,
setupContextWithBftExtraDataEncoder(
QbftContext.class, validators, new QbftExtraDataCodec()));
// Ensure the created IbftRound has the valid ConsensusRoundIdentifier;
when(roundFactory.createNewRound(any(), anyInt()))

@ -36,6 +36,7 @@ import org.hyperledger.besu.consensus.common.bft.ConsensusRoundIdentifier;
import org.hyperledger.besu.consensus.common.bft.RoundTimer;
import org.hyperledger.besu.consensus.common.bft.blockcreation.BftBlockCreator;
import org.hyperledger.besu.consensus.common.bft.payload.SignedData;
import org.hyperledger.besu.consensus.qbft.QbftContext;
import org.hyperledger.besu.consensus.qbft.QbftExtraDataCodec;
import org.hyperledger.besu.consensus.qbft.messagewrappers.RoundChange;
import org.hyperledger.besu.consensus.qbft.network.QbftMessageTransmitter;
@ -109,7 +110,8 @@ public class QbftRoundTest {
new ProtocolContext(
blockChain,
worldStateArchive,
setupContextWithBftExtraDataEncoder(emptyList(), new QbftExtraDataCodec()));
setupContextWithBftExtraDataEncoder(
QbftContext.class, emptyList(), new QbftExtraDataCodec()));
when(messageValidator.validateProposal(any())).thenReturn(true);
when(messageValidator.validatePrepare(any())).thenReturn(true);

@ -21,12 +21,18 @@ import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import org.hyperledger.besu.consensus.common.bft.BftBlockHeaderFunctions;
import org.hyperledger.besu.consensus.common.bft.BftExtraDataCodec;
import org.hyperledger.besu.consensus.common.bft.ConsensusRoundHelpers;
import org.hyperledger.besu.consensus.common.bft.ConsensusRoundIdentifier;
import org.hyperledger.besu.consensus.common.bft.ProposedBlockHelpers;
import org.hyperledger.besu.consensus.qbft.QbftContext;
import org.hyperledger.besu.consensus.qbft.QbftExtraDataCodec;
import org.hyperledger.besu.consensus.qbft.messagewrappers.Proposal;
import org.hyperledger.besu.consensus.qbft.payload.MessageFactory;
import org.hyperledger.besu.consensus.qbft.pki.PkiQbftBlockHeaderFunctions;
import org.hyperledger.besu.consensus.qbft.pki.PkiQbftExtraData;
import org.hyperledger.besu.consensus.qbft.pki.PkiQbftExtraDataCodec;
import org.hyperledger.besu.crypto.NodeKey;
import org.hyperledger.besu.crypto.NodeKeyUtils;
import org.hyperledger.besu.ethereum.BlockValidator;
@ -35,12 +41,19 @@ import org.hyperledger.besu.ethereum.ProtocolContext;
import org.hyperledger.besu.ethereum.chain.MutableBlockchain;
import org.hyperledger.besu.ethereum.core.Address;
import org.hyperledger.besu.ethereum.core.Block;
import org.hyperledger.besu.ethereum.core.BlockDataGenerator;
import org.hyperledger.besu.ethereum.core.BlockDataGenerator.BlockOptions;
import org.hyperledger.besu.ethereum.core.Hash;
import org.hyperledger.besu.ethereum.core.Util;
import org.hyperledger.besu.ethereum.mainnet.HeaderValidationMode;
import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive;
import org.hyperledger.besu.pki.cms.CmsValidator;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import org.apache.tuweni.bytes.Bytes;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@ -53,6 +66,7 @@ public class ProposalPayloadValidatorTest {
@Mock private BlockValidator blockValidator;
@Mock private MutableBlockchain blockChain;
@Mock private WorldStateArchive worldStateArchive;
@Mock private CmsValidator cmsValidator;
private ProtocolContext protocolContext;
private static final int CHAIN_HEIGHT = 3;
@ -64,7 +78,7 @@ public class ProposalPayloadValidatorTest {
private final MessageFactory messageFactory = new MessageFactory(nodeKey);
final ConsensusRoundIdentifier roundIdentifier =
ConsensusRoundHelpers.createFrom(targetRound, 1, 0);
final QbftExtraDataCodec bftExtraDataEncoder = new QbftExtraDataCodec();
final QbftExtraDataCodec bftExtraDataCodec = new QbftExtraDataCodec();
@Before
public void setup() {
@ -72,16 +86,16 @@ public class ProposalPayloadValidatorTest {
new ProtocolContext(
blockChain,
worldStateArchive,
setupContextWithBftExtraDataEncoder(emptyList(), bftExtraDataEncoder));
setupContextWithBftExtraDataEncoder(QbftContext.class, emptyList(), bftExtraDataCodec));
}
@Test
public void validationPassesWhenProposerAndRoundMatchAndBlockIsValid() {
final ProposalPayloadValidator payloadValidator =
new ProposalPayloadValidator(
expectedProposer, roundIdentifier, blockValidator, protocolContext);
expectedProposer, roundIdentifier, blockValidator, protocolContext, bftExtraDataCodec);
final Block block =
ProposedBlockHelpers.createProposalBlock(emptyList(), roundIdentifier, bftExtraDataEncoder);
ProposedBlockHelpers.createProposalBlock(emptyList(), roundIdentifier, bftExtraDataCodec);
final Proposal proposal =
messageFactory.createProposal(roundIdentifier, block, emptyList(), emptyList());
@ -99,13 +113,13 @@ public class ProposalPayloadValidatorTest {
public void validationPassesWhenBlockRoundDoesNotMatchProposalRound() {
final ProposalPayloadValidator payloadValidator =
new ProposalPayloadValidator(
expectedProposer, roundIdentifier, blockValidator, protocolContext);
expectedProposer, roundIdentifier, blockValidator, protocolContext, bftExtraDataCodec);
final Block block =
ProposedBlockHelpers.createProposalBlock(
emptyList(),
ConsensusRoundHelpers.createFrom(roundIdentifier, 0, +1),
bftExtraDataEncoder);
bftExtraDataCodec);
final Proposal proposal =
messageFactory.createProposal(roundIdentifier, block, emptyList(), emptyList());
@ -126,9 +140,9 @@ public class ProposalPayloadValidatorTest {
final ProposalPayloadValidator payloadValidator =
new ProposalPayloadValidator(
expectedProposer, roundIdentifier, blockValidator, protocolContext);
expectedProposer, roundIdentifier, blockValidator, protocolContext, bftExtraDataCodec);
final Block block =
ProposedBlockHelpers.createProposalBlock(emptyList(), roundIdentifier, bftExtraDataEncoder);
ProposedBlockHelpers.createProposalBlock(emptyList(), roundIdentifier, bftExtraDataCodec);
final Proposal proposal =
messageFactory.createProposal(roundIdentifier, block, emptyList(), emptyList());
@ -146,7 +160,11 @@ public class ProposalPayloadValidatorTest {
public void validationFailsWhenExpectedProposerDoesNotMatchPayloadsAuthor() {
final ProposalPayloadValidator payloadValidator =
new ProposalPayloadValidator(
Address.fromHexString("0x1"), roundIdentifier, blockValidator, protocolContext);
Address.fromHexString("0x1"),
roundIdentifier,
blockValidator,
protocolContext,
bftExtraDataCodec);
final Block block = ProposedBlockHelpers.createProposalBlock(emptyList(), roundIdentifier);
final Proposal proposal =
messageFactory.createProposal(roundIdentifier, block, emptyList(), emptyList());
@ -159,7 +177,7 @@ public class ProposalPayloadValidatorTest {
public void validationFailsWhenMessageMismatchesExpectedRound() {
final ProposalPayloadValidator payloadValidator =
new ProposalPayloadValidator(
expectedProposer, roundIdentifier, blockValidator, protocolContext);
expectedProposer, roundIdentifier, blockValidator, protocolContext, bftExtraDataCodec);
final Block block = ProposedBlockHelpers.createProposalBlock(emptyList(), roundIdentifier);
final Proposal proposal =
@ -177,7 +195,7 @@ public class ProposalPayloadValidatorTest {
public void validationFailsWhenMessageMismatchesExpectedHeight() {
final ProposalPayloadValidator payloadValidator =
new ProposalPayloadValidator(
expectedProposer, roundIdentifier, blockValidator, protocolContext);
expectedProposer, roundIdentifier, blockValidator, protocolContext, bftExtraDataCodec);
final Block block = ProposedBlockHelpers.createProposalBlock(emptyList(), roundIdentifier);
final Proposal proposal =
@ -195,12 +213,12 @@ public class ProposalPayloadValidatorTest {
public void validationFailsForBlockWithIncorrectHeight() {
final ProposalPayloadValidator payloadValidator =
new ProposalPayloadValidator(
expectedProposer, roundIdentifier, blockValidator, protocolContext);
expectedProposer, roundIdentifier, blockValidator, protocolContext, bftExtraDataCodec);
final Block block =
ProposedBlockHelpers.createProposalBlock(
emptyList(),
ConsensusRoundHelpers.createFrom(roundIdentifier, +1, 0),
bftExtraDataEncoder);
bftExtraDataCodec);
final Proposal proposal =
messageFactory.createProposal(roundIdentifier, block, emptyList(), emptyList());
@ -213,4 +231,102 @@ public class ProposalPayloadValidatorTest {
assertThat(payloadValidator.validate(proposal.getSignedPayload())).isFalse();
}
@Test
public void validationForCmsFailsWhenCmsFailsValidation() {
final PkiQbftExtraDataCodec pkiQbftExtraDataCodec = new PkiQbftExtraDataCodec();
final QbftContext qbftContext =
setupContextWithBftExtraDataEncoder(QbftContext.class, emptyList(), pkiQbftExtraDataCodec);
final Bytes cms = Bytes.fromHexStringLenient("0x1");
final ProtocolContext protocolContext =
new ProtocolContext(blockChain, worldStateArchive, qbftContext);
final ProposalPayloadValidator payloadValidator =
new ProposalPayloadValidator(
expectedProposer,
roundIdentifier,
blockValidator,
protocolContext,
pkiQbftExtraDataCodec,
Optional.of(cmsValidator));
final Block block =
createPkiProposalBlock(emptyList(), roundIdentifier, pkiQbftExtraDataCodec, cms);
final Proposal proposal =
messageFactory.createProposal(roundIdentifier, block, emptyList(), emptyList());
final Hash hashWithoutCms =
PkiQbftBlockHeaderFunctions.forCmsSignature(pkiQbftExtraDataCodec).hash(block.getHeader());
when(blockValidator.validateAndProcessBlock(
eq(protocolContext),
eq(block),
eq(HeaderValidationMode.LIGHT),
eq(HeaderValidationMode.FULL)))
.thenReturn(Optional.of(new BlockProcessingOutputs(null, null)));
when(cmsValidator.validate(eq(cms), eq(hashWithoutCms))).thenReturn(false);
assertThat(payloadValidator.validate(proposal.getSignedPayload())).isFalse();
}
@Test
public void validationForCmsPassesWhenCmsIsValid() {
final PkiQbftExtraDataCodec pkiQbftExtraDataCodec = new PkiQbftExtraDataCodec();
final QbftContext qbftContext =
setupContextWithBftExtraDataEncoder(QbftContext.class, emptyList(), pkiQbftExtraDataCodec);
final Bytes cms = Bytes.fromHexStringLenient("0x1");
final ProtocolContext protocolContext =
new ProtocolContext(blockChain, worldStateArchive, qbftContext);
final ProposalPayloadValidator payloadValidator =
new ProposalPayloadValidator(
expectedProposer,
roundIdentifier,
blockValidator,
protocolContext,
pkiQbftExtraDataCodec,
Optional.of(cmsValidator));
final Block block =
createPkiProposalBlock(emptyList(), roundIdentifier, pkiQbftExtraDataCodec, cms);
final Proposal proposal =
messageFactory.createProposal(roundIdentifier, block, emptyList(), emptyList());
final Hash hashWithoutCms =
PkiQbftBlockHeaderFunctions.forCmsSignature(pkiQbftExtraDataCodec).hash(block.getHeader());
when(blockValidator.validateAndProcessBlock(
eq(protocolContext),
eq(block),
eq(HeaderValidationMode.LIGHT),
eq(HeaderValidationMode.FULL)))
.thenReturn(Optional.of(new BlockProcessingOutputs(null, null)));
when(cmsValidator.validate(eq(cms), eq(hashWithoutCms))).thenReturn(true);
assertThat(payloadValidator.validate(proposal.getSignedPayload())).isTrue();
}
public static Block createPkiProposalBlock(
final List<Address> validators,
final ConsensusRoundIdentifier roundId,
final BftExtraDataCodec bftExtraDataCodec,
final Bytes cms) {
final Bytes extraData =
bftExtraDataCodec.encode(
new PkiQbftExtraData(
Bytes.wrap(new byte[32]),
Collections.emptyList(),
Optional.empty(),
roundId.getRoundNumber(),
validators,
cms));
final BlockOptions blockOptions =
BlockOptions.create()
.setExtraData(extraData)
.setBlockNumber(roundId.getSequenceNumber())
.setBlockHeaderFunctions(BftBlockHeaderFunctions.forCommittedSeal(bftExtraDataCodec))
.hasOmmers(false)
.hasTransactions(false);
if (validators.size() > 0) {
blockOptions.setCoinbase(validators.get(0));
}
return new BlockDataGenerator().block(blockOptions);
}
}

@ -28,6 +28,7 @@ import org.hyperledger.besu.consensus.common.bft.ConsensusRoundHelpers;
import org.hyperledger.besu.consensus.common.bft.ConsensusRoundIdentifier;
import org.hyperledger.besu.consensus.common.bft.ProposedBlockHelpers;
import org.hyperledger.besu.consensus.common.bft.payload.SignedData;
import org.hyperledger.besu.consensus.qbft.QbftContext;
import org.hyperledger.besu.consensus.qbft.QbftExtraDataCodec;
import org.hyperledger.besu.consensus.qbft.messagewrappers.Prepare;
import org.hyperledger.besu.consensus.qbft.messagewrappers.Proposal;
@ -95,7 +96,8 @@ public class ProposalValidatorTest {
new ProtocolContext(
blockChain,
worldStateArchive,
setupContextWithBftExtraDataEncoder(emptyList(), bftExtraDataEncoder));
setupContextWithBftExtraDataEncoder(
QbftContext.class, emptyList(), bftExtraDataEncoder));
// typically tests require the blockValidation to be successful
when(blockValidator.validateAndProcessBlock(

@ -29,6 +29,7 @@ import org.hyperledger.besu.consensus.common.bft.ConsensusRoundHelpers;
import org.hyperledger.besu.consensus.common.bft.ConsensusRoundIdentifier;
import org.hyperledger.besu.consensus.common.bft.ProposedBlockHelpers;
import org.hyperledger.besu.consensus.common.bft.payload.SignedData;
import org.hyperledger.besu.consensus.qbft.QbftContext;
import org.hyperledger.besu.consensus.qbft.QbftExtraDataCodec;
import org.hyperledger.besu.consensus.qbft.messagewrappers.RoundChange;
import org.hyperledger.besu.consensus.qbft.payload.PreparedRoundMetadata;
@ -79,7 +80,8 @@ public class RoundChangeMessageValidatorTest {
new ProtocolContext(
blockChain,
worldStateArchive,
setupContextWithBftExtraDataEncoder(emptyList(), bftExtraDataEncoder));
setupContextWithBftExtraDataEncoder(
QbftContext.class, emptyList(), bftExtraDataEncoder));
}
@Test

@ -70,6 +70,10 @@ public class CmsValidator {
* is trusted, otherwise returns false.
*/
public boolean validate(final Bytes cms, final Bytes expectedContent) {
if (cms == null || cms == Bytes.EMPTY) {
return false;
}
try {
LOGGER.trace("Validating CMS message");

@ -220,6 +220,13 @@ public class CmsCreationAndValidationTest {
cmsValidator = new CmsValidator(truststoreWrapper);
}
@Test
public void cmsValidationWithEmptyCmsMessage() {
final Bytes data = Bytes.random(32);
assertThat(cmsValidator.validate(Bytes.EMPTY, data)).isFalse();
}
@Test
public void cmsValidationWithTrustedSelfSignedCertificate() {
final CmsCreator cmsCreator = new CmsCreator(keystoreWrapper, "trusted_selfsigned");

Loading…
Cancel
Save