Skip to content

Fix QBFT ProposalPayload backward compatibility with pre-26.1.0 Besu versions#9977

Merged
jframe merged 5 commits intobesu-eth:mainfrom
jframe:qbft_proposal_bal_backwards_compatability
Mar 6, 2026
Merged

Fix QBFT ProposalPayload backward compatibility with pre-26.1.0 Besu versions#9977
jframe merged 5 commits intobesu-eth:mainfrom
jframe:qbft_proposal_bal_backwards_compatability

Conversation

@jframe
Copy link
Copy Markdown
Contributor

@jframe jframe commented Mar 6, 2026

Summary

  • ProposalPayload.readBlockAccessList() was missing an isEndOfCurrentList() guard before calling nextIsNull()
  • When a pre-26.1.0 node sends a proposal without the blockAccessList field, the RLP cursor sits at end-of-list after reading the block. nextIsNull() then peeks at the next byte outside the payload list — the 65-byte signature — which is a LONG_ELEMENT, not null. This causes BlockAccessListDecoder.decode() to call enterList() on the signature and throw: RLPException: Expected current item to be a list, but it is: LONG_ELEMENT
  • Fix adds the isEndOfCurrentList() check to return Optional.empty() for old-format messages
  • Adds a regression test that encodes a ProposalPayload in the pre-26.1.0 wire format (without blockAccessList) using an anonymous subclass and verifies it decodes correctly

Background

The blockAccessList field was added to ProposalPayload in 26.1.0 (commit a07c7bf65a2). Networks running mixed 26.1.0+/pre-26.1.0 validators would hit this crash whenever an older node sent a proposal.

Test plan

  • ./gradlew :consensus:qbft-core:test --tests "org.hyperledger.besu.consensus.qbft.core.messagewrappers.ProposalTest"

🤖 Generated with Claude Code

ProposalPayload.readBlockAccessList() did not check isEndOfCurrentList()
before calling nextIsNull(). When a pre-26.1.0 node sends a proposal
without the blockAccessList field, the RLP cursor is at end-of-list
after reading the block, but nextIsNull() peeks at the next byte outside
the payload list (the 65-byte signature), which is a LONG_ELEMENT not a
null, causing BlockAccessListDecoder.decode() to call enterList() on it
and throw an RLPException.

Fix adds the isEndOfCurrentList() guard to return Optional.empty() for
messages in the old format, and adds a regression test that encodes a
proposal in the pre-26.1.0 wire format and verifies it decodes correctly.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

Signed-off-by: Jason Frame <jason.frame@consensys.net>
Copilot AI review requested due to automatic review settings March 6, 2026 00:00
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes QBFT ProposalPayload decoding to be backward-compatible with pre-26.1.0 nodes that omit the blockAccessList RLP field, avoiding mis-parsing the following signature bytes.

Changes:

  • Add an isEndOfCurrentList() guard in ProposalPayload.readBlockAccessList() to return Optional.empty() for legacy payloads.
  • Add a regression test that encodes a legacy-format ProposalPayload (without blockAccessList) and verifies successful decode.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
consensus/qbft-core/src/main/java/org/hyperledger/besu/consensus/qbft/core/payload/ProposalPayload.java Adds end-of-list guard to safely handle old payload format without blockAccessList.
consensus/qbft-core/src/test/java/org/hyperledger/besu/consensus/qbft/core/messagewrappers/ProposalTest.java Adds regression test covering decode of legacy proposal payloads.

jframe and others added 3 commits March 6, 2026 10:07
…us/qbft/core/messagewrappers/ProposalTest.java


Update comment with correct number of items for each payload

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Signed-off-by: Jason Frame <jasonwframe@gmail.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

Signed-off-by: Jason Frame <jason.frame@consensys.net>
Signed-off-by: Jason Frame <jason.frame@consensys.net>
@jframe jframe changed the title Fix QBFT ProposalPayload backward compatibility with pre-26.1.0 nodes Fix QBFT ProposalPayload backward compatibility with pre-26.1.0 Besu versions Mar 6, 2026
@jframe jframe merged commit 4721226 into besu-eth:main Mar 6, 2026
46 checks passed
@jframe jframe deleted the qbft_proposal_bal_backwards_compatability branch March 6, 2026 01:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants