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. 78
      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) - \[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) - 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) - 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 ### Bug Fixes
- Consider effective price and effective priority fee in transaction replacement rules [\#2529](https://github.com/hyperledger/besu/issues/2529) - 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<>(); final List<Object[]> ret = new ArrayList<>();
ret.addAll( ret.addAll(
List.of( List.of(
new Object[] {
"qbft-pki",
new PkiQbftAcceptanceTestParameterization(
BesuNodeFactory::createPkiQbftNode,
BesuNodeFactory::createPkiQbftNodeWithValidators)
},
new Object[] { new Object[] {
"qbft-tls-jks", "qbft-tls-jks",
new PkiQbftAcceptanceTestParameterization( 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.ValidatorProvider;
import org.hyperledger.besu.consensus.common.validator.blockbased.BlockValidatorProvider; import org.hyperledger.besu.consensus.common.validator.blockbased.BlockValidatorProvider;
import org.hyperledger.besu.consensus.qbft.QbftBlockHeaderValidationRulesetFactory; 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.QbftExtraDataCodec;
import org.hyperledger.besu.consensus.qbft.QbftGossip; import org.hyperledger.besu.consensus.qbft.QbftGossip;
import org.hyperledger.besu.consensus.qbft.blockcreation.QbftBlockCreatorFactory; import org.hyperledger.besu.consensus.qbft.blockcreation.QbftBlockCreatorFactory;
import org.hyperledger.besu.consensus.qbft.jsonrpc.QbftJsonRpcMethods; import org.hyperledger.besu.consensus.qbft.jsonrpc.QbftJsonRpcMethods;
import org.hyperledger.besu.consensus.qbft.payload.MessageFactory; 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.pki.PkiQbftExtraDataCodec;
import org.hyperledger.besu.consensus.qbft.protocol.Istanbul100SubProtocol; import org.hyperledger.besu.consensus.qbft.protocol.Istanbul100SubProtocol;
import org.hyperledger.besu.consensus.qbft.statemachine.QbftBlockHeightManagerFactory; import org.hyperledger.besu.consensus.qbft.statemachine.QbftBlockHeightManagerFactory;
@ -299,15 +299,8 @@ public class QbftBesuControllerBuilder extends BftBesuControllerBuilder {
validatorProvider = new TransactionValidatorProvider(blockchain, validatorContractController); validatorProvider = new TransactionValidatorProvider(blockchain, validatorContractController);
} }
if (pkiBlockCreationConfiguration.isPresent()) { return new QbftContext(
return new PkiQbftContext( validatorProvider, epochManager, bftBlockInterface().get(), pkiBlockCreationConfiguration);
validatorProvider,
epochManager,
bftBlockInterface().get(),
pkiBlockCreationConfiguration.get());
} else {
return new BftContext(validatorProvider, epochManager, bftBlockInterface().get());
}
} }
private BftValidatorOverrides convertBftForks(final List<BftFork> bftForks) { private BftValidatorOverrides convertBftForks(final List<BftFork> bftForks) {

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

@ -15,12 +15,15 @@
package org.hyperledger.besu.consensus.common.bft; package org.hyperledger.besu.consensus.common.bft;
import static java.util.Collections.emptyList; import static java.util.Collections.emptyList;
import static java.util.Collections.singletonList;
import org.hyperledger.besu.crypto.NodeKey; import org.hyperledger.besu.crypto.NodeKey;
import org.hyperledger.besu.crypto.NodeKeyUtils;
import org.hyperledger.besu.crypto.SECPSignature; import org.hyperledger.besu.crypto.SECPSignature;
import org.hyperledger.besu.ethereum.core.Address; import org.hyperledger.besu.ethereum.core.Address;
import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.core.BlockHeader;
import org.hyperledger.besu.ethereum.core.Hash; import org.hyperledger.besu.ethereum.core.Hash;
import org.hyperledger.besu.ethereum.core.Util;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
@ -31,6 +34,22 @@ import org.apache.tuweni.bytes.Bytes;
public class BftExtraDataFixture { 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( public static BftExtraData createExtraData(
final BlockHeader header, final BlockHeader header,
final Bytes vanityData, 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.ValidatorProvider;
import org.hyperledger.besu.consensus.common.validator.blockbased.BlockValidatorProvider; import org.hyperledger.besu.consensus.common.validator.blockbased.BlockValidatorProvider;
import org.hyperledger.besu.consensus.qbft.QbftBlockHeaderValidationRulesetFactory; 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.QbftExtraDataCodec;
import org.hyperledger.besu.consensus.qbft.QbftGossip; import org.hyperledger.besu.consensus.qbft.QbftGossip;
import org.hyperledger.besu.consensus.qbft.blockcreation.QbftBlockCreatorFactory; import org.hyperledger.besu.consensus.qbft.blockcreation.QbftBlockCreatorFactory;
@ -400,7 +401,7 @@ public class TestContextBuilder {
new ProtocolContext( new ProtocolContext(
blockChain, blockChain,
worldStateArchive, worldStateArchive,
new BftContext(validatorProvider, epochManager, blockInterface)); new QbftContext(validatorProvider, epochManager, blockInterface, Optional.empty()));
final GasPricePendingTransactionsSorter pendingTransactions = final GasPricePendingTransactionsSorter pendingTransactions =
new GasPricePendingTransactionsSorter( 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.RoundTimer;
import org.hyperledger.besu.consensus.common.bft.blockcreation.BftBlockCreator; import org.hyperledger.besu.consensus.common.bft.blockcreation.BftBlockCreator;
import org.hyperledger.besu.consensus.common.bft.inttest.StubValidatorMulticaster; 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.QbftExtraDataCodec;
import org.hyperledger.besu.consensus.qbft.network.QbftMessageTransmitter; import org.hyperledger.besu.consensus.qbft.network.QbftMessageTransmitter;
import org.hyperledger.besu.consensus.qbft.payload.MessageFactory; import org.hyperledger.besu.consensus.qbft.payload.MessageFactory;
@ -115,7 +116,8 @@ public class QbftRoundIntegrationTest {
new ProtocolContext( new ProtocolContext(
blockChain, blockChain,
worldStateArchive, worldStateArchive,
setupContextWithBftExtraDataEncoder(emptyList(), qbftExtraDataEncoder)); setupContextWithBftExtraDataEncoder(
QbftContext.class, emptyList(), qbftExtraDataEncoder));
} }
@Test @Test

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

@ -87,7 +87,7 @@ public class QbftExtraDataCodec extends BftExtraDataCodec {
final List<SECPSignature> seals = final List<SECPSignature> seals =
rlpInput.readList( rlpInput.readList(
rlp -> SignatureAlgorithmFactory.getInstance().decodeSignature(rlp.readBytes())); rlp -> SignatureAlgorithmFactory.getInstance().decodeSignature(rlp.readBytes()));
rlpInput.leaveList(); rlpInput.leaveListLenient();
return new BftExtraData(vanityData, seals, vote, round, validators); 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 { public class PkiQbftExtraData extends BftExtraData {
private final Optional<Bytes> cms; private final Bytes cms;
public PkiQbftExtraData( public PkiQbftExtraData(
final Bytes vanityData, final Bytes vanityData,
@ -35,12 +35,12 @@ public class PkiQbftExtraData extends BftExtraData {
final Optional<Vote> vote, final Optional<Vote> vote,
final int round, final int round,
final Collection<Address> validators, final Collection<Address> validators,
final Optional<Bytes> cms) { final Bytes cms) {
super(vanityData, seals, vote, round, validators); super(vanityData, seals, vote, round, validators);
this.cms = cms; this.cms = cms;
} }
PkiQbftExtraData(final BftExtraData bftExtraData, final Optional<Bytes> cms) { PkiQbftExtraData(final BftExtraData bftExtraData, final Bytes cms) {
this( this(
bftExtraData.getVanityData(), bftExtraData.getVanityData(),
bftExtraData.getSeals(), bftExtraData.getSeals(),
@ -50,7 +50,7 @@ public class PkiQbftExtraData extends BftExtraData {
cms); cms);
} }
public Optional<Bytes> getCms() { public Bytes getCms() {
return cms; return cms;
} }
} }

@ -1,13 +1,16 @@
/* /*
* Copyright ConsenSys AG. * 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 * the License. You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * 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 * Unless required by applicable law or agreed to in writing, software distributed under the
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for 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. * specific language governing permissions and limitations under the License.
* *
* SPDX-License-Identifier: Apache-2.0 * 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.BytesValueRLPOutput;
import org.hyperledger.besu.ethereum.rlp.RLPInput; import org.hyperledger.besu.ethereum.rlp.RLPInput;
import java.util.Optional; import java.util.List;
import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.bytes.Bytes;
/* /*
The PkiQbftExtraData encoding format is different from the "regular" QbftExtraData encoding. The PkiQbftExtraData encoding format is different from the "regular" QbftExtraData encoding. We
We have an "envelope" list, with two elements: the extra data and the cms message. have an extra bytes element in the end of the list.
The RLP encoding format is as follows: ["extra_data", ["cms"]]
*/ */
public class PkiQbftExtraDataCodec extends QbftExtraDataCodec { public class PkiQbftExtraDataCodec extends QbftExtraDataCodec {
public static final int QBFT_EXTRA_DATA_LIST_SIZE = 5;
@Override @Override
public BftExtraData decodeRaw(final Bytes input) { public BftExtraData decodeRaw(final Bytes input) {
if (input.isEmpty()) { if (input.isEmpty()) {
throw new IllegalArgumentException("Invalid Bytes supplied - Bft Extra Data required."); throw new IllegalArgumentException("Invalid Bytes supplied - Bft Extra Data required.");
} }
final RLPInput rlpInput = new BytesValueRLPInput(input, false); final BftExtraData bftExtraData = super.decodeRaw(input);
rlpInput.enterList();
// Consume all the ExtraData input from the envelope list, and decode it using the QBFT decoder final RLPInput rlpInput = new BytesValueRLPInput(input, false);
final Bytes extraDataListAsBytes = rlpInput.currentListAsBytes();
final BftExtraData bftExtraData = super.decodeRaw(extraDataListAsBytes);
final Optional<Bytes> cms; final Bytes cms;
if (rlpInput.nextIsList() && rlpInput.nextSize() == 0) { final List<RLPInput> elements = rlpInput.readList(RLPInput::readAsRlp);
cms = Optional.empty(); if (elements.size() > QBFT_EXTRA_DATA_LIST_SIZE) {
final RLPInput cmsElement = elements.get(elements.size() - 1);
cms = cmsElement.readBytes();
} else { } else {
rlpInput.enterList(); cms = Bytes.EMPTY;
cms = Optional.of(rlpInput.readBytes());
rlpInput.leaveList();
} }
return new PkiQbftExtraData(bftExtraData, cms); return new PkiQbftExtraData(bftExtraData, cms);
@ -58,33 +59,30 @@ public class PkiQbftExtraDataCodec extends QbftExtraDataCodec {
@Override @Override
protected Bytes encode(final BftExtraData bftExtraData, final EncodingType encodingType) { protected Bytes encode(final BftExtraData bftExtraData, final EncodingType encodingType) {
if (!(bftExtraData instanceof PkiQbftExtraData)) { return encode(bftExtraData, encodingType, true);
throw new IllegalStateException(
"PkiQbftExtraDataCodec must be used only with PkiQbftExtraData");
} }
final PkiQbftExtraData extraData = (PkiQbftExtraData) bftExtraData;
final BytesValueRLPOutput encoder = new BytesValueRLPOutput(); private Bytes encode(
encoder.startList(); // start envelope list final BftExtraData bftExtraData, final EncodingType encodingType, final boolean includeCms) {
final Bytes encoded = super.encode(bftExtraData, encodingType);
if (!(bftExtraData instanceof PkiQbftExtraData) || !includeCms) {
return encoded;
}
final Bytes encodedQbftExtraData = super.encode(bftExtraData, encodingType); final BytesValueRLPOutput rlpOutput = new BytesValueRLPOutput();
encoder.writeRaw(encodedQbftExtraData); 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();
if (encodingType == EncodingType.ALL) { return rlpOutput.encoded();
if (extraData.getCms().isPresent()) {
Bytes cmsBytes = extraData.getCms().get();
encoder.startList();
encoder.writeBytes(cmsBytes);
encoder.endList();
} else {
encoder.writeEmptyList();
}
} else {
encoder.writeEmptyList();
} }
encoder.endList(); // end envelope list public Bytes encodeWithoutCms(final BftExtraData bftExtraData) {
return encode(bftExtraData, EncodingType.ALL, false);
return encoder.encoded();
} }
} }

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

@ -14,18 +14,27 @@
*/ */
package org.hyperledger.besu.consensus.qbft.validation; 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.ConsensusRoundIdentifier;
import org.hyperledger.besu.consensus.common.bft.payload.SignedData; 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.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;
import org.hyperledger.besu.ethereum.BlockValidator.BlockProcessingOutputs; import org.hyperledger.besu.ethereum.BlockValidator.BlockProcessingOutputs;
import org.hyperledger.besu.ethereum.ProtocolContext; import org.hyperledger.besu.ethereum.ProtocolContext;
import org.hyperledger.besu.ethereum.core.Address; import org.hyperledger.besu.ethereum.core.Address;
import org.hyperledger.besu.ethereum.core.Block; 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.ethereum.mainnet.HeaderValidationMode;
import org.hyperledger.besu.pki.cms.CmsValidator;
import java.util.Optional; import java.util.Optional;
import com.google.common.annotations.VisibleForTesting;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
@ -38,16 +47,41 @@ public class ProposalPayloadValidator {
private final ConsensusRoundIdentifier targetRound; private final ConsensusRoundIdentifier targetRound;
private final BlockValidator blockValidator; private final BlockValidator blockValidator;
private final ProtocolContext protocolContext; private final ProtocolContext protocolContext;
private final BftExtraDataCodec bftExtraDataCodec;
private final Optional<CmsValidator> cmsValidator;
public ProposalPayloadValidator( public ProposalPayloadValidator(
final Address expectedProposer, final Address expectedProposer,
final ConsensusRoundIdentifier targetRound, final ConsensusRoundIdentifier targetRound,
final BlockValidator blockValidator, 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.expectedProposer = expectedProposer;
this.targetRound = targetRound; this.targetRound = targetRound;
this.blockValidator = blockValidator; this.blockValidator = blockValidator;
this.protocolContext = protocolContext; this.protocolContext = protocolContext;
this.bftExtraDataCodec = bftExtraDataCodec;
this.cmsValidator = cmsValidator;
} }
public boolean validate(final SignedData<ProposalPayload> signedPayload) { public boolean validate(final SignedData<ProposalPayload> signedPayload) {
@ -74,6 +108,13 @@ public class ProposalPayloadValidator {
return false; return false;
} }
if (cmsValidator.isPresent()) {
return validateCms(
block,
protocolContext.getConsensusState(QbftContext.class).getBlockInterface(),
cmsValidator.get());
}
return true; return true;
} }
@ -89,4 +130,26 @@ public class ProposalPayloadValidator {
return true; 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 = final ProposalPayloadValidator payloadValidator =
new ProposalPayloadValidator( new ProposalPayloadValidator(
expectedProposer, roundIdentifier, blockValidator, protocolContext); expectedProposer, roundIdentifier, blockValidator, protocolContext, bftExtraDataCodec);
if (!payloadValidator.validate(msg.getSignedPayload())) { if (!payloadValidator.validate(msg.getSignedPayload())) {
LOG.info("{}: invalid proposal payload in proposal message", ERROR_PREFIX); LOG.info("{}: invalid proposal payload in proposal message", ERROR_PREFIX);

@ -47,7 +47,10 @@ public class QbftBlockHeaderValidationRulesetFactoryTest {
private ProtocolContext protocolContext(final Collection<Address> validators) { private ProtocolContext protocolContext(final Collection<Address> validators) {
return new ProtocolContext( return new ProtocolContext(
null, null, setupContextWithBftExtraDataEncoder(validators, new QbftExtraDataCodec())); null,
null,
setupContextWithBftExtraDataEncoder(
QbftContext.class, validators, new QbftExtraDataCodec()));
} }
@Test @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.BftExtraData;
import org.hyperledger.besu.consensus.common.bft.Vote; 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.ProtocolContext;
import org.hyperledger.besu.ethereum.core.Address; import org.hyperledger.besu.ethereum.core.Address;
import org.hyperledger.besu.ethereum.core.AddressHelpers; import org.hyperledger.besu.ethereum.core.AddressHelpers;
@ -43,7 +44,9 @@ public class QbftValidatorsValidationRuleTest {
new QbftValidatorsValidationRule(true); new QbftValidatorsValidationRule(true);
final ProtocolContext context = final ProtocolContext context =
new ProtocolContext( new ProtocolContext(
null, null, setupContextWithBftExtraData(Collections.emptyList(), bftExtraData)); null,
null,
setupContextWithBftExtraData(QbftContext.class, Collections.emptyList(), bftExtraData));
when(bftExtraData.getValidators()).thenReturn(Collections.emptyList()); when(bftExtraData.getValidators()).thenReturn(Collections.emptyList());
when(bftExtraData.getVote()).thenReturn(Optional.empty()); when(bftExtraData.getVote()).thenReturn(Optional.empty());
assertThat(qbftValidatorsValidationRule.validate(blockHeader, null, context)).isTrue(); assertThat(qbftValidatorsValidationRule.validate(blockHeader, null, context)).isTrue();
@ -58,7 +61,8 @@ public class QbftValidatorsValidationRuleTest {
AddressHelpers.ofValue(1), AddressHelpers.ofValue(2), AddressHelpers.ofValue(3)); AddressHelpers.ofValue(1), AddressHelpers.ofValue(2), AddressHelpers.ofValue(3));
final ProtocolContext context = final ProtocolContext context =
new ProtocolContext(null, null, setupContextWithBftExtraData(validators, bftExtraData)); new ProtocolContext(
null, null, setupContextWithBftExtraData(QbftContext.class, validators, bftExtraData));
when(bftExtraData.getValidators()).thenReturn(validators); when(bftExtraData.getValidators()).thenReturn(validators);
assertThat(qbftValidatorsValidationRule.validate(blockHeader, null, context)).isTrue(); assertThat(qbftValidatorsValidationRule.validate(blockHeader, null, context)).isTrue();
} }
@ -72,7 +76,8 @@ public class QbftValidatorsValidationRuleTest {
AddressHelpers.ofValue(1), AddressHelpers.ofValue(2), AddressHelpers.ofValue(3)); AddressHelpers.ofValue(1), AddressHelpers.ofValue(2), AddressHelpers.ofValue(3));
final ProtocolContext context = final ProtocolContext context =
new ProtocolContext(null, null, setupContextWithBftExtraData(validators, bftExtraData)); new ProtocolContext(
null, null, setupContextWithBftExtraData(QbftContext.class, validators, bftExtraData));
when(bftExtraData.getValidators()).thenReturn(validators); when(bftExtraData.getValidators()).thenReturn(validators);
assertThat(qbftValidatorsValidationRule.validate(blockHeader, null, context)).isFalse(); assertThat(qbftValidatorsValidationRule.validate(blockHeader, null, context)).isFalse();
} }
@ -83,7 +88,9 @@ public class QbftValidatorsValidationRuleTest {
new QbftValidatorsValidationRule(true); new QbftValidatorsValidationRule(true);
final ProtocolContext context = final ProtocolContext context =
new ProtocolContext( new ProtocolContext(
null, null, setupContextWithBftExtraData(Collections.emptyList(), bftExtraData)); null,
null,
setupContextWithBftExtraData(QbftContext.class, Collections.emptyList(), bftExtraData));
when(bftExtraData.getValidators()).thenReturn(Collections.emptyList()); when(bftExtraData.getValidators()).thenReturn(Collections.emptyList());
when(bftExtraData.getVote()).thenReturn(Optional.of(mock(Vote.class))); when(bftExtraData.getVote()).thenReturn(Optional.of(mock(Vote.class)));
assertThat(qbftValidatorsValidationRule.validate(blockHeader, null, context)).isFalse(); 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 * 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 * 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.assertj.core.api.Assertions.assertThat;
import static org.hyperledger.besu.consensus.qbft.QbftExtraDataCodecTestUtils.createNonEmptyVanityData; 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.consensus.common.bft.Vote;
import org.hyperledger.besu.crypto.SECPSignature; import org.hyperledger.besu.crypto.SECPSignature;
import org.hyperledger.besu.crypto.SignatureAlgorithm; import org.hyperledger.besu.crypto.SignatureAlgorithm;
@ -41,25 +42,34 @@ public class PkiQbftExtraDataCodecTest {
private static final Supplier<SignatureAlgorithm> SIGNATURE_ALGORITHM = private static final Supplier<SignatureAlgorithm> SIGNATURE_ALGORITHM =
Suppliers.memoize(SignatureAlgorithmFactory::getInstance); 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 // Arbitrary bytes representing a non-empty CMS
private final Bytes cms = Bytes.fromHexString("0x01"); private final Bytes cms = Bytes.fromHexString("0x01");
// Raw hex-encoded extra data with arbitrary CMS data (0x01) private final String RAW_EXCLUDE_COMMIT_SEALS_AND_ROUND_NUMBER_ENCODED_STRING =
private final String RAW_HEX_ENCODING_STRING = "0xf867a00102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20ea940000000000000000"
"0xf8f4f8f0a00102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20ea94000000000000" + "000000000000000000000001940000000000000000000000000000000000000002d7940000000000000000"
+ "0000000000000000000000000001940000000000000000000000000000000000000002d794000000000000" + "00000000000000000000000181ff80c001";
+ "000000000000000000000000000181ff83fedcbaf886b84100000000000000000000000000000000000000"
+ "00000000000000000000000001000000000000000000000000000000000000000000000000000000000000" private final String RAW_EXCLUDE_COMMIT_SEALS_ENCODED_STRING =
+ "000a00b841000000000000000000000000000000000000000000000000000000000000000a000000000000" "0xf86aa00102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20ea940000000000000000"
+ "000000000000000000000000000000000000000000000000000100c101"; + "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(); private final PkiQbftExtraDataCodec bftExtraDataCodec = new PkiQbftExtraDataCodec();
@ -79,8 +89,6 @@ public class PkiQbftExtraDataCodecTest {
final Bytes vanity_data = Bytes.wrap(vanity_bytes); final Bytes vanity_data = Bytes.wrap(vanity_bytes);
final BytesValueRLPOutput encoder = new BytesValueRLPOutput(); final BytesValueRLPOutput encoder = new BytesValueRLPOutput();
encoder.startList(); // start envelope list
encoder.startList(); // start extra data list encoder.startList(); // start extra data list
// vanity data // vanity data
encoder.writeBytes(vanity_data); encoder.writeBytes(vanity_data);
@ -95,13 +103,54 @@ public class PkiQbftExtraDataCodecTest {
encoder.writeIntScalar(round); encoder.writeIntScalar(round);
// committer seals // committer seals
encoder.writeList(committerSeals, (committer, rlp) -> rlp.writeBytes(committer.encodedBytes())); encoder.writeList(committerSeals, (committer, rlp) -> rlp.writeBytes(committer.encodedBytes()));
// cms
encoder.writeBytes(cms);
encoder.endList(); // end extra data list encoder.endList(); // end extra data list
encoder.startList(); // start cms list final Bytes bufferToInject = encoder.encoded();
encoder.writeBytes(cms);
encoder.endList(); // end cms 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));
encoder.endList(); // end envelope list // 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(); final Bytes bufferToInject = encoder.encoded();
@ -112,30 +161,70 @@ public class PkiQbftExtraDataCodecTest {
assertThat(extraData.getRound()).isEqualTo(round); assertThat(extraData.getRound()).isEqualTo(round);
assertThat(extraData.getSeals()).isEqualTo(committerSeals); assertThat(extraData.getSeals()).isEqualTo(committerSeals);
assertThat(extraData.getValidators()).isEqualTo(validators); 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 @Test
public void encodingExtraDataWithEmptyCmsMatchesKnownRawHexString() { public void encodingForBlockchainShouldIncludeCms() {
final Bytes expectedRawDecoding = Bytes.fromHexString(EMPTY_CMS_RAW_HEX_ENCODING_STRING); final Bytes expectedRawDecoding =
final Bytes encoded = bftExtraDataCodec.encode(getDecodedExtraDataWithEmptyCms()); Bytes.fromHexString(RAW_EXCLUDE_COMMIT_SEALS_AND_ROUND_NUMBER_ENCODED_STRING);
final Bytes encoded =
bftExtraDataCodec.encodeWithoutCommitSealsAndRoundNumber(getDecodedExtraData(cms));
assertThat(encoded).isEqualTo(expectedRawDecoding); assertThat(encoded).isEqualTo(expectedRawDecoding);
} }
@Test @Test
public void encodingMatchesKnownRawHexString() { public void encodingWithoutCommitSealsShouldIncludeCms() {
final Bytes expectedRawDecoding = Bytes.fromHexString(RAW_HEX_ENCODING_STRING); final Bytes expectedRawDecoding = Bytes.fromHexString(RAW_EXCLUDE_COMMIT_SEALS_ENCODED_STRING);
final Bytes encoded = bftExtraDataCodec.encode(getDecodedExtraData(Optional.of(cms))); final Bytes encoded = bftExtraDataCodec.encodeWithoutCommitSeals(getDecodedExtraData(cms));
assertThat(encoded).isEqualTo(expectedRawDecoding); assertThat(encoded).isEqualTo(expectedRawDecoding);
} }
private static PkiQbftExtraData getDecodedExtraDataWithEmptyCms() { @Test
return getDecodedExtraData(Optional.empty()); 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 encodingForCreatingCmsProposal() {
final Bytes expectedRawDecoding = Bytes.fromHexString(RAW_QBFT_EXTRA_DATA);
final Bytes encoded = bftExtraDataCodec.encodeWithoutCms(getDecodedExtraData(cms));
assertThat(encoded).isEqualTo(expectedRawDecoding);
}
/*
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 = final List<Address> validators =
Arrays.asList(Address.fromHexString("1"), Address.fromHexString("2")); Arrays.asList(Address.fromHexString("1"), Address.fromHexString("2"));
final Optional<Vote> vote = Optional.of(Vote.authVote(Address.fromHexString("1"))); 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.events.RoundExpiry;
import org.hyperledger.besu.consensus.common.bft.network.ValidatorMulticaster; import org.hyperledger.besu.consensus.common.bft.network.ValidatorMulticaster;
import org.hyperledger.besu.consensus.common.bft.statemachine.BftFinalState; 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.QbftExtraDataCodec;
import org.hyperledger.besu.consensus.qbft.messagedata.RoundChangeMessageData; import org.hyperledger.besu.consensus.qbft.messagedata.RoundChangeMessageData;
import org.hyperledger.besu.consensus.qbft.messagewrappers.Commit; import org.hyperledger.besu.consensus.qbft.messagewrappers.Commit;
@ -148,7 +149,10 @@ public class QbftBlockHeightManagerTest {
protocolContext = protocolContext =
new 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; // Ensure the created IbftRound has the valid ConsensusRoundIdentifier;
when(roundFactory.createNewRound(any(), anyInt())) 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.RoundTimer;
import org.hyperledger.besu.consensus.common.bft.blockcreation.BftBlockCreator; import org.hyperledger.besu.consensus.common.bft.blockcreation.BftBlockCreator;
import org.hyperledger.besu.consensus.common.bft.payload.SignedData; 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.QbftExtraDataCodec;
import org.hyperledger.besu.consensus.qbft.messagewrappers.RoundChange; import org.hyperledger.besu.consensus.qbft.messagewrappers.RoundChange;
import org.hyperledger.besu.consensus.qbft.network.QbftMessageTransmitter; import org.hyperledger.besu.consensus.qbft.network.QbftMessageTransmitter;
@ -109,7 +110,8 @@ public class QbftRoundTest {
new ProtocolContext( new ProtocolContext(
blockChain, blockChain,
worldStateArchive, worldStateArchive,
setupContextWithBftExtraDataEncoder(emptyList(), new QbftExtraDataCodec())); setupContextWithBftExtraDataEncoder(
QbftContext.class, emptyList(), new QbftExtraDataCodec()));
when(messageValidator.validateProposal(any())).thenReturn(true); when(messageValidator.validateProposal(any())).thenReturn(true);
when(messageValidator.validatePrepare(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.verifyNoMoreInteractions;
import static org.mockito.Mockito.when; 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.ConsensusRoundHelpers;
import org.hyperledger.besu.consensus.common.bft.ConsensusRoundIdentifier; import org.hyperledger.besu.consensus.common.bft.ConsensusRoundIdentifier;
import org.hyperledger.besu.consensus.common.bft.ProposedBlockHelpers; 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.QbftExtraDataCodec;
import org.hyperledger.besu.consensus.qbft.messagewrappers.Proposal; import org.hyperledger.besu.consensus.qbft.messagewrappers.Proposal;
import org.hyperledger.besu.consensus.qbft.payload.MessageFactory; 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.NodeKey;
import org.hyperledger.besu.crypto.NodeKeyUtils; import org.hyperledger.besu.crypto.NodeKeyUtils;
import org.hyperledger.besu.ethereum.BlockValidator; 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.chain.MutableBlockchain;
import org.hyperledger.besu.ethereum.core.Address; import org.hyperledger.besu.ethereum.core.Address;
import org.hyperledger.besu.ethereum.core.Block; 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.core.Util;
import org.hyperledger.besu.ethereum.mainnet.HeaderValidationMode; import org.hyperledger.besu.ethereum.mainnet.HeaderValidationMode;
import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive; 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 java.util.Optional;
import org.apache.tuweni.bytes.Bytes;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
@ -53,6 +66,7 @@ public class ProposalPayloadValidatorTest {
@Mock private BlockValidator blockValidator; @Mock private BlockValidator blockValidator;
@Mock private MutableBlockchain blockChain; @Mock private MutableBlockchain blockChain;
@Mock private WorldStateArchive worldStateArchive; @Mock private WorldStateArchive worldStateArchive;
@Mock private CmsValidator cmsValidator;
private ProtocolContext protocolContext; private ProtocolContext protocolContext;
private static final int CHAIN_HEIGHT = 3; private static final int CHAIN_HEIGHT = 3;
@ -64,7 +78,7 @@ public class ProposalPayloadValidatorTest {
private final MessageFactory messageFactory = new MessageFactory(nodeKey); private final MessageFactory messageFactory = new MessageFactory(nodeKey);
final ConsensusRoundIdentifier roundIdentifier = final ConsensusRoundIdentifier roundIdentifier =
ConsensusRoundHelpers.createFrom(targetRound, 1, 0); ConsensusRoundHelpers.createFrom(targetRound, 1, 0);
final QbftExtraDataCodec bftExtraDataEncoder = new QbftExtraDataCodec(); final QbftExtraDataCodec bftExtraDataCodec = new QbftExtraDataCodec();
@Before @Before
public void setup() { public void setup() {
@ -72,16 +86,16 @@ public class ProposalPayloadValidatorTest {
new ProtocolContext( new ProtocolContext(
blockChain, blockChain,
worldStateArchive, worldStateArchive,
setupContextWithBftExtraDataEncoder(emptyList(), bftExtraDataEncoder)); setupContextWithBftExtraDataEncoder(QbftContext.class, emptyList(), bftExtraDataCodec));
} }
@Test @Test
public void validationPassesWhenProposerAndRoundMatchAndBlockIsValid() { public void validationPassesWhenProposerAndRoundMatchAndBlockIsValid() {
final ProposalPayloadValidator payloadValidator = final ProposalPayloadValidator payloadValidator =
new ProposalPayloadValidator( new ProposalPayloadValidator(
expectedProposer, roundIdentifier, blockValidator, protocolContext); expectedProposer, roundIdentifier, blockValidator, protocolContext, bftExtraDataCodec);
final Block block = final Block block =
ProposedBlockHelpers.createProposalBlock(emptyList(), roundIdentifier, bftExtraDataEncoder); ProposedBlockHelpers.createProposalBlock(emptyList(), roundIdentifier, bftExtraDataCodec);
final Proposal proposal = final Proposal proposal =
messageFactory.createProposal(roundIdentifier, block, emptyList(), emptyList()); messageFactory.createProposal(roundIdentifier, block, emptyList(), emptyList());
@ -99,13 +113,13 @@ public class ProposalPayloadValidatorTest {
public void validationPassesWhenBlockRoundDoesNotMatchProposalRound() { public void validationPassesWhenBlockRoundDoesNotMatchProposalRound() {
final ProposalPayloadValidator payloadValidator = final ProposalPayloadValidator payloadValidator =
new ProposalPayloadValidator( new ProposalPayloadValidator(
expectedProposer, roundIdentifier, blockValidator, protocolContext); expectedProposer, roundIdentifier, blockValidator, protocolContext, bftExtraDataCodec);
final Block block = final Block block =
ProposedBlockHelpers.createProposalBlock( ProposedBlockHelpers.createProposalBlock(
emptyList(), emptyList(),
ConsensusRoundHelpers.createFrom(roundIdentifier, 0, +1), ConsensusRoundHelpers.createFrom(roundIdentifier, 0, +1),
bftExtraDataEncoder); bftExtraDataCodec);
final Proposal proposal = final Proposal proposal =
messageFactory.createProposal(roundIdentifier, block, emptyList(), emptyList()); messageFactory.createProposal(roundIdentifier, block, emptyList(), emptyList());
@ -126,9 +140,9 @@ public class ProposalPayloadValidatorTest {
final ProposalPayloadValidator payloadValidator = final ProposalPayloadValidator payloadValidator =
new ProposalPayloadValidator( new ProposalPayloadValidator(
expectedProposer, roundIdentifier, blockValidator, protocolContext); expectedProposer, roundIdentifier, blockValidator, protocolContext, bftExtraDataCodec);
final Block block = final Block block =
ProposedBlockHelpers.createProposalBlock(emptyList(), roundIdentifier, bftExtraDataEncoder); ProposedBlockHelpers.createProposalBlock(emptyList(), roundIdentifier, bftExtraDataCodec);
final Proposal proposal = final Proposal proposal =
messageFactory.createProposal(roundIdentifier, block, emptyList(), emptyList()); messageFactory.createProposal(roundIdentifier, block, emptyList(), emptyList());
@ -146,7 +160,11 @@ public class ProposalPayloadValidatorTest {
public void validationFailsWhenExpectedProposerDoesNotMatchPayloadsAuthor() { public void validationFailsWhenExpectedProposerDoesNotMatchPayloadsAuthor() {
final ProposalPayloadValidator payloadValidator = final ProposalPayloadValidator payloadValidator =
new ProposalPayloadValidator( new ProposalPayloadValidator(
Address.fromHexString("0x1"), roundIdentifier, blockValidator, protocolContext); Address.fromHexString("0x1"),
roundIdentifier,
blockValidator,
protocolContext,
bftExtraDataCodec);
final Block block = ProposedBlockHelpers.createProposalBlock(emptyList(), roundIdentifier); final Block block = ProposedBlockHelpers.createProposalBlock(emptyList(), roundIdentifier);
final Proposal proposal = final Proposal proposal =
messageFactory.createProposal(roundIdentifier, block, emptyList(), emptyList()); messageFactory.createProposal(roundIdentifier, block, emptyList(), emptyList());
@ -159,7 +177,7 @@ public class ProposalPayloadValidatorTest {
public void validationFailsWhenMessageMismatchesExpectedRound() { public void validationFailsWhenMessageMismatchesExpectedRound() {
final ProposalPayloadValidator payloadValidator = final ProposalPayloadValidator payloadValidator =
new ProposalPayloadValidator( new ProposalPayloadValidator(
expectedProposer, roundIdentifier, blockValidator, protocolContext); expectedProposer, roundIdentifier, blockValidator, protocolContext, bftExtraDataCodec);
final Block block = ProposedBlockHelpers.createProposalBlock(emptyList(), roundIdentifier); final Block block = ProposedBlockHelpers.createProposalBlock(emptyList(), roundIdentifier);
final Proposal proposal = final Proposal proposal =
@ -177,7 +195,7 @@ public class ProposalPayloadValidatorTest {
public void validationFailsWhenMessageMismatchesExpectedHeight() { public void validationFailsWhenMessageMismatchesExpectedHeight() {
final ProposalPayloadValidator payloadValidator = final ProposalPayloadValidator payloadValidator =
new ProposalPayloadValidator( new ProposalPayloadValidator(
expectedProposer, roundIdentifier, blockValidator, protocolContext); expectedProposer, roundIdentifier, blockValidator, protocolContext, bftExtraDataCodec);
final Block block = ProposedBlockHelpers.createProposalBlock(emptyList(), roundIdentifier); final Block block = ProposedBlockHelpers.createProposalBlock(emptyList(), roundIdentifier);
final Proposal proposal = final Proposal proposal =
@ -195,12 +213,12 @@ public class ProposalPayloadValidatorTest {
public void validationFailsForBlockWithIncorrectHeight() { public void validationFailsForBlockWithIncorrectHeight() {
final ProposalPayloadValidator payloadValidator = final ProposalPayloadValidator payloadValidator =
new ProposalPayloadValidator( new ProposalPayloadValidator(
expectedProposer, roundIdentifier, blockValidator, protocolContext); expectedProposer, roundIdentifier, blockValidator, protocolContext, bftExtraDataCodec);
final Block block = final Block block =
ProposedBlockHelpers.createProposalBlock( ProposedBlockHelpers.createProposalBlock(
emptyList(), emptyList(),
ConsensusRoundHelpers.createFrom(roundIdentifier, +1, 0), ConsensusRoundHelpers.createFrom(roundIdentifier, +1, 0),
bftExtraDataEncoder); bftExtraDataCodec);
final Proposal proposal = final Proposal proposal =
messageFactory.createProposal(roundIdentifier, block, emptyList(), emptyList()); messageFactory.createProposal(roundIdentifier, block, emptyList(), emptyList());
@ -213,4 +231,102 @@ public class ProposalPayloadValidatorTest {
assertThat(payloadValidator.validate(proposal.getSignedPayload())).isFalse(); 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.ConsensusRoundIdentifier;
import org.hyperledger.besu.consensus.common.bft.ProposedBlockHelpers; import org.hyperledger.besu.consensus.common.bft.ProposedBlockHelpers;
import org.hyperledger.besu.consensus.common.bft.payload.SignedData; 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.QbftExtraDataCodec;
import org.hyperledger.besu.consensus.qbft.messagewrappers.Prepare; import org.hyperledger.besu.consensus.qbft.messagewrappers.Prepare;
import org.hyperledger.besu.consensus.qbft.messagewrappers.Proposal; import org.hyperledger.besu.consensus.qbft.messagewrappers.Proposal;
@ -95,7 +96,8 @@ public class ProposalValidatorTest {
new ProtocolContext( new ProtocolContext(
blockChain, blockChain,
worldStateArchive, worldStateArchive,
setupContextWithBftExtraDataEncoder(emptyList(), bftExtraDataEncoder)); setupContextWithBftExtraDataEncoder(
QbftContext.class, emptyList(), bftExtraDataEncoder));
// typically tests require the blockValidation to be successful // typically tests require the blockValidation to be successful
when(blockValidator.validateAndProcessBlock( 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.ConsensusRoundIdentifier;
import org.hyperledger.besu.consensus.common.bft.ProposedBlockHelpers; import org.hyperledger.besu.consensus.common.bft.ProposedBlockHelpers;
import org.hyperledger.besu.consensus.common.bft.payload.SignedData; 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.QbftExtraDataCodec;
import org.hyperledger.besu.consensus.qbft.messagewrappers.RoundChange; import org.hyperledger.besu.consensus.qbft.messagewrappers.RoundChange;
import org.hyperledger.besu.consensus.qbft.payload.PreparedRoundMetadata; import org.hyperledger.besu.consensus.qbft.payload.PreparedRoundMetadata;
@ -79,7 +80,8 @@ public class RoundChangeMessageValidatorTest {
new ProtocolContext( new ProtocolContext(
blockChain, blockChain,
worldStateArchive, worldStateArchive,
setupContextWithBftExtraDataEncoder(emptyList(), bftExtraDataEncoder)); setupContextWithBftExtraDataEncoder(
QbftContext.class, emptyList(), bftExtraDataEncoder));
} }
@Test @Test

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

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

Loading…
Cancel
Save