Skip to content

Commit f64c147

Browse files
pulluribBhanu Pullurimacfarla
authored
QBFT: Fix validation of proposal based on older round's prepared block (hyperledger#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>
1 parent 674a7ba commit f64c147

File tree

6 files changed

+66
-19
lines changed

6 files changed

+66
-19
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
### Bug fixes
3131
- Fix registering new metric categories from plugins [#7825](https://github.com/hyperledger/besu/pull/7825)
3232
- Fix CVE-2024-47535 [7878](https://github.com/hyperledger/besu/pull/7878)
33+
- Fix QBFT prepared block based proposal validation [#7875](https://github.com/hyperledger/besu/pull/7875)
3334

3435
## 24.10.0
3536

consensus/qbft/src/integration-test/java/org/hyperledger/besu/consensus/qbft/support/TestContext.java

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -97,13 +97,37 @@ public MessageFactory getLocalNodeMessageFactory() {
9797
}
9898

9999
public Block createBlockForProposalFromChainHead(final long timestamp) {
100-
return createBlockForProposalFromChainHead(timestamp, finalState.getLocalAddress());
100+
return createBlockForProposalFromChainHead(timestamp, finalState.getLocalAddress(), 0);
101+
}
102+
103+
public Block createBlockForProposalFromChainHead(final long timestamp, final int roundNumber) {
104+
return createBlockForProposalFromChainHead(
105+
timestamp, finalState.getLocalAddress(), roundNumber);
106+
}
107+
108+
public Block createBlockForProposalFromChainHead(final long timestamp, final Address proposer) {
109+
// this implies that EVERY block will have this node as the proposer :/
110+
return createBlockForProposal(blockchain.getChainHeadHeader(), timestamp, proposer, 0);
111+
}
112+
113+
public Block createBlockForProposalFromChainHead(
114+
final long timestamp, final Address proposer, final int roundNumber) {
115+
// this implies that EVERY block will have this node as the proposer :/
116+
return createBlockForProposal(
117+
blockchain.getChainHeadHeader(), timestamp, proposer, roundNumber);
101118
}
102119

103120
public Block createBlockForProposal(
104-
final BlockHeader parent, final long timestamp, final Address proposer) {
121+
final BlockHeader parent,
122+
final long timestamp,
123+
final Address proposer,
124+
final int roundNumber) {
105125
final Block block =
106-
finalState.getBlockCreatorFactory().create(0).createBlock(timestamp, parent).getBlock();
126+
finalState
127+
.getBlockCreatorFactory()
128+
.create(roundNumber)
129+
.createBlock(timestamp, parent)
130+
.getBlock();
107131

108132
final BlockHeaderBuilder headerBuilder = BlockHeaderBuilder.fromHeader(block.getHeader());
109133
headerBuilder
@@ -114,9 +138,9 @@ public Block createBlockForProposal(
114138
return new Block(newHeader, block.getBody());
115139
}
116140

117-
public Block createBlockForProposalFromChainHead(final long timestamp, final Address proposer) {
118-
// this implies that EVERY block will have this node as the proposer :/
119-
return createBlockForProposal(blockchain.getChainHeadHeader(), timestamp, proposer);
141+
public Block createBlockForProposal(
142+
final BlockHeader parent, final long timestamp, final Address proposer) {
143+
return createBlockForProposal(parent, timestamp, proposer, 0);
120144
}
121145

122146
public RoundSpecificPeers roundSpecificPeers(final ConsensusRoundIdentifier roundId) {

consensus/qbft/src/integration-test/java/org/hyperledger/besu/consensus/qbft/test/RoundChangeTest.java

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,8 @@ public void roundChangeHasPopulatedCertificateIfQuorumPrepareMessagesAndProposal
144144
public void whenSufficientRoundChangeMessagesAreReceivedForNewRoundLocalNodeCreatesProposalMsg() {
145145
// Note: Round-4 is the next round for which the local node is Proposer
146146
final ConsensusRoundIdentifier targetRound = new ConsensusRoundIdentifier(1, 4);
147-
final Block locallyProposedBlock = context.createBlockForProposalFromChainHead(blockTimeStamp);
147+
final Block locallyProposedBlock =
148+
context.createBlockForProposalFromChainHead(blockTimeStamp, 4);
148149

149150
final RoundChange rc1 = peers.getNonProposing(0).injectRoundChange(targetRound, empty());
150151
final RoundChange rc2 = peers.getNonProposing(1).injectRoundChange(targetRound, empty());
@@ -177,14 +178,14 @@ public void proposalMessageContainsBlockOnWhichPeerPrepared() {
177178
context,
178179
new ConsensusRoundIdentifier(1, 1),
179180
context.createBlockForProposalFromChainHead(
180-
ARBITRARY_BLOCKTIME / 2, peers.getProposer().getNodeAddress()));
181+
ARBITRARY_BLOCKTIME / 2, peers.getProposer().getNodeAddress(), 1));
181182

182183
final PreparedCertificate bestPrepCert =
183184
createValidPreparedCertificate(
184185
context,
185186
new ConsensusRoundIdentifier(1, 2),
186187
context.createBlockForProposalFromChainHead(
187-
ARBITRARY_BLOCKTIME, peers.getProposer().getNodeAddress()));
188+
ARBITRARY_BLOCKTIME, peers.getProposer().getNodeAddress(), 2));
188189

189190
final ConsensusRoundIdentifier targetRound = new ConsensusRoundIdentifier(1, 4);
190191

@@ -206,7 +207,7 @@ public void proposalMessageContainsBlockOnWhichPeerPrepared() {
206207
// round number.
207208
final Block expectedBlockToPropose =
208209
context.createBlockForProposalFromChainHead(
209-
ARBITRARY_BLOCKTIME, peers.getProposer().getNodeAddress());
210+
ARBITRARY_BLOCKTIME, peers.getProposer().getNodeAddress(), 4);
210211

211212
final Proposal expectedProposal =
212213
localNodeMessageFactory.createProposal(
@@ -234,7 +235,8 @@ public void cannotRoundChangeToAnEarlierRound() {
234235
final ConsensusRoundIdentifier priorRound = new ConsensusRoundIdentifier(1, 4);
235236
peers.roundChange(priorRound);
236237

237-
final Block locallyProposedBlock = context.createBlockForProposalFromChainHead(blockTimeStamp);
238+
final Block locallyProposedBlock =
239+
context.createBlockForProposalFromChainHead(blockTimeStamp, 9);
238240

239241
final Proposal expectedProposal =
240242
localNodeMessageFactory.createProposal(
@@ -271,7 +273,7 @@ public void subsequentRoundChangeMessagesFromPeerDoNotOverwritePriorMessage() {
271273
context,
272274
new ConsensusRoundIdentifier(1, 2),
273275
context.createBlockForProposalFromChainHead(
274-
ARBITRARY_BLOCKTIME, peers.getProposer().getNodeAddress()));
276+
ARBITRARY_BLOCKTIME, peers.getProposer().getNodeAddress(), 2));
275277

276278
final List<SignedData<RoundChangePayload>> roundChangeMessages = Lists.newArrayList();
277279
// Create a roundChange containing a PreparedCertificate
@@ -288,7 +290,7 @@ public void subsequentRoundChangeMessagesFromPeerDoNotOverwritePriorMessage() {
288290

289291
final Block expectedBlockToPropose =
290292
context.createBlockForProposalFromChainHead(
291-
ARBITRARY_BLOCKTIME, peers.getProposer().getNodeAddress());
293+
ARBITRARY_BLOCKTIME, peers.getProposer().getNodeAddress(), 4);
292294

293295
final Proposal expectedProposal =
294296
localNodeMessageFactory.createProposal(

consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/statemachine/QbftRound.java

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -160,9 +160,18 @@ public void startRoundWith(
160160
} else {
161161
LOG.debug(
162162
"Sending proposal from PreparedCertificate. round={}", roundState.getRoundIdentifier());
163-
blockToPublish = bestPreparedCertificate.get().getBlock();
163+
Block preparedBlock = bestPreparedCertificate.get().getBlock();
164+
final BftBlockInterface bftBlockInterface =
165+
protocolContext.getConsensusContext(BftContext.class).getBlockInterface();
166+
blockToPublish =
167+
bftBlockInterface.replaceRoundInBlock(
168+
preparedBlock,
169+
roundState.getRoundIdentifier().getRoundNumber(),
170+
BftBlockHeaderFunctions.forCommittedSeal(bftExtraDataCodec));
164171
}
165172

173+
LOG.debug(" proposal - new/prepared block hash : {}", blockToPublish.getHash());
174+
166175
updateStateWithProposalAndTransmit(
167176
blockToPublish,
168177
roundChangeArtifacts.getRoundChanges(),
@@ -202,8 +211,9 @@ protected void updateStateWithProposalAndTransmit(
202211
proposal.getSignedPayload().getPayload().getProposedBlock(),
203212
roundChanges,
204213
prepares);
205-
updateStateWithProposedBlock(proposal);
206-
sendPrepare(block);
214+
if (updateStateWithProposedBlock(proposal)) {
215+
sendPrepare(block);
216+
}
207217
}
208218

209219
/**

consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/statemachine/QbftRoundFactory.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,8 @@ public QbftRound createNewRound(final BlockHeader parentHeader, final int round)
9999
*/
100100
public QbftRound createNewRoundWithState(
101101
final BlockHeader parentHeader, final RoundState roundState) {
102-
final BlockCreator blockCreator = blockCreatorFactory.create(0);
102+
final BlockCreator blockCreator =
103+
blockCreatorFactory.create(roundState.getRoundIdentifier().getRoundNumber());
103104

104105
// TODO(tmm): Why is this created everytime?!
105106
final QbftMessageTransmitter messageTransmitter =

consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/validation/ProposalValidator.java

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,13 @@ private boolean validateProposalAndRoundChangeAreConsistent(final Proposal propo
137137
final PreparedRoundMetadata metadata =
138138
roundChangeWithLatestPreparedRound.get().getPayload().getPreparedRoundMetadata().get();
139139

140+
LOG.debug(
141+
"Prepared Metadata blockhash : {}, proposal blockhash: {}, prepared round in message: {}, proposal round in message: {}",
142+
metadata.getPreparedBlockHash(),
143+
proposal.getBlock().getHash(),
144+
metadata.getPreparedRound(),
145+
proposal.getRoundIdentifier().getRoundNumber());
146+
140147
// The Hash in the roundchange/proposals is NOT the same as the value in the
141148
// prepares/roundchanges
142149
// as said payloads reference the block with an OLD round number in it - therefore, need
@@ -155,8 +162,10 @@ private boolean validateProposalAndRoundChangeAreConsistent(final Proposal propo
155162

156163
if (!metadata.getPreparedBlockHash().equals(expectedPriorBlockHash)) {
157164
LOG.info(
158-
"{}: Latest Prepared Metadata blockhash does not align with proposed block",
159-
ERROR_PREFIX);
165+
"{}: Latest Prepared Metadata blockhash does not align with proposed block. Expected: {}, Actual: {}",
166+
ERROR_PREFIX,
167+
expectedPriorBlockHash,
168+
metadata.getPreparedBlockHash());
160169
return false;
161170
}
162171

0 commit comments

Comments
 (0)