Skip to content
Merged
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
- Plugin API: Allow the registration of multiple PluginTransactionPoolValidatorFactory [#9964](https://github.com/hyperledger/besu/pull/9964)
- Add `-Pcases` case name filtering to JMH benchmark suite [#9982](https://github.com/hyperledger/besu/pull/9982)
- Use JDK SHA-256 provider to leverage hardware SHA-NI instructions instead of BouncyCastle [#9924](https://github.com/hyperledger/besu/pull/9924)
- Support [EIP-7975](https://eips.ethereum.org/EIPS/eip-7975): eth/70 - partial block receipt lists

## 26.2.0

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"response": {
"jsonrpc": "2.0",
"id": 2,
"result": "0x45"
"result": "0x46"
},
"statusCode": 200
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,14 @@
/**
* Eth protocol messages as defined in <a
* href="https://github.com/ethereum/devp2p/blob/master/caps/eth.md">Ethereum Wire Protocol
* (ETH)</a>}
* (ETH)</a>
*/
public class EthProtocol implements SubProtocol {
public static final String NAME = "eth";
private static final EthProtocol INSTANCE = new EthProtocol();
public static final Capability ETH68 = Capability.create(NAME, EthProtocolVersion.V68);
public static final Capability ETH69 = Capability.create(NAME, EthProtocolVersion.V69);
public static final Capability ETH70 = Capability.create(NAME, EthProtocolVersion.V70);
public static final BitSet REQUEST_ID_MESSAGES;

static {
Expand All @@ -52,7 +53,7 @@ public class EthProtocol implements SubProtocol {
}

// Latest version of the Eth protocol
public static final Capability LATEST = ETH69;
public static final Capability LATEST = ETH70;

public static boolean requestIdCompatible(final int code) {
return REQUEST_ID_MESSAGES.get(code);
Expand All @@ -67,7 +68,7 @@ public String getName() {
public int messageSpace(final int protocolVersion) {
return switch (protocolVersion) {
case EthProtocolVersion.V68 -> 17;
case EthProtocolVersion.V69 -> 18;
case EthProtocolVersion.V69, EthProtocolVersion.V70 -> 18;
default -> 0;
};
}
Expand Down Expand Up @@ -106,4 +107,8 @@ public static EthProtocol get() {
public static boolean isEth69Compatible(final Capability capability) {
return NAME.equals(capability.getName()) && capability.getVersion() >= ETH69.getVersion();
}

public static boolean isEth70Compatible(final Capability capability) {
return NAME.equals(capability.getName()) && capability.getVersion() >= ETH70.getVersion();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
public class EthProtocolVersion {
public static final int V68 = 68;
public static final int V69 = 69;
public static final int V70 = 70;

/** eth/68 */
private static final List<Integer> eth68Messages =
Expand Down Expand Up @@ -76,7 +77,7 @@ public class EthProtocolVersion {
public static List<Integer> getSupportedMessages(final int protocolVersion) {
return switch (protocolVersion) {
case EthProtocolVersion.V68 -> eth68Messages;
case EthProtocolVersion.V69 -> eth69Messages;
case EthProtocolVersion.V69, EthProtocolVersion.V70 -> eth69Messages;
default -> Collections.emptyList();
};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import org.hyperledger.besu.ethereum.core.Difficulty;
import org.hyperledger.besu.ethereum.eth.EthProtocol;
import org.hyperledger.besu.ethereum.eth.EthProtocolConfiguration;
import org.hyperledger.besu.ethereum.eth.manager.exceptions.ProtocolViolationException;
import org.hyperledger.besu.ethereum.eth.messages.EthProtocolMessages;
import org.hyperledger.besu.ethereum.eth.messages.StatusMessage;
import org.hyperledger.besu.ethereum.eth.peervalidation.PeerValidator;
Expand Down Expand Up @@ -175,6 +176,7 @@ private List<Capability> calculateCapabilities(

capabilities.add(EthProtocol.ETH68);
capabilities.add(EthProtocol.ETH69);
capabilities.add(EthProtocol.ETH70);
capabilities.removeIf(cap -> cap.getVersion() > ethProtocolConfiguration.getMaxEthCapability());
capabilities.removeIf(cap -> cap.getVersion() < ethProtocolConfiguration.getMinEthCapability());

Expand Down Expand Up @@ -321,6 +323,16 @@ public void processMessage(final Capability capability, final Message message) {

ethPeer.disconnect(
DisconnectMessage.DisconnectReason.BREACH_OF_PROTOCOL_MALFORMED_MESSAGE_RECEIVED);
} catch (final ProtocolViolationException e) {
LOG.atDebug()
.setMessage("Received invalid message {} ({}), disconnecting: {}, {}")
.addArgument(messageData::getData)
.addArgument(e::getReason)
.addArgument(ethPeer::toString)
.addArgument(e::toString)
.log();

ethPeer.disconnect(e.getReason());
}
maybeResponseData.ifPresent(
responseData -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
*/
package org.hyperledger.besu.ethereum.eth.manager;

import static org.hyperledger.besu.ethereum.p2p.rlpx.wire.messages.DisconnectMessage.DisconnectReason.INVALID_FIRST_BLOCK_RECEIPT_INDEX;

import org.hyperledger.besu.datatypes.Hash;
import org.hyperledger.besu.ethereum.chain.Blockchain;
import org.hyperledger.besu.ethereum.core.BlockBody;
Expand All @@ -26,15 +28,18 @@
import org.hyperledger.besu.ethereum.core.encoding.receipt.TransactionReceiptEncodingConfiguration;
import org.hyperledger.besu.ethereum.eth.EthProtocol;
import org.hyperledger.besu.ethereum.eth.EthProtocolConfiguration;
import org.hyperledger.besu.ethereum.eth.manager.exceptions.ProtocolViolationException;
import org.hyperledger.besu.ethereum.eth.messages.BlockBodiesMessage;
import org.hyperledger.besu.ethereum.eth.messages.BlockHeadersMessage;
import org.hyperledger.besu.ethereum.eth.messages.EthProtocolMessages;
import org.hyperledger.besu.ethereum.eth.messages.GetBlockBodiesMessage;
import org.hyperledger.besu.ethereum.eth.messages.GetBlockHeadersMessage;
import org.hyperledger.besu.ethereum.eth.messages.GetNodeDataMessage;
import org.hyperledger.besu.ethereum.eth.messages.GetPaginatedReceiptsMessage;
import org.hyperledger.besu.ethereum.eth.messages.GetPooledTransactionsMessage;
import org.hyperledger.besu.ethereum.eth.messages.GetReceiptsMessage;
import org.hyperledger.besu.ethereum.eth.messages.NodeDataMessage;
import org.hyperledger.besu.ethereum.eth.messages.PaginatedReceiptsMessage;
import org.hyperledger.besu.ethereum.eth.messages.PooledTransactionsMessage;
import org.hyperledger.besu.ethereum.eth.messages.ReceiptsMessage;
import org.hyperledger.besu.ethereum.eth.transactions.TransactionPool;
Expand Down Expand Up @@ -96,13 +101,22 @@ private void registerResponseConstructors() {
maxMessageSize));
ethMessages.registerResponseConstructor(
EthProtocolMessages.GET_RECEIPTS,
(peer, messageData, capability) ->
constructGetReceiptsResponse(
(peer, messageData, capability) -> {
if (EthProtocol.isEth70Compatible(capability)) {
return constructGetPaginatedReceiptsResponse(
peer,
blockchain,
messageData,
ethereumWireProtocolConfiguration.getMaxGetReceipts(),
maxMessageSize,
capability));
maxMessageSize);
}
return constructGetReceiptsResponse(
blockchain,
messageData,
ethereumWireProtocolConfiguration.getMaxGetReceipts(),
maxMessageSize,
capability);
});
ethMessages.registerResponseConstructor(
EthProtocolMessages.GET_NODE_DATA,
(peer, messageData, capability) ->
Expand Down Expand Up @@ -226,18 +240,18 @@ static MessageData constructGetReceiptsResponse(
final int maxMessageSize,
final Capability cap) {
final GetReceiptsMessage getReceipts = GetReceiptsMessage.readFrom(message);
final Iterable<Hash> hashes = getReceipts.hashes();
final Iterable<Hash> blockHashes = getReceipts.blockHashes();

int responseSizeEstimate = RLP.MAX_PREFIX_SIZE;
final BytesValueRLPOutput rlp = new BytesValueRLPOutput();
rlp.startList();
int count = 0;
for (final Hash hash : hashes) {
for (final Hash blockHash : blockHashes) {
if (count >= requestLimit) {
break;
}
count++;
final Optional<List<TransactionReceipt>> maybeReceipts = blockchain.getTxReceipts(hash);
final Optional<List<TransactionReceipt>> maybeReceipts = blockchain.getTxReceipts(blockHash);
if (maybeReceipts.isEmpty()) {
continue;
}
Expand Down Expand Up @@ -265,6 +279,103 @@ static MessageData constructGetReceiptsResponse(
return ReceiptsMessage.createUnsafe(rlp.encoded());
}

static MessageData constructGetPaginatedReceiptsResponse(
final EthPeer peer,
final Blockchain blockchain,
final MessageData message,
final int requestLimit,
final int maxMessageSize) {
final GetPaginatedReceiptsMessage getPaginatedReceipts =
GetPaginatedReceiptsMessage.readFrom(message);
final List<Hash> requestedBlockHashes = getPaginatedReceipts.blockHashes();
final List<Hash> blockHashes;
if (requestedBlockHashes.size() > requestLimit) {
LOG.atDebug()
.setMessage(
"Requested receipts for {} blocks, more than allowed max number of {}, ignoring extra blocks")
.addArgument(requestedBlockHashes::size)
.addArgument(requestLimit)
.log();
blockHashes = requestedBlockHashes.subList(0, requestLimit);
} else {
blockHashes = requestedBlockHashes;
}

final var blockReceiptsRLPs = new ArrayList<BytesValueRLPOutput>(blockHashes.size());

int skipBefore = getPaginatedReceipts.firstBlockReceiptIndex();
LOG.trace(
"Paginated receipt request for {} blocks with first block receipt index {}",
blockHashes.size(),
skipBefore);
int responseSizeEstimate = RLP.MAX_PREFIX_SIZE;
boolean lastBlockIncomplete = false;

for (final Hash blockHash : blockHashes) {
final Optional<List<TransactionReceipt>> maybeReceipts = blockchain.getTxReceipts(blockHash);
if (maybeReceipts.isEmpty()) {
LOG.debug(
"Invalid request from peer {}, block {} does not exists, returning", peer, blockHash);
break;
}
Comment thread
macfarla marked this conversation as resolved.

final List<TransactionReceipt> blockReceipts = maybeReceipts.get();
final List<TransactionReceipt> requestedReceipts;

if (skipBefore > blockReceipts.size()) {
Comment thread
fab-10 marked this conversation as resolved.
throw new ProtocolViolationException(
"Invalid request from peer %s, firstBlockReceiptIndex %d is greater than or equal the receipt count of %d for block %s"
.formatted(peer, skipBefore, blockReceipts.size(), blockHash),
INVALID_FIRST_BLOCK_RECEIPT_INDEX);
}

if (skipBefore > 0) {
requestedReceipts = blockReceipts.subList(skipBefore, blockReceipts.size());
skipBefore = 0;
} else {
requestedReceipts = blockReceipts;
}

final BytesValueRLPOutput encodedBlockReceipts = new BytesValueRLPOutput();
encodedBlockReceipts.startList();

for (final TransactionReceipt receipt : requestedReceipts) {
final BytesValueRLPOutput encodedReceipt = new BytesValueRLPOutput();
TransactionReceiptEncoder.writeTo(
receipt,
encodedReceipt,
TransactionReceiptEncodingConfiguration.ETH69_RECEIPT_CONFIGURATION);
if (responseSizeEstimate + encodedReceipt.encodedSize() + RLP.MAX_PREFIX_SIZE
> maxMessageSize) {
lastBlockIncomplete = true;
break;
}
responseSizeEstimate += encodedReceipt.encodedSize();
encodedBlockReceipts.writeRaw(encodedReceipt.encoded());
}

encodedBlockReceipts.endList();
blockReceiptsRLPs.add(encodedBlockReceipts);
if (lastBlockIncomplete) {
break;
}
}

final BytesValueRLPOutput rlp = new BytesValueRLPOutput();
rlp.writeLongScalar(lastBlockIncomplete ? 1 : 0);
rlp.startList();
blockReceiptsRLPs.forEach(r -> rlp.writeRaw(r.encoded()));
rlp.endList();

final Bytes encodedResponse = rlp.encoded();
LOG.trace(
"Returning paginated receipts for {} blocks, with last block incomplete {}, enconded size {}",
blockHashes.size(),
lastBlockIncomplete,
encodedResponse.size());
return PaginatedReceiptsMessage.createUnsafe(encodedResponse, lastBlockIncomplete);
}

static MessageData constructGetPooledTransactionsResponse(
final TransactionPool transactionPool,
final EthPeer peer,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* Copyright contributors to Besu.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
*/
package org.hyperledger.besu.ethereum.eth.manager.exceptions;

import org.hyperledger.besu.ethereum.p2p.rlpx.wire.messages.DisconnectMessage;

public class ProtocolViolationException extends RuntimeException {
private final DisconnectMessage.DisconnectReason reason;

public ProtocolViolationException(final String message) {
this(message, DisconnectMessage.DisconnectReason.BREACH_OF_PROTOCOL);
}

public ProtocolViolationException(
final String message, final DisconnectMessage.DisconnectReason reason) {
super(message);
this.reason = reason;
}

public DisconnectMessage.DisconnectReason getReason() {
return reason;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ public enum PeerTaskValidationResponse {
RESULTS_DO_NOT_MATCH_QUERY(null, true),
NON_SEQUENTIAL_HEADERS_RETURNED(
DisconnectMessage.DisconnectReason.BREACH_OF_PROTOCOL_NON_SEQUENTIAL_HEADERS, true),
RESULTS_VALID_AND_GOOD(null, false);
RESULTS_VALID_AND_GOOD(null, false),
INVALID_RECEIPT_RETURNED(DisconnectMessage.DisconnectReason.INVALID_RECEIPT_RECEIVED, true);
private final Optional<DisconnectMessage.DisconnectReason> disconnectReason;
private final boolean recordUselessResponse;

Expand Down
Loading
Loading