From 395405225eedeb3226dece547d91e2d0ba5317c1 Mon Sep 17 00:00:00 2001 From: mbaxter Date: Fri, 29 Mar 2024 15:46:15 -0400 Subject: [PATCH 1/5] Add bad block events Signed-off-by: mbaxter --- .../dsl/node/ThreadBesuNodeRunner.java | 3 +- .../org/hyperledger/besu/cli/BesuCommand.java | 3 +- .../besu/services/BesuEventsImpl.java | 17 ++- .../besu/services/BesuEventsImplTest.java | 106 +++++++++++++++++- .../besu/ethereum/chain/BadBlockCause.java | 8 +- .../besu/ethereum/chain/BadBlockManager.java | 15 ++- .../hyperledger/besu/ethereum/core/Block.java | 4 +- .../ethereum/chain/BadBlockManagerTest.java | 105 +++++++++++++++++ plugin-api/build.gradle | 2 +- .../besu/plugin/data/BadBlockCause.java | 42 +++++++ .../hyperledger/besu/plugin/data/Block.java | 24 +++- .../besu/plugin/services/BesuEvents.java | 39 +++++++ 12 files changed, 351 insertions(+), 17 deletions(-) create mode 100644 plugin-api/src/main/java/org/hyperledger/besu/plugin/data/BadBlockCause.java rename ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/BadBlockReason.java => plugin-api/src/main/java/org/hyperledger/besu/plugin/data/Block.java (68%) diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/ThreadBesuNodeRunner.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/ThreadBesuNodeRunner.java index 0f943ea0797..5ab01326d70 100644 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/ThreadBesuNodeRunner.java +++ b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/ThreadBesuNodeRunner.java @@ -313,7 +313,8 @@ public void startNode(final BesuNode node) { besuController.getProtocolContext().getBlockchain(), besuController.getProtocolManager().getBlockBroadcaster(), besuController.getTransactionPool(), - besuController.getSyncState())); + besuController.getSyncState(), + besuController.getProtocolContext().getBadBlockManager())); besuPluginContext.startPlugins(); runner.startEthereumMainLoop(); diff --git a/besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java b/besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java index 1c63329ac25..198a61154cb 100644 --- a/besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java +++ b/besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java @@ -1293,7 +1293,8 @@ private void startPlugins() { besuController.getProtocolContext().getBlockchain(), besuController.getProtocolManager().getBlockBroadcaster(), besuController.getTransactionPool(), - besuController.getSyncState())); + besuController.getSyncState(), + besuController.getProtocolContext().getBadBlockManager())); besuPluginContext.addService(MetricsSystem.class, getMetricsSystem()); besuPluginContext.addService( diff --git a/besu/src/main/java/org/hyperledger/besu/services/BesuEventsImpl.java b/besu/src/main/java/org/hyperledger/besu/services/BesuEventsImpl.java index 205b325ea52..5e6a63df407 100644 --- a/besu/src/main/java/org/hyperledger/besu/services/BesuEventsImpl.java +++ b/besu/src/main/java/org/hyperledger/besu/services/BesuEventsImpl.java @@ -18,6 +18,7 @@ import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.ethereum.api.query.LogsQuery; +import org.hyperledger.besu.ethereum.chain.BadBlockManager; import org.hyperledger.besu.ethereum.chain.Blockchain; import org.hyperledger.besu.ethereum.core.BlockBody; import org.hyperledger.besu.ethereum.core.Difficulty; @@ -44,6 +45,7 @@ public class BesuEventsImpl implements BesuEvents { private final BlockBroadcaster blockBroadcaster; private final TransactionPool transactionPool; private final SyncState syncState; + private final BadBlockManager badBlockManager; /** * Constructor for BesuEventsImpl @@ -52,16 +54,19 @@ public class BesuEventsImpl implements BesuEvents { * @param blockBroadcaster An instance of BlockBroadcaster * @param transactionPool An instance of TransactionPool * @param syncState An instance of SyncState + * @param badBlockManager A cache of bad blocks encountered on the network */ public BesuEventsImpl( final Blockchain blockchain, final BlockBroadcaster blockBroadcaster, final TransactionPool transactionPool, - final SyncState syncState) { + final SyncState syncState, + final BadBlockManager badBlockManager) { this.blockchain = blockchain; this.blockBroadcaster = blockBroadcaster; this.transactionPool = transactionPool; this.syncState = syncState; + this.badBlockManager = badBlockManager; } @Override @@ -166,6 +171,16 @@ public void removeLogListener(final long listenerIdentifier) { blockchain.removeObserver(listenerIdentifier); } + @Override + public long addBadBlockListener(final BadBlockListener listener) { + return badBlockManager.subscribeToBadBlocks(listener); + } + + @Override + public void removeBadBlockListener(final long listenerIdentifier) { + badBlockManager.unsubscribeFromBadBlocks(listenerIdentifier); + } + private static PropagatedBlockContext blockPropagatedContext( final Supplier blockHeaderSupplier, final Supplier blockBodySupplier, diff --git a/besu/src/test/java/org/hyperledger/besu/services/BesuEventsImplTest.java b/besu/src/test/java/org/hyperledger/besu/services/BesuEventsImplTest.java index 321ef8a3b82..9c0457b5a51 100644 --- a/besu/src/test/java/org/hyperledger/besu/services/BesuEventsImplTest.java +++ b/besu/src/test/java/org/hyperledger/besu/services/BesuEventsImplTest.java @@ -27,6 +27,8 @@ import org.hyperledger.besu.datatypes.Transaction; import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.ethereum.ProtocolContext; +import org.hyperledger.besu.ethereum.chain.BadBlockCause; +import org.hyperledger.besu.ethereum.chain.BadBlockManager; import org.hyperledger.besu.ethereum.chain.DefaultBlockchain; import org.hyperledger.besu.ethereum.chain.MutableBlockchain; import org.hyperledger.besu.ethereum.core.Block; @@ -60,9 +62,11 @@ import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive; import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; import org.hyperledger.besu.plugin.data.AddedBlockContext; +import org.hyperledger.besu.plugin.data.BlockHeader; import org.hyperledger.besu.plugin.data.LogWithMetadata; import org.hyperledger.besu.plugin.data.PropagatedBlockContext; import org.hyperledger.besu.plugin.data.SyncStatus; +import org.hyperledger.besu.plugin.services.BesuEvents.BadBlockListener; import org.hyperledger.besu.services.kvstore.InMemoryKeyValueStorage; import org.hyperledger.besu.testutil.DeterministicEthScheduler; import org.hyperledger.besu.testutil.TestClock; @@ -113,6 +117,7 @@ public class BesuEventsImplTest { private BesuEventsImpl serviceImpl; private MutableBlockchain blockchain; private final BlockDataGenerator gen = new BlockDataGenerator(); + private final BadBlockManager badBlockManager = new BadBlockManager(); @BeforeEach public void setUp() { @@ -171,7 +176,9 @@ public void setUp() { new BlobCache(), MiningParameters.newDefault()); - serviceImpl = new BesuEventsImpl(blockchain, blockBroadcaster, transactionPool, syncState); + serviceImpl = + new BesuEventsImpl( + blockchain, blockBroadcaster, transactionPool, syncState, badBlockManager); } @Test @@ -504,6 +511,103 @@ public void logEventDoesNotFireAfterUnsubscribe() { assertThat(result).isEmpty(); } + @Test + public void badBlockEventFiresAfterSubscribe() { + // Track bad block events + final AtomicReference badBlockResult = + new AtomicReference<>(); + final AtomicReference badBlockCauseResult = + new AtomicReference<>(); + final AtomicReference badBlockHeaderResult = new AtomicReference<>(); + final AtomicReference + badBlockHeaderCauseResult = new AtomicReference<>(); + serviceImpl.addBadBlockListener( + new BadBlockListener() { + @Override + public void onBadBlockAdded( + final org.hyperledger.besu.plugin.data.Block badBlock, + final org.hyperledger.besu.plugin.data.BadBlockCause cause) { + badBlockResult.set(badBlock); + badBlockCauseResult.set(cause); + } + + @Override + public void onBadBlockHeaderAdded( + final BlockHeader badBlockHeader, + final org.hyperledger.besu.plugin.data.BadBlockCause cause) { + badBlockHeaderResult.set(badBlockHeader); + badBlockHeaderCauseResult.set(cause); + } + }); + + // Add bad block + final BadBlockCause blockCause = BadBlockCause.fromValidationFailure("failed"); + final Block block = gen.block(new BlockDataGenerator.BlockOptions()); + badBlockManager.addBadBlock(block, blockCause); + + // Check we caught the bad block + assertThat(badBlockResult.get()).isEqualTo(block); + assertThat(badBlockCauseResult.get()).isEqualTo(blockCause); + assertThat(badBlockHeaderResult.get()).isNull(); + + // Add bad block header + final BadBlockCause headerCause = BadBlockCause.fromValidationFailure("oops"); + final Block headerBlock = gen.block(new BlockDataGenerator.BlockOptions()); + badBlockManager.addBadHeader(headerBlock.getHeader(), headerCause); + + // Check we caught the bad block header + assertThat(badBlockHeaderResult.get()).isEqualTo(headerBlock.getHeader()); + assertThat(badBlockHeaderCauseResult.get()).isEqualTo(headerCause); + } + + @Test + public void badBlockEventDoesNotFireAfterUnsubscribe() { + // Track bad block events + final AtomicReference badBlockResult = + new AtomicReference<>(); + final AtomicReference badBlockCauseResult = + new AtomicReference<>(); + final AtomicReference badBlockHeaderResult = new AtomicReference<>(); + final AtomicReference + badBlockHeaderCauseResult = new AtomicReference<>(); + final long listenerId = + serviceImpl.addBadBlockListener( + new BadBlockListener() { + @Override + public void onBadBlockAdded( + final org.hyperledger.besu.plugin.data.Block badBlock, + final org.hyperledger.besu.plugin.data.BadBlockCause cause) { + badBlockResult.set(badBlock); + badBlockCauseResult.set(cause); + } + + @Override + public void onBadBlockHeaderAdded( + final BlockHeader badBlockHeader, + final org.hyperledger.besu.plugin.data.BadBlockCause cause) { + badBlockHeaderResult.set(badBlockHeader); + badBlockHeaderCauseResult.set(cause); + } + }); + // Unsubscribe + serviceImpl.removeBadBlockListener(listenerId); + + // Add bad block + final BadBlockCause blockCause = BadBlockCause.fromValidationFailure("failed"); + final Block block = gen.block(new BlockDataGenerator.BlockOptions()); + badBlockManager.addBadBlock(block, blockCause); + // Add bad block header + final BadBlockCause headerCause = BadBlockCause.fromValidationFailure("oops"); + final Block headerBlock = gen.block(new BlockDataGenerator.BlockOptions()); + badBlockManager.addBadHeader(headerBlock.getHeader(), headerCause); + + // Check we did not process any events + assertThat(badBlockResult.get()).isNull(); + assertThat(badBlockCauseResult.get()).isNull(); + assertThat(badBlockHeaderResult.get()).isNull(); + assertThat(badBlockHeaderCauseResult.get()).isNull(); + } + private Block generateBlock() { final BlockBody body = new BlockBody(Collections.emptyList(), Collections.emptyList()); return new Block(new BlockHeaderTestFixture().buildHeader(), body); diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/BadBlockCause.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/BadBlockCause.java index 601e96de25f..6fd6ae1dfbf 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/BadBlockCause.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/BadBlockCause.java @@ -15,14 +15,14 @@ */ package org.hyperledger.besu.ethereum.chain; -import static org.hyperledger.besu.ethereum.chain.BadBlockReason.DESCENDS_FROM_BAD_BLOCK; -import static org.hyperledger.besu.ethereum.chain.BadBlockReason.SPEC_VALIDATION_FAILURE; +import static org.hyperledger.besu.plugin.data.BadBlockCause.BadBlockReason.DESCENDS_FROM_BAD_BLOCK; +import static org.hyperledger.besu.plugin.data.BadBlockCause.BadBlockReason.SPEC_VALIDATION_FAILURE; import org.hyperledger.besu.ethereum.core.Block; import com.google.common.base.MoreObjects; -public class BadBlockCause { +public class BadBlockCause implements org.hyperledger.besu.plugin.data.BadBlockCause { private final BadBlockReason reason; private final String description; @@ -42,10 +42,12 @@ private BadBlockCause(final BadBlockReason reason, final String description) { this.description = description; } + @Override public BadBlockReason getReason() { return reason; } + @Override public String getDescription() { return description; } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/BadBlockManager.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/BadBlockManager.java index b13f81c877c..0f56e42df95 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/BadBlockManager.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/BadBlockManager.java @@ -17,6 +17,8 @@ import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.ethereum.core.Block; import org.hyperledger.besu.ethereum.core.BlockHeader; +import org.hyperledger.besu.plugin.services.BesuEvents.BadBlockListener; +import org.hyperledger.besu.util.Subscribers; import java.util.Collection; import java.util.Optional; @@ -37,6 +39,7 @@ public class BadBlockManager { CacheBuilder.newBuilder().maximumSize(MAX_BAD_BLOCKS_SIZE).concurrencyLevel(1).build(); private final Cache latestValidHashes = CacheBuilder.newBuilder().maximumSize(MAX_BAD_BLOCKS_SIZE).concurrencyLevel(1).build(); + private final Subscribers badBlockSubscribers = Subscribers.create(true); /** * Add a new invalid block. @@ -45,9 +48,9 @@ public class BadBlockManager { * @param cause the cause detailing why the block is considered invalid */ public void addBadBlock(final Block badBlock, final BadBlockCause cause) { - // TODO(#6301) Expose bad block with cause through BesuEvents LOG.debug("Register bad block {} with cause: {}", badBlock.toLogString(), cause); this.badBlocks.put(badBlock.getHash(), badBlock); + badBlockSubscribers.forEach(s -> s.onBadBlockAdded(badBlock, cause)); } public void reset() { @@ -81,9 +84,9 @@ public Optional getBadBlock(final Hash hash) { } public void addBadHeader(final BlockHeader header, final BadBlockCause cause) { - // TODO(#6301) Expose bad block header with cause through BesuEvents LOG.debug("Register bad block header {} with cause: {}", header.toLogString(), cause); badHeaders.put(header.getHash(), header); + badBlockSubscribers.forEach(s -> s.onBadBlockHeaderAdded(header, cause)); } public boolean isBadBlock(final Hash blockHash) { @@ -97,4 +100,12 @@ public void addLatestValidHash(final Hash blockHash, final Hash latestValidHash) public Optional getLatestValidHash(final Hash blockHash) { return Optional.ofNullable(latestValidHashes.getIfPresent(blockHash)); } + + public long subscribeToBadBlocks(final BadBlockListener listener) { + return badBlockSubscribers.subscribe(listener); + } + + public void unsubscribeFromBadBlocks(final long id) { + badBlockSubscribers.unsubscribe(id); + } } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/Block.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/Block.java index 32313820cc8..69829bb8879 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/Block.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/Block.java @@ -25,7 +25,7 @@ import org.apache.tuweni.bytes.Bytes; -public class Block { +public class Block implements org.hyperledger.besu.plugin.data.Block { private final BlockHeader header; private final BlockBody body; @@ -35,10 +35,12 @@ public Block(final BlockHeader header, final BlockBody body) { this.body = body; } + @Override public BlockHeader getHeader() { return header; } + @Override public BlockBody getBody() { return body; } diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/chain/BadBlockManagerTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/chain/BadBlockManagerTest.java index ba027c64361..41fa5ac5134 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/chain/BadBlockManagerTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/chain/BadBlockManagerTest.java @@ -19,6 +19,11 @@ import org.hyperledger.besu.ethereum.core.Block; import org.hyperledger.besu.ethereum.core.BlockchainSetupUtil; +import org.hyperledger.besu.plugin.data.BlockHeader; +import org.hyperledger.besu.plugin.services.BesuEvents.BadBlockListener; + +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; import org.junit.jupiter.api.Test; @@ -26,6 +31,7 @@ public class BadBlockManagerTest { final BlockchainSetupUtil chainUtil = BlockchainSetupUtil.forMainnet(); final Block block = chainUtil.getBlock(1); + final Block block2 = chainUtil.getBlock(2); final BadBlockManager badBlockManager = new BadBlockManager(); @Test @@ -66,4 +72,103 @@ public void isBadBlock_trueForBadBlock() { assertThat(badBlockManager.isBadBlock(block.getHash())).isTrue(); } + + @Test + public void subscribeToBadBlocks_listenerReceivesBadBlockEvent() { + + final AtomicReference badBlockResult = + new AtomicReference<>(); + final AtomicReference badBlockCauseResult = + new AtomicReference<>(); + + badBlockManager.subscribeToBadBlocks( + (new BadBlockListener() { + @Override + public void onBadBlockAdded( + final org.hyperledger.besu.plugin.data.Block badBlock, + final org.hyperledger.besu.plugin.data.BadBlockCause cause) { + badBlockResult.set(badBlock); + badBlockCauseResult.set(cause); + } + + @Override + public void onBadBlockHeaderAdded( + final BlockHeader badBlockHeader, + final org.hyperledger.besu.plugin.data.BadBlockCause cause) { + // Noop + } + })); + + final BadBlockCause cause = BadBlockCause.fromValidationFailure("fail"); + badBlockManager.addBadBlock(block, cause); + + // Check event was emitted + assertThat(badBlockResult.get()).isEqualTo(block); + assertThat(badBlockCauseResult.get()).isEqualTo(cause); + } + + @Test + public void subscribeToBadBlocks_listenerReceivesBadHeaderEvent() { + + final AtomicReference badBlockResult = + new AtomicReference<>(); + final AtomicReference badBlockCauseResult = + new AtomicReference<>(); + + badBlockManager.subscribeToBadBlocks( + (new BadBlockListener() { + @Override + public void onBadBlockAdded( + final org.hyperledger.besu.plugin.data.Block badBlock, + final org.hyperledger.besu.plugin.data.BadBlockCause cause) { + // Noop + } + + @Override + public void onBadBlockHeaderAdded( + final BlockHeader badBlockHeader, + final org.hyperledger.besu.plugin.data.BadBlockCause cause) { + badBlockResult.set(badBlockHeader); + badBlockCauseResult.set(cause); + } + })); + + final BadBlockCause cause = BadBlockCause.fromValidationFailure("fail"); + badBlockManager.addBadHeader(block.getHeader(), cause); + + // Check event was emitted + assertThat(badBlockResult.get()).isEqualTo(block.getHeader()); + assertThat(badBlockCauseResult.get()).isEqualTo(cause); + } + + @Test + public void unsubscribeFromBadBlocks_listenerReceivesNoEvents() { + + final AtomicInteger eventCount = new AtomicInteger(0); + final long subscribeId = + badBlockManager.subscribeToBadBlocks( + (new BadBlockListener() { + @Override + public void onBadBlockAdded( + final org.hyperledger.besu.plugin.data.Block badBlock, + final org.hyperledger.besu.plugin.data.BadBlockCause cause) { + eventCount.incrementAndGet(); + } + + @Override + public void onBadBlockHeaderAdded( + final BlockHeader badBlockHeader, + final org.hyperledger.besu.plugin.data.BadBlockCause cause) { + eventCount.incrementAndGet(); + } + })); + badBlockManager.unsubscribeFromBadBlocks(subscribeId); + + final BadBlockCause cause = BadBlockCause.fromValidationFailure("fail"); + badBlockManager.addBadBlock(block, cause); + badBlockManager.addBadHeader(block2.getHeader(), cause); + + // Check no events fired + assertThat(eventCount.get()).isEqualTo(0); + } } diff --git a/plugin-api/build.gradle b/plugin-api/build.gradle index 423411b5630..1735a9ce601 100644 --- a/plugin-api/build.gradle +++ b/plugin-api/build.gradle @@ -69,7 +69,7 @@ Calculated : ${currentHash} tasks.register('checkAPIChanges', FileStateChecker) { description = "Checks that the API for the Plugin-API project does not change without deliberate thought" files = sourceSets.main.allJava.files - knownHash = 'YH+8rbilrhatRAh8rK8/36qxwrqkybBaaNeg+AkZ0c4=' + knownHash = 'nUyHUxLGdydlFf1JtQMD++5o6S52ybF8Y0VlmebsA6g=' } check.dependsOn('checkAPIChanges') diff --git a/plugin-api/src/main/java/org/hyperledger/besu/plugin/data/BadBlockCause.java b/plugin-api/src/main/java/org/hyperledger/besu/plugin/data/BadBlockCause.java new file mode 100644 index 00000000000..66c813b92a6 --- /dev/null +++ b/plugin-api/src/main/java/org/hyperledger/besu/plugin/data/BadBlockCause.java @@ -0,0 +1,42 @@ +/* + * Copyright Hyperledger Besu Contributors. + * + * 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.plugin.data; + +/** Represents the reason a block is marked as "bad" */ +public interface BadBlockCause { + + /** + * The reason why the block was categorized as bad + * + * @return The reason enum + */ + BadBlockReason getReason(); + + /** + * A more descriptive explanation for why the block was marked bad + * + * @return the description + */ + String getDescription(); + + /** An enum representing the reason why a block is marked bad */ + enum BadBlockReason { + /** Standard spec-related validation failures */ + SPEC_VALIDATION_FAILURE, + /** This block is bad because it descends from a bad block */ + DESCENDS_FROM_BAD_BLOCK, + } +} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/BadBlockReason.java b/plugin-api/src/main/java/org/hyperledger/besu/plugin/data/Block.java similarity index 68% rename from ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/BadBlockReason.java rename to plugin-api/src/main/java/org/hyperledger/besu/plugin/data/Block.java index f38655cb830..c8372fd394f 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/BadBlockReason.java +++ b/plugin-api/src/main/java/org/hyperledger/besu/plugin/data/Block.java @@ -11,12 +11,24 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 + * */ -package org.hyperledger.besu.ethereum.chain; +package org.hyperledger.besu.plugin.data; + +/** A Block on the network */ +public interface Block { + + /** + * The block's header + * + * @return The block header + */ + BlockHeader getHeader(); -public enum BadBlockReason { - // Standard spec-related validation failures - SPEC_VALIDATION_FAILURE, - // This block is bad because it descends from a bad block - DESCENDS_FROM_BAD_BLOCK, + /** + * The block's body + * + * @return The block body + */ + BlockBody getBody(); } diff --git a/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/BesuEvents.java b/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/BesuEvents.java index 9d5b6394511..37ec10434b8 100644 --- a/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/BesuEvents.java +++ b/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/BesuEvents.java @@ -17,6 +17,9 @@ import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.datatypes.Transaction; import org.hyperledger.besu.plugin.data.AddedBlockContext; +import org.hyperledger.besu.plugin.data.BadBlockCause; +import org.hyperledger.besu.plugin.data.Block; +import org.hyperledger.besu.plugin.data.BlockHeader; import org.hyperledger.besu.plugin.data.LogWithMetadata; import org.hyperledger.besu.plugin.data.PropagatedBlockContext; import org.hyperledger.besu.plugin.data.SyncStatus; @@ -156,6 +159,22 @@ public interface BesuEvents extends BesuService { */ void removeLogListener(long listenerIdentifier); + /** + * Add listener to track bad blocks. These are intrinsically bad blocks that have failed + * validation or descend from a bad block that has failed validation. + * + * @param listener The listener that will receive bad block events. + * @return The id of the listener to be used to remove the listener. + */ + long addBadBlockListener(BadBlockListener listener); + + /** + * Remove the bad block listener with the associated id. + * + * @param listenerIdentifier The id of the listener that was returned from addBadBlockListener. + */ + void removeBadBlockListener(long listenerIdentifier); + /** The listener interface for receiving new block propagated events. */ interface BlockPropagatedListener { @@ -259,4 +278,24 @@ interface InitialSyncCompletionListener { /** Emitted when initial sync restarts */ void onInitialSyncRestart(); } + + /** The interface defining bad block listeners */ + interface BadBlockListener { + + /** + * Fires when a bad block is encountered + * + * @param badBlock The bad block + * @param cause The reason the block was marked bad + */ + void onBadBlockAdded(Block badBlock, BadBlockCause cause); + + /** + * Fires when a bad block's header is encountered + * + * @param badBlockHeader The bad block's header + * @param cause The reason why the header was marked bad + */ + void onBadBlockHeaderAdded(BlockHeader badBlockHeader, BadBlockCause cause); + } } From 949ad7d181cd15ea3c98a8d2bb68f3ed80787482 Mon Sep 17 00:00:00 2001 From: mbaxter Date: Fri, 29 Mar 2024 16:03:32 -0400 Subject: [PATCH 2/5] Simplify bad block listener interface Signed-off-by: mbaxter --- .../besu/services/BesuEventsImplTest.java | 84 +++++++------------ .../besu/ethereum/chain/BadBlockManager.java | 4 +- .../hyperledger/besu/ethereum/core/Block.java | 4 +- .../ethereum/chain/BadBlockManagerTest.java | 63 +++----------- plugin-api/build.gradle | 2 +- .../hyperledger/besu/plugin/data/Block.java | 34 -------- .../besu/plugin/services/BesuEvents.java | 15 +--- 7 files changed, 50 insertions(+), 156 deletions(-) delete mode 100644 plugin-api/src/main/java/org/hyperledger/besu/plugin/data/Block.java diff --git a/besu/src/test/java/org/hyperledger/besu/services/BesuEventsImplTest.java b/besu/src/test/java/org/hyperledger/besu/services/BesuEventsImplTest.java index 9c0457b5a51..97dd8d7ee3e 100644 --- a/besu/src/test/java/org/hyperledger/besu/services/BesuEventsImplTest.java +++ b/besu/src/test/java/org/hyperledger/besu/services/BesuEventsImplTest.java @@ -62,11 +62,9 @@ import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive; import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; import org.hyperledger.besu.plugin.data.AddedBlockContext; -import org.hyperledger.besu.plugin.data.BlockHeader; import org.hyperledger.besu.plugin.data.LogWithMetadata; import org.hyperledger.besu.plugin.data.PropagatedBlockContext; import org.hyperledger.besu.plugin.data.SyncStatus; -import org.hyperledger.besu.plugin.services.BesuEvents.BadBlockListener; import org.hyperledger.besu.services.kvstore.InMemoryKeyValueStorage; import org.hyperledger.besu.testutil.DeterministicEthScheduler; import org.hyperledger.besu.testutil.TestClock; @@ -512,32 +510,17 @@ public void logEventDoesNotFireAfterUnsubscribe() { } @Test - public void badBlockEventFiresAfterSubscribe() { + public void badBlockEventFiresAfterSubscribe_badBlockAdded() { // Track bad block events - final AtomicReference badBlockResult = + final AtomicReference badBlockResult = new AtomicReference<>(); final AtomicReference badBlockCauseResult = new AtomicReference<>(); - final AtomicReference badBlockHeaderResult = new AtomicReference<>(); - final AtomicReference - badBlockHeaderCauseResult = new AtomicReference<>(); + serviceImpl.addBadBlockListener( - new BadBlockListener() { - @Override - public void onBadBlockAdded( - final org.hyperledger.besu.plugin.data.Block badBlock, - final org.hyperledger.besu.plugin.data.BadBlockCause cause) { - badBlockResult.set(badBlock); - badBlockCauseResult.set(cause); - } - - @Override - public void onBadBlockHeaderAdded( - final BlockHeader badBlockHeader, - final org.hyperledger.besu.plugin.data.BadBlockCause cause) { - badBlockHeaderResult.set(badBlockHeader); - badBlockHeaderCauseResult.set(cause); - } + (badBlock, cause) -> { + badBlockResult.set(badBlock); + badBlockCauseResult.set(cause); }); // Add bad block @@ -548,46 +531,45 @@ public void onBadBlockHeaderAdded( // Check we caught the bad block assertThat(badBlockResult.get()).isEqualTo(block); assertThat(badBlockCauseResult.get()).isEqualTo(blockCause); - assertThat(badBlockHeaderResult.get()).isNull(); + } + + @Test + public void badBlockEventFiresAfterSubscribe_badBlockHeaderAdded() { + // Track bad block events + final AtomicReference badBlockResult = + new AtomicReference<>(); + final AtomicReference badBlockCauseResult = + new AtomicReference<>(); + + serviceImpl.addBadBlockListener( + (badBlock, cause) -> { + badBlockResult.set(badBlock); + badBlockCauseResult.set(cause); + }); // Add bad block header - final BadBlockCause headerCause = BadBlockCause.fromValidationFailure("oops"); - final Block headerBlock = gen.block(new BlockDataGenerator.BlockOptions()); - badBlockManager.addBadHeader(headerBlock.getHeader(), headerCause); + final BadBlockCause cause = BadBlockCause.fromValidationFailure("oops"); + final Block badBlock = gen.block(new BlockDataGenerator.BlockOptions()); + badBlockManager.addBadHeader(badBlock.getHeader(), cause); - // Check we caught the bad block header - assertThat(badBlockHeaderResult.get()).isEqualTo(headerBlock.getHeader()); - assertThat(badBlockHeaderCauseResult.get()).isEqualTo(headerCause); + // Check we caught the bad block + assertThat(badBlockResult.get()).isEqualTo(badBlock.getHeader()); + assertThat(badBlockCauseResult.get()).isEqualTo(cause); } @Test public void badBlockEventDoesNotFireAfterUnsubscribe() { // Track bad block events - final AtomicReference badBlockResult = + final AtomicReference badBlockResult = new AtomicReference<>(); final AtomicReference badBlockCauseResult = new AtomicReference<>(); - final AtomicReference badBlockHeaderResult = new AtomicReference<>(); - final AtomicReference - badBlockHeaderCauseResult = new AtomicReference<>(); + final long listenerId = serviceImpl.addBadBlockListener( - new BadBlockListener() { - @Override - public void onBadBlockAdded( - final org.hyperledger.besu.plugin.data.Block badBlock, - final org.hyperledger.besu.plugin.data.BadBlockCause cause) { - badBlockResult.set(badBlock); - badBlockCauseResult.set(cause); - } - - @Override - public void onBadBlockHeaderAdded( - final BlockHeader badBlockHeader, - final org.hyperledger.besu.plugin.data.BadBlockCause cause) { - badBlockHeaderResult.set(badBlockHeader); - badBlockHeaderCauseResult.set(cause); - } + (badBlock, cause) -> { + badBlockResult.set(badBlock); + badBlockCauseResult.set(cause); }); // Unsubscribe serviceImpl.removeBadBlockListener(listenerId); @@ -604,8 +586,6 @@ public void onBadBlockHeaderAdded( // Check we did not process any events assertThat(badBlockResult.get()).isNull(); assertThat(badBlockCauseResult.get()).isNull(); - assertThat(badBlockHeaderResult.get()).isNull(); - assertThat(badBlockHeaderCauseResult.get()).isNull(); } private Block generateBlock() { diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/BadBlockManager.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/BadBlockManager.java index 0f56e42df95..211d28c3428 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/BadBlockManager.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/BadBlockManager.java @@ -50,7 +50,7 @@ public class BadBlockManager { public void addBadBlock(final Block badBlock, final BadBlockCause cause) { LOG.debug("Register bad block {} with cause: {}", badBlock.toLogString(), cause); this.badBlocks.put(badBlock.getHash(), badBlock); - badBlockSubscribers.forEach(s -> s.onBadBlockAdded(badBlock, cause)); + badBlockSubscribers.forEach(s -> s.onBadBlockAdded(badBlock.getHeader(), cause)); } public void reset() { @@ -86,7 +86,7 @@ public Optional getBadBlock(final Hash hash) { public void addBadHeader(final BlockHeader header, final BadBlockCause cause) { LOG.debug("Register bad block header {} with cause: {}", header.toLogString(), cause); badHeaders.put(header.getHash(), header); - badBlockSubscribers.forEach(s -> s.onBadBlockHeaderAdded(header, cause)); + badBlockSubscribers.forEach(s -> s.onBadBlockAdded(header, cause)); } public boolean isBadBlock(final Hash blockHash) { diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/Block.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/Block.java index 69829bb8879..32313820cc8 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/Block.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/Block.java @@ -25,7 +25,7 @@ import org.apache.tuweni.bytes.Bytes; -public class Block implements org.hyperledger.besu.plugin.data.Block { +public class Block { private final BlockHeader header; private final BlockBody body; @@ -35,12 +35,10 @@ public Block(final BlockHeader header, final BlockBody body) { this.body = body; } - @Override public BlockHeader getHeader() { return header; } - @Override public BlockBody getBody() { return body; } diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/chain/BadBlockManagerTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/chain/BadBlockManagerTest.java index 41fa5ac5134..b4a73eb5b17 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/chain/BadBlockManagerTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/chain/BadBlockManagerTest.java @@ -19,8 +19,6 @@ import org.hyperledger.besu.ethereum.core.Block; import org.hyperledger.besu.ethereum.core.BlockchainSetupUtil; -import org.hyperledger.besu.plugin.data.BlockHeader; -import org.hyperledger.besu.plugin.services.BesuEvents.BadBlockListener; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; @@ -76,34 +74,22 @@ public void isBadBlock_trueForBadBlock() { @Test public void subscribeToBadBlocks_listenerReceivesBadBlockEvent() { - final AtomicReference badBlockResult = + final AtomicReference badBlockResult = new AtomicReference<>(); final AtomicReference badBlockCauseResult = new AtomicReference<>(); badBlockManager.subscribeToBadBlocks( - (new BadBlockListener() { - @Override - public void onBadBlockAdded( - final org.hyperledger.besu.plugin.data.Block badBlock, - final org.hyperledger.besu.plugin.data.BadBlockCause cause) { - badBlockResult.set(badBlock); - badBlockCauseResult.set(cause); - } - - @Override - public void onBadBlockHeaderAdded( - final BlockHeader badBlockHeader, - final org.hyperledger.besu.plugin.data.BadBlockCause cause) { - // Noop - } - })); + (badBlock, cause) -> { + badBlockResult.set(badBlock); + badBlockCauseResult.set(cause); + }); final BadBlockCause cause = BadBlockCause.fromValidationFailure("fail"); badBlockManager.addBadBlock(block, cause); // Check event was emitted - assertThat(badBlockResult.get()).isEqualTo(block); + assertThat(badBlockResult.get()).isEqualTo(block.getHeader()); assertThat(badBlockCauseResult.get()).isEqualTo(cause); } @@ -116,22 +102,10 @@ public void subscribeToBadBlocks_listenerReceivesBadHeaderEvent() { new AtomicReference<>(); badBlockManager.subscribeToBadBlocks( - (new BadBlockListener() { - @Override - public void onBadBlockAdded( - final org.hyperledger.besu.plugin.data.Block badBlock, - final org.hyperledger.besu.plugin.data.BadBlockCause cause) { - // Noop - } - - @Override - public void onBadBlockHeaderAdded( - final BlockHeader badBlockHeader, - final org.hyperledger.besu.plugin.data.BadBlockCause cause) { - badBlockResult.set(badBlockHeader); - badBlockCauseResult.set(cause); - } - })); + (badBlock, cause) -> { + badBlockResult.set(badBlock); + badBlockCauseResult.set(cause); + }); final BadBlockCause cause = BadBlockCause.fromValidationFailure("fail"); badBlockManager.addBadHeader(block.getHeader(), cause); @@ -146,22 +120,7 @@ public void unsubscribeFromBadBlocks_listenerReceivesNoEvents() { final AtomicInteger eventCount = new AtomicInteger(0); final long subscribeId = - badBlockManager.subscribeToBadBlocks( - (new BadBlockListener() { - @Override - public void onBadBlockAdded( - final org.hyperledger.besu.plugin.data.Block badBlock, - final org.hyperledger.besu.plugin.data.BadBlockCause cause) { - eventCount.incrementAndGet(); - } - - @Override - public void onBadBlockHeaderAdded( - final BlockHeader badBlockHeader, - final org.hyperledger.besu.plugin.data.BadBlockCause cause) { - eventCount.incrementAndGet(); - } - })); + badBlockManager.subscribeToBadBlocks((block, cause) -> eventCount.incrementAndGet()); badBlockManager.unsubscribeFromBadBlocks(subscribeId); final BadBlockCause cause = BadBlockCause.fromValidationFailure("fail"); diff --git a/plugin-api/build.gradle b/plugin-api/build.gradle index 1735a9ce601..5f240fa9ffb 100644 --- a/plugin-api/build.gradle +++ b/plugin-api/build.gradle @@ -69,7 +69,7 @@ Calculated : ${currentHash} tasks.register('checkAPIChanges', FileStateChecker) { description = "Checks that the API for the Plugin-API project does not change without deliberate thought" files = sourceSets.main.allJava.files - knownHash = 'nUyHUxLGdydlFf1JtQMD++5o6S52ybF8Y0VlmebsA6g=' + knownHash = 'B/25sm3vIvAYNclkWu1GysVCvOTa49s1fyvTJWbIqGc=' } check.dependsOn('checkAPIChanges') diff --git a/plugin-api/src/main/java/org/hyperledger/besu/plugin/data/Block.java b/plugin-api/src/main/java/org/hyperledger/besu/plugin/data/Block.java deleted file mode 100644 index c8372fd394f..00000000000 --- a/plugin-api/src/main/java/org/hyperledger/besu/plugin/data/Block.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright Hyperledger Besu Contributors. - * - * 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.plugin.data; - -/** A Block on the network */ -public interface Block { - - /** - * The block's header - * - * @return The block header - */ - BlockHeader getHeader(); - - /** - * The block's body - * - * @return The block body - */ - BlockBody getBody(); -} diff --git a/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/BesuEvents.java b/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/BesuEvents.java index 37ec10434b8..911eff3b593 100644 --- a/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/BesuEvents.java +++ b/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/BesuEvents.java @@ -18,7 +18,6 @@ import org.hyperledger.besu.datatypes.Transaction; import org.hyperledger.besu.plugin.data.AddedBlockContext; import org.hyperledger.besu.plugin.data.BadBlockCause; -import org.hyperledger.besu.plugin.data.Block; import org.hyperledger.besu.plugin.data.BlockHeader; import org.hyperledger.besu.plugin.data.LogWithMetadata; import org.hyperledger.besu.plugin.data.PropagatedBlockContext; @@ -283,19 +282,11 @@ interface InitialSyncCompletionListener { interface BadBlockListener { /** - * Fires when a bad block is encountered - * - * @param badBlock The bad block - * @param cause The reason the block was marked bad - */ - void onBadBlockAdded(Block badBlock, BadBlockCause cause); - - /** - * Fires when a bad block's header is encountered + * Fires when a bad block is encountered on the network * * @param badBlockHeader The bad block's header - * @param cause The reason why the header was marked bad + * @param cause The reason why the block was marked bad */ - void onBadBlockHeaderAdded(BlockHeader badBlockHeader, BadBlockCause cause); + void onBadBlockAdded(BlockHeader badBlockHeader, BadBlockCause cause); } } From ef79f78cfaaadcb23c8091ac457eeb1d1ba7f8b8 Mon Sep 17 00:00:00 2001 From: mbaxter Date: Fri, 29 Mar 2024 16:14:19 -0400 Subject: [PATCH 3/5] Add changelog entry Signed-off-by: mbaxter --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1d7730f0e3c..a0bebc9367d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,6 +31,7 @@ - Dedicated log marker for invalid txs removed from the txpool [#6826](https://github.com/hyperledger/besu/pull/6826) - Prevent startup with BONSAI and privacy enabled [#6809](https://github.com/hyperledger/besu/pull/6809) - Remove deprecated Forest pruning [#6810](https://github.com/hyperledger/besu/pull/6810) +- Expose bad block events via the BesuEvents plugin API [#6848](https://github.com/hyperledger/besu/pull/6848) ### Bug fixes - Fix txpool dump/restore race condition [#6665](https://github.com/hyperledger/besu/pull/6665) From 1a00548586256316b771451c540e0e26b7add3ab Mon Sep 17 00:00:00 2001 From: mbaxter Date: Fri, 29 Mar 2024 16:36:58 -0400 Subject: [PATCH 4/5] Fix test Signed-off-by: mbaxter --- .../java/org/hyperledger/besu/services/BesuEventsImplTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/besu/src/test/java/org/hyperledger/besu/services/BesuEventsImplTest.java b/besu/src/test/java/org/hyperledger/besu/services/BesuEventsImplTest.java index 97dd8d7ee3e..3673e78e312 100644 --- a/besu/src/test/java/org/hyperledger/besu/services/BesuEventsImplTest.java +++ b/besu/src/test/java/org/hyperledger/besu/services/BesuEventsImplTest.java @@ -529,7 +529,7 @@ public void badBlockEventFiresAfterSubscribe_badBlockAdded() { badBlockManager.addBadBlock(block, blockCause); // Check we caught the bad block - assertThat(badBlockResult.get()).isEqualTo(block); + assertThat(badBlockResult.get()).isEqualTo(block.getHeader()); assertThat(badBlockCauseResult.get()).isEqualTo(blockCause); } From 50641fec0fdbf1e72f3c8da8f5f543ce8d0227e3 Mon Sep 17 00:00:00 2001 From: mbaxter Date: Tue, 9 Apr 2024 12:06:07 -0400 Subject: [PATCH 5/5] Fix reftest build error Signed-off-by: mbaxter --- ethereum/referencetests/src/reference-test/external-resources | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ethereum/referencetests/src/reference-test/external-resources b/ethereum/referencetests/src/reference-test/external-resources index 853b1e03b10..52ddcbcef0d 160000 --- a/ethereum/referencetests/src/reference-test/external-resources +++ b/ethereum/referencetests/src/reference-test/external-resources @@ -1 +1 @@ -Subproject commit 853b1e03b1078d370614002851ba1ee9803d9fcf +Subproject commit 52ddcbcef0d58ec7de6b768b564725391a30b934