QBFT: Fix validation of proposal based on older round's prepared block (#7875)

* QBFT: Fix validation of proposal based on older round's prepared block

* Add round numbers to new blocks and prepared based proposals

* QBFT fix around proposal validation by proposer and testcase updates

- Check the result of proposer's self proposal validation before sending prepare message
- Update roundchange testcases to include round numbers in blocks

* Update changelog with qbft fix

Signed-off-by: Bhanu Pulluri <bhanu.pulluri@kaleido.io>

---------

Signed-off-by: Bhanu Pulluri <bhanu.pulluri@kaleido.io>
Co-authored-by: Bhanu Pulluri <bhanu.pulluri@kaleido.io>
Co-authored-by: Sally MacFarlane <macfarla.github@gmail.com>
pull/7927/head
Bhanu Pulluri 1 week ago committed by GitHub
parent 674a7bae58
commit f64c147c4a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 1
      CHANGELOG.md
  2. 36
      consensus/qbft/src/integration-test/java/org/hyperledger/besu/consensus/qbft/support/TestContext.java
  3. 16
      consensus/qbft/src/integration-test/java/org/hyperledger/besu/consensus/qbft/test/RoundChangeTest.java
  4. 16
      consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/statemachine/QbftRound.java
  5. 3
      consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/statemachine/QbftRoundFactory.java
  6. 13
      consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/validation/ProposalValidator.java

@ -30,6 +30,7 @@
### Bug fixes ### Bug fixes
- Fix registering new metric categories from plugins [#7825](https://github.com/hyperledger/besu/pull/7825) - Fix registering new metric categories from plugins [#7825](https://github.com/hyperledger/besu/pull/7825)
- Fix CVE-2024-47535 [7878](https://github.com/hyperledger/besu/pull/7878) - Fix CVE-2024-47535 [7878](https://github.com/hyperledger/besu/pull/7878)
- Fix QBFT prepared block based proposal validation [#7875](https://github.com/hyperledger/besu/pull/7875)
## 24.10.0 ## 24.10.0

@ -97,13 +97,37 @@ public class TestContext {
} }
public Block createBlockForProposalFromChainHead(final long timestamp) { public Block createBlockForProposalFromChainHead(final long timestamp) {
return createBlockForProposalFromChainHead(timestamp, finalState.getLocalAddress()); return createBlockForProposalFromChainHead(timestamp, finalState.getLocalAddress(), 0);
}
public Block createBlockForProposalFromChainHead(final long timestamp, final int roundNumber) {
return createBlockForProposalFromChainHead(
timestamp, finalState.getLocalAddress(), roundNumber);
}
public Block createBlockForProposalFromChainHead(final long timestamp, final Address proposer) {
// this implies that EVERY block will have this node as the proposer :/
return createBlockForProposal(blockchain.getChainHeadHeader(), timestamp, proposer, 0);
}
public Block createBlockForProposalFromChainHead(
final long timestamp, final Address proposer, final int roundNumber) {
// this implies that EVERY block will have this node as the proposer :/
return createBlockForProposal(
blockchain.getChainHeadHeader(), timestamp, proposer, roundNumber);
} }
public Block createBlockForProposal( public Block createBlockForProposal(
final BlockHeader parent, final long timestamp, final Address proposer) { final BlockHeader parent,
final long timestamp,
final Address proposer,
final int roundNumber) {
final Block block = final Block block =
finalState.getBlockCreatorFactory().create(0).createBlock(timestamp, parent).getBlock(); finalState
.getBlockCreatorFactory()
.create(roundNumber)
.createBlock(timestamp, parent)
.getBlock();
final BlockHeaderBuilder headerBuilder = BlockHeaderBuilder.fromHeader(block.getHeader()); final BlockHeaderBuilder headerBuilder = BlockHeaderBuilder.fromHeader(block.getHeader());
headerBuilder headerBuilder
@ -114,9 +138,9 @@ public class TestContext {
return new Block(newHeader, block.getBody()); return new Block(newHeader, block.getBody());
} }
public Block createBlockForProposalFromChainHead(final long timestamp, final Address proposer) { public Block createBlockForProposal(
// this implies that EVERY block will have this node as the proposer :/ final BlockHeader parent, final long timestamp, final Address proposer) {
return createBlockForProposal(blockchain.getChainHeadHeader(), timestamp, proposer); return createBlockForProposal(parent, timestamp, proposer, 0);
} }
public RoundSpecificPeers roundSpecificPeers(final ConsensusRoundIdentifier roundId) { public RoundSpecificPeers roundSpecificPeers(final ConsensusRoundIdentifier roundId) {

@ -144,7 +144,8 @@ public class RoundChangeTest {
public void whenSufficientRoundChangeMessagesAreReceivedForNewRoundLocalNodeCreatesProposalMsg() { public void whenSufficientRoundChangeMessagesAreReceivedForNewRoundLocalNodeCreatesProposalMsg() {
// Note: Round-4 is the next round for which the local node is Proposer // Note: Round-4 is the next round for which the local node is Proposer
final ConsensusRoundIdentifier targetRound = new ConsensusRoundIdentifier(1, 4); final ConsensusRoundIdentifier targetRound = new ConsensusRoundIdentifier(1, 4);
final Block locallyProposedBlock = context.createBlockForProposalFromChainHead(blockTimeStamp); final Block locallyProposedBlock =
context.createBlockForProposalFromChainHead(blockTimeStamp, 4);
final RoundChange rc1 = peers.getNonProposing(0).injectRoundChange(targetRound, empty()); final RoundChange rc1 = peers.getNonProposing(0).injectRoundChange(targetRound, empty());
final RoundChange rc2 = peers.getNonProposing(1).injectRoundChange(targetRound, empty()); final RoundChange rc2 = peers.getNonProposing(1).injectRoundChange(targetRound, empty());
@ -177,14 +178,14 @@ public class RoundChangeTest {
context, context,
new ConsensusRoundIdentifier(1, 1), new ConsensusRoundIdentifier(1, 1),
context.createBlockForProposalFromChainHead( context.createBlockForProposalFromChainHead(
ARBITRARY_BLOCKTIME / 2, peers.getProposer().getNodeAddress())); ARBITRARY_BLOCKTIME / 2, peers.getProposer().getNodeAddress(), 1));
final PreparedCertificate bestPrepCert = final PreparedCertificate bestPrepCert =
createValidPreparedCertificate( createValidPreparedCertificate(
context, context,
new ConsensusRoundIdentifier(1, 2), new ConsensusRoundIdentifier(1, 2),
context.createBlockForProposalFromChainHead( context.createBlockForProposalFromChainHead(
ARBITRARY_BLOCKTIME, peers.getProposer().getNodeAddress())); ARBITRARY_BLOCKTIME, peers.getProposer().getNodeAddress(), 2));
final ConsensusRoundIdentifier targetRound = new ConsensusRoundIdentifier(1, 4); final ConsensusRoundIdentifier targetRound = new ConsensusRoundIdentifier(1, 4);
@ -206,7 +207,7 @@ public class RoundChangeTest {
// round number. // round number.
final Block expectedBlockToPropose = final Block expectedBlockToPropose =
context.createBlockForProposalFromChainHead( context.createBlockForProposalFromChainHead(
ARBITRARY_BLOCKTIME, peers.getProposer().getNodeAddress()); ARBITRARY_BLOCKTIME, peers.getProposer().getNodeAddress(), 4);
final Proposal expectedProposal = final Proposal expectedProposal =
localNodeMessageFactory.createProposal( localNodeMessageFactory.createProposal(
@ -234,7 +235,8 @@ public class RoundChangeTest {
final ConsensusRoundIdentifier priorRound = new ConsensusRoundIdentifier(1, 4); final ConsensusRoundIdentifier priorRound = new ConsensusRoundIdentifier(1, 4);
peers.roundChange(priorRound); peers.roundChange(priorRound);
final Block locallyProposedBlock = context.createBlockForProposalFromChainHead(blockTimeStamp); final Block locallyProposedBlock =
context.createBlockForProposalFromChainHead(blockTimeStamp, 9);
final Proposal expectedProposal = final Proposal expectedProposal =
localNodeMessageFactory.createProposal( localNodeMessageFactory.createProposal(
@ -271,7 +273,7 @@ public class RoundChangeTest {
context, context,
new ConsensusRoundIdentifier(1, 2), new ConsensusRoundIdentifier(1, 2),
context.createBlockForProposalFromChainHead( context.createBlockForProposalFromChainHead(
ARBITRARY_BLOCKTIME, peers.getProposer().getNodeAddress())); ARBITRARY_BLOCKTIME, peers.getProposer().getNodeAddress(), 2));
final List<SignedData<RoundChangePayload>> roundChangeMessages = Lists.newArrayList(); final List<SignedData<RoundChangePayload>> roundChangeMessages = Lists.newArrayList();
// Create a roundChange containing a PreparedCertificate // Create a roundChange containing a PreparedCertificate
@ -288,7 +290,7 @@ public class RoundChangeTest {
final Block expectedBlockToPropose = final Block expectedBlockToPropose =
context.createBlockForProposalFromChainHead( context.createBlockForProposalFromChainHead(
ARBITRARY_BLOCKTIME, peers.getProposer().getNodeAddress()); ARBITRARY_BLOCKTIME, peers.getProposer().getNodeAddress(), 4);
final Proposal expectedProposal = final Proposal expectedProposal =
localNodeMessageFactory.createProposal( localNodeMessageFactory.createProposal(

@ -160,9 +160,18 @@ public class QbftRound {
} else { } else {
LOG.debug( LOG.debug(
"Sending proposal from PreparedCertificate. round={}", roundState.getRoundIdentifier()); "Sending proposal from PreparedCertificate. round={}", roundState.getRoundIdentifier());
blockToPublish = bestPreparedCertificate.get().getBlock(); Block preparedBlock = bestPreparedCertificate.get().getBlock();
final BftBlockInterface bftBlockInterface =
protocolContext.getConsensusContext(BftContext.class).getBlockInterface();
blockToPublish =
bftBlockInterface.replaceRoundInBlock(
preparedBlock,
roundState.getRoundIdentifier().getRoundNumber(),
BftBlockHeaderFunctions.forCommittedSeal(bftExtraDataCodec));
} }
LOG.debug(" proposal - new/prepared block hash : {}", blockToPublish.getHash());
updateStateWithProposalAndTransmit( updateStateWithProposalAndTransmit(
blockToPublish, blockToPublish,
roundChangeArtifacts.getRoundChanges(), roundChangeArtifacts.getRoundChanges(),
@ -202,8 +211,9 @@ public class QbftRound {
proposal.getSignedPayload().getPayload().getProposedBlock(), proposal.getSignedPayload().getPayload().getProposedBlock(),
roundChanges, roundChanges,
prepares); prepares);
updateStateWithProposedBlock(proposal); if (updateStateWithProposedBlock(proposal)) {
sendPrepare(block); sendPrepare(block);
}
} }
/** /**

@ -99,7 +99,8 @@ public class QbftRoundFactory {
*/ */
public QbftRound createNewRoundWithState( public QbftRound createNewRoundWithState(
final BlockHeader parentHeader, final RoundState roundState) { final BlockHeader parentHeader, final RoundState roundState) {
final BlockCreator blockCreator = blockCreatorFactory.create(0); final BlockCreator blockCreator =
blockCreatorFactory.create(roundState.getRoundIdentifier().getRoundNumber());
// TODO(tmm): Why is this created everytime?! // TODO(tmm): Why is this created everytime?!
final QbftMessageTransmitter messageTransmitter = final QbftMessageTransmitter messageTransmitter =

@ -137,6 +137,13 @@ public class ProposalValidator {
final PreparedRoundMetadata metadata = final PreparedRoundMetadata metadata =
roundChangeWithLatestPreparedRound.get().getPayload().getPreparedRoundMetadata().get(); roundChangeWithLatestPreparedRound.get().getPayload().getPreparedRoundMetadata().get();
LOG.debug(
"Prepared Metadata blockhash : {}, proposal blockhash: {}, prepared round in message: {}, proposal round in message: {}",
metadata.getPreparedBlockHash(),
proposal.getBlock().getHash(),
metadata.getPreparedRound(),
proposal.getRoundIdentifier().getRoundNumber());
// The Hash in the roundchange/proposals is NOT the same as the value in the // The Hash in the roundchange/proposals is NOT the same as the value in the
// prepares/roundchanges // prepares/roundchanges
// as said payloads reference the block with an OLD round number in it - therefore, need // as said payloads reference the block with an OLD round number in it - therefore, need
@ -155,8 +162,10 @@ public class ProposalValidator {
if (!metadata.getPreparedBlockHash().equals(expectedPriorBlockHash)) { if (!metadata.getPreparedBlockHash().equals(expectedPriorBlockHash)) {
LOG.info( LOG.info(
"{}: Latest Prepared Metadata blockhash does not align with proposed block", "{}: Latest Prepared Metadata blockhash does not align with proposed block. Expected: {}, Actual: {}",
ERROR_PREFIX); ERROR_PREFIX,
expectedPriorBlockHash,
metadata.getPreparedBlockHash());
return false; return false;
} }

Loading…
Cancel
Save