|
33 | 33 | import org.hyperledger.besu.datatypes.Address; |
34 | 34 | import org.hyperledger.besu.ethereum.core.Util; |
35 | 35 | import org.hyperledger.besu.ethereum.mainnet.block.access.list.BlockAccessList; |
| 36 | +import org.hyperledger.besu.ethereum.rlp.RLPInput; |
| 37 | +import org.hyperledger.besu.ethereum.rlp.RLPOutput; |
36 | 38 |
|
37 | 39 | import java.util.List; |
38 | 40 | import java.util.Optional; |
@@ -104,6 +106,54 @@ private SignedData<RoundChangePayload> createRoundChange(final NodeKey nodeKey) |
104 | 106 | nodeKey.sign(Bytes32.wrap(roundChangePayload.hashForSignature().getBytes()))); |
105 | 107 | } |
106 | 108 |
|
| 109 | + @Test |
| 110 | + public void canDecodeProposalMessageFromLegacyNodeWithoutBlockAccessList() { |
| 111 | + // Simulate a pre-26.1.0 validator that encodes ProposalPayload WITHOUT the blockAccessList |
| 112 | + // field. |
| 113 | + // Old format payload list: [seqNum, roundNum, block] (3 items) |
| 114 | + // New format payload list: [seqNum, roundNum, block, null] (4 items) |
| 115 | + |
| 116 | + // The mock must consume the block bytes written by the legacy writeTo override, otherwise the |
| 117 | + // RLP cursor remains on the block hash and readBlockAccessList sees it instead of end-of-list. |
| 118 | + when(blockEncoder.readFrom(any())) |
| 119 | + .thenAnswer( |
| 120 | + inv -> { |
| 121 | + inv.<RLPInput>getArgument(0).skipNext(); |
| 122 | + return BLOCK; |
| 123 | + }); |
| 124 | + |
| 125 | + final NodeKey nodeKey = NodeKeyUtils.generate(); |
| 126 | + final ConsensusRoundIdentifier roundIdentifier = new ConsensusRoundIdentifier(1, 1); |
| 127 | + |
| 128 | + // ProposalPayload subclass that overrides writeTo() to omit the blockAccessList field, |
| 129 | + // reproducing the pre-26.1.0 wire format |
| 130 | + final ProposalPayload oldFormatPayload = |
| 131 | + new ProposalPayload(roundIdentifier, BLOCK, blockEncoder) { |
| 132 | + @Override |
| 133 | + public void writeTo(final RLPOutput output) { |
| 134 | + output.startList(); |
| 135 | + writeConsensusRound(output); |
| 136 | + output.writeBytes(BLOCK.getHash().getBytes()); |
| 137 | + // No blockAccessList field — simulates pre-26.1.0 encoding |
| 138 | + output.endList(); |
| 139 | + } |
| 140 | + }; |
| 141 | + |
| 142 | + final SignedData<ProposalPayload> signedPayload = |
| 143 | + SignedData.create( |
| 144 | + oldFormatPayload, |
| 145 | + nodeKey.sign(Bytes32.wrap(oldFormatPayload.hashForSignature().getBytes()))); |
| 146 | + |
| 147 | + final Proposal proposal = |
| 148 | + Proposal.decode(new Proposal(signedPayload, List.of(), List.of()).encode(), blockEncoder); |
| 149 | + |
| 150 | + assertThat(proposal.getBlockAccessList()).isEmpty(); |
| 151 | + assertThat(proposal.getSignedPayload().getPayload().getRoundIdentifier()) |
| 152 | + .isEqualTo(roundIdentifier); |
| 153 | + assertThat(proposal.getSignedPayload().getPayload().getProposedBlock().getHash()) |
| 154 | + .isEqualTo(BLOCK.getHash()); |
| 155 | + } |
| 156 | + |
107 | 157 | private void assertProposal( |
108 | 158 | final Proposal decodedProposal, |
109 | 159 | final Address expectedAddr, |
|
0 commit comments