Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -97,13 +97,37 @@ public MessageFactory getLocalNodeMessageFactory() {
}

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(
final BlockHeader parent, final long timestamp, final Address proposer) {
final BlockHeader parent,
final long timestamp,
final Address proposer,
final int roundNumber) {
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());
headerBuilder
Expand All @@ -114,9 +138,9 @@ public Block createBlockForProposal(
return new Block(newHeader, block.getBody());
}

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);
public Block createBlockForProposal(
final BlockHeader parent, final long timestamp, final Address proposer) {
return createBlockForProposal(parent, timestamp, proposer, 0);
}

public RoundSpecificPeers roundSpecificPeers(final ConsensusRoundIdentifier roundId) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,8 @@ public void roundChangeHasPopulatedCertificateIfQuorumPrepareMessagesAndProposal
public void whenSufficientRoundChangeMessagesAreReceivedForNewRoundLocalNodeCreatesProposalMsg() {
// Note: Round-4 is the next round for which the local node is Proposer
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 rc2 = peers.getNonProposing(1).injectRoundChange(targetRound, empty());
Expand Down Expand Up @@ -177,14 +178,14 @@ public void proposalMessageContainsBlockOnWhichPeerPrepared() {
context,
new ConsensusRoundIdentifier(1, 1),
context.createBlockForProposalFromChainHead(
ARBITRARY_BLOCKTIME / 2, peers.getProposer().getNodeAddress()));
ARBITRARY_BLOCKTIME / 2, peers.getProposer().getNodeAddress(), 1));

final PreparedCertificate bestPrepCert =
createValidPreparedCertificate(
context,
new ConsensusRoundIdentifier(1, 2),
context.createBlockForProposalFromChainHead(
ARBITRARY_BLOCKTIME, peers.getProposer().getNodeAddress()));
ARBITRARY_BLOCKTIME, peers.getProposer().getNodeAddress(), 2));

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

Expand All @@ -206,7 +207,7 @@ public void proposalMessageContainsBlockOnWhichPeerPrepared() {
// round number.
final Block expectedBlockToPropose =
context.createBlockForProposalFromChainHead(
ARBITRARY_BLOCKTIME, peers.getProposer().getNodeAddress());
ARBITRARY_BLOCKTIME, peers.getProposer().getNodeAddress(), 4);

final Proposal expectedProposal =
localNodeMessageFactory.createProposal(
Expand Down Expand Up @@ -234,7 +235,8 @@ public void cannotRoundChangeToAnEarlierRound() {
final ConsensusRoundIdentifier priorRound = new ConsensusRoundIdentifier(1, 4);
peers.roundChange(priorRound);

final Block locallyProposedBlock = context.createBlockForProposalFromChainHead(blockTimeStamp);
final Block locallyProposedBlock =
context.createBlockForProposalFromChainHead(blockTimeStamp, 9);

final Proposal expectedProposal =
localNodeMessageFactory.createProposal(
Expand Down Expand Up @@ -271,7 +273,7 @@ public void subsequentRoundChangeMessagesFromPeerDoNotOverwritePriorMessage() {
context,
new ConsensusRoundIdentifier(1, 2),
context.createBlockForProposalFromChainHead(
ARBITRARY_BLOCKTIME, peers.getProposer().getNodeAddress()));
ARBITRARY_BLOCKTIME, peers.getProposer().getNodeAddress(), 2));

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

final Block expectedBlockToPropose =
context.createBlockForProposalFromChainHead(
ARBITRARY_BLOCKTIME, peers.getProposer().getNodeAddress());
ARBITRARY_BLOCKTIME, peers.getProposer().getNodeAddress(), 4);

final Proposal expectedProposal =
localNodeMessageFactory.createProposal(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -160,9 +160,18 @@ public void startRoundWith(
} else {
LOG.debug(
"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(
blockToPublish,
roundChangeArtifacts.getRoundChanges(),
Expand Down Expand Up @@ -202,8 +211,9 @@ protected void updateStateWithProposalAndTransmit(
proposal.getSignedPayload().getPayload().getProposedBlock(),
roundChanges,
prepares);
updateStateWithProposedBlock(proposal);
sendPrepare(block);
if (updateStateWithProposedBlock(proposal)) {
sendPrepare(block);
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,8 @@ public QbftRound createNewRound(final BlockHeader parentHeader, final int round)
*/
public QbftRound createNewRoundWithState(
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?!
final QbftMessageTransmitter messageTransmitter =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,13 @@ private boolean validateProposalAndRoundChangeAreConsistent(final Proposal propo
final PreparedRoundMetadata metadata =
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
// prepares/roundchanges
// as said payloads reference the block with an OLD round number in it - therefore, need
Expand All @@ -155,8 +162,10 @@ private boolean validateProposalAndRoundChangeAreConsistent(final Proposal propo

if (!metadata.getPreparedBlockHash().equals(expectedPriorBlockHash)) {
LOG.info(
"{}: Latest Prepared Metadata blockhash does not align with proposed block",
ERROR_PREFIX);
"{}: Latest Prepared Metadata blockhash does not align with proposed block. Expected: {}, Actual: {}",
ERROR_PREFIX,
expectedPriorBlockHash,
metadata.getPreparedBlockHash());
return false;
}

Expand Down
Loading