Skip to content

Commit

Permalink
QBFT PKI-backed Block Creation (#2677)
Browse files Browse the repository at this point in the history
Signed-off-by: Lucas Saldanha <lucascrsaldanha@gmail.com>
Co-authored-by: Jason Frame <jasonwframe@gmail.com>
  • Loading branch information
lucassaldanha and jframe authored Sep 7, 2021
1 parent ed59386 commit 0215444
Show file tree
Hide file tree
Showing 31 changed files with 918 additions and 133 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,12 @@ public static List<Object[]> getFactories() {
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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,12 @@
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;
Expand Down Expand Up @@ -299,15 +299,8 @@ protected BftContext createConsensusContext(
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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,14 @@ public static BftContext setupContextWithValidators(final Collection<Address> va

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 =
Expand All @@ -54,7 +61,14 @@ public static BftContext setupContextWithBftExtraData(

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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -31,6 +34,22 @@

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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
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;
Expand Down Expand Up @@ -400,7 +401,7 @@ private static ControllerAndState createControllerAndFinalState(
new ProtocolContext(
blockChain,
worldStateArchive,
new BftContext(validatorProvider, epochManager, blockInterface));
new QbftContext(validatorProvider, epochManager, blockInterface, Optional.empty()));

final GasPricePendingTransactionsSorter pendingTransactions =
new GasPricePendingTransactionsSorter(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
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;
Expand Down Expand Up @@ -115,7 +116,8 @@ public void setup() {
new ProtocolContext(
blockChain,
worldStateArchive,
setupContextWithBftExtraDataEncoder(emptyList(), qbftExtraDataEncoder));
setupContextWithBftExtraDataEncoder(
QbftContext.class, emptyList(), qbftExtraDataEncoder));
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ public BftExtraData decodeRaw(final Bytes input) {
final List<SECPSignature> seals =
rlpInput.readList(
rlp -> SignatureAlgorithmFactory.getInstance().decodeSignature(rlp.readBytes()));
rlpInput.leaveList();
rlpInput.leaveListLenient();

return new BftExtraData(vanityData, seals, vote, round, validators);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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));
}
}
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -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());
}
}
Loading

0 comments on commit 0215444

Please sign in to comment.