[Issue-6301] Add bad block events (#6848)

Signed-off-by: mbaxter <mbaxter.dev@gmail.com>
This commit is contained in:
mbaxter
2024-04-09 12:55:26 -04:00
committed by GitHub
parent b6a26c422e
commit 66176c0619
12 changed files with 261 additions and 32 deletions

View File

@@ -38,6 +38,7 @@
- Add `tx-pool-blob-price-bump` option to configure the price bump percentage required to replace blob transactions (by default 100%) [#6874](https://github.com/hyperledger/besu/pull/6874)
- Log detailed timing of block creation steps [#6880](https://github.com/hyperledger/besu/pull/6880)
- Expose transaction count by type metrics for the layered txpool [#6903](https://github.com/hyperledger/besu/pull/6903)
- 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)

View File

@@ -313,7 +313,8 @@ public class ThreadBesuNodeRunner implements BesuNodeRunner {
besuController.getProtocolContext().getBlockchain(),
besuController.getProtocolManager().getBlockBroadcaster(),
besuController.getTransactionPool(),
besuController.getSyncState()));
besuController.getSyncState(),
besuController.getProtocolContext().getBadBlockManager()));
besuPluginContext.startPlugins();
runner.startEthereumMainLoop();

View File

@@ -1293,7 +1293,8 @@ public class BesuCommand implements DefaultCommandValues, Runnable {
besuController.getProtocolContext().getBlockchain(),
besuController.getProtocolManager().getBlockBroadcaster(),
besuController.getTransactionPool(),
besuController.getSyncState()));
besuController.getSyncState(),
besuController.getProtocolContext().getBadBlockManager()));
besuPluginContext.addService(MetricsSystem.class, getMetricsSystem());
besuPluginContext.addService(

View File

@@ -18,6 +18,7 @@ import static java.util.stream.Collectors.toUnmodifiableList;
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 class BesuEventsImpl implements BesuEvents {
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<BlockHeader> blockHeaderSupplier,
final Supplier<BlockBody> blockBodySupplier,

View File

@@ -27,6 +27,8 @@ import org.hyperledger.besu.crypto.SignatureAlgorithmFactory;
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;
@@ -113,6 +115,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 +174,9 @@ public class BesuEventsImplTest {
new BlobCache(),
MiningParameters.newDefault());
serviceImpl = new BesuEventsImpl(blockchain, blockBroadcaster, transactionPool, syncState);
serviceImpl =
new BesuEventsImpl(
blockchain, blockBroadcaster, transactionPool, syncState, badBlockManager);
}
@Test
@@ -504,6 +509,85 @@ public class BesuEventsImplTest {
assertThat(result).isEmpty();
}
@Test
public void badBlockEventFiresAfterSubscribe_badBlockAdded() {
// Track bad block events
final AtomicReference<org.hyperledger.besu.plugin.data.BlockHeader> badBlockResult =
new AtomicReference<>();
final AtomicReference<org.hyperledger.besu.plugin.data.BadBlockCause> badBlockCauseResult =
new AtomicReference<>();
serviceImpl.addBadBlockListener(
(badBlock, cause) -> {
badBlockResult.set(badBlock);
badBlockCauseResult.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.getHeader());
assertThat(badBlockCauseResult.get()).isEqualTo(blockCause);
}
@Test
public void badBlockEventFiresAfterSubscribe_badBlockHeaderAdded() {
// Track bad block events
final AtomicReference<org.hyperledger.besu.plugin.data.BlockHeader> badBlockResult =
new AtomicReference<>();
final AtomicReference<org.hyperledger.besu.plugin.data.BadBlockCause> badBlockCauseResult =
new AtomicReference<>();
serviceImpl.addBadBlockListener(
(badBlock, cause) -> {
badBlockResult.set(badBlock);
badBlockCauseResult.set(cause);
});
// Add bad block header
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
assertThat(badBlockResult.get()).isEqualTo(badBlock.getHeader());
assertThat(badBlockCauseResult.get()).isEqualTo(cause);
}
@Test
public void badBlockEventDoesNotFireAfterUnsubscribe() {
// Track bad block events
final AtomicReference<org.hyperledger.besu.plugin.data.BlockHeader> badBlockResult =
new AtomicReference<>();
final AtomicReference<org.hyperledger.besu.plugin.data.BadBlockCause> badBlockCauseResult =
new AtomicReference<>();
final long listenerId =
serviceImpl.addBadBlockListener(
(badBlock, cause) -> {
badBlockResult.set(badBlock);
badBlockCauseResult.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();
}
private Block generateBlock() {
final BlockBody body = new BlockBody(Collections.emptyList(), Collections.emptyList());
return new Block(new BlockHeaderTestFixture().buildHeader(), body);

View File

@@ -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 @@ public class BadBlockCause {
this.description = description;
}
@Override
public BadBlockReason getReason() {
return reason;
}
@Override
public String getDescription() {
return description;
}

View File

@@ -17,6 +17,8 @@ package org.hyperledger.besu.ethereum.chain;
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<Hash, Hash> latestValidHashes =
CacheBuilder.newBuilder().maximumSize(MAX_BAD_BLOCKS_SIZE).concurrencyLevel(1).build();
private final Subscribers<BadBlockListener> 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.getHeader(), cause));
}
public void reset() {
@@ -81,9 +84,9 @@ public class BadBlockManager {
}
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.onBadBlockAdded(header, cause));
}
public boolean isBadBlock(final Hash blockHash) {
@@ -97,4 +100,12 @@ public class BadBlockManager {
public Optional<Hash> 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);
}
}

View File

@@ -1,22 +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.ethereum.chain;
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,
}

View File

@@ -20,12 +20,16 @@ import static org.assertj.core.api.Assertions.assertThat;
import org.hyperledger.besu.ethereum.core.Block;
import org.hyperledger.besu.ethereum.core.BlockchainSetupUtil;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import org.junit.jupiter.api.Test;
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 +70,64 @@ public class BadBlockManagerTest {
assertThat(badBlockManager.isBadBlock(block.getHash())).isTrue();
}
@Test
public void subscribeToBadBlocks_listenerReceivesBadBlockEvent() {
final AtomicReference<org.hyperledger.besu.plugin.data.BlockHeader> badBlockResult =
new AtomicReference<>();
final AtomicReference<org.hyperledger.besu.plugin.data.BadBlockCause> badBlockCauseResult =
new AtomicReference<>();
badBlockManager.subscribeToBadBlocks(
(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.getHeader());
assertThat(badBlockCauseResult.get()).isEqualTo(cause);
}
@Test
public void subscribeToBadBlocks_listenerReceivesBadHeaderEvent() {
final AtomicReference<org.hyperledger.besu.plugin.data.BlockHeader> badBlockResult =
new AtomicReference<>();
final AtomicReference<org.hyperledger.besu.plugin.data.BadBlockCause> badBlockCauseResult =
new AtomicReference<>();
badBlockManager.subscribeToBadBlocks(
(badBlock, cause) -> {
badBlockResult.set(badBlock);
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((block, 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);
}
}

View File

@@ -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 = 'lsBecdCyK9rIi5FIjURF2uPwKzXgqHCayMcLyOOl4fE='
knownHash = '0mJiCGsToqx5aAJEvwnT3V0R8o4PXBYWiB0wT6CMpuo='
}
check.dependsOn('checkAPIChanges')

View File

@@ -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,
}
}

View File

@@ -17,6 +17,8 @@ package org.hyperledger.besu.plugin.services;
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.BlockHeader;
import org.hyperledger.besu.plugin.data.LogWithMetadata;
import org.hyperledger.besu.plugin.data.PropagatedBlockContext;
import org.hyperledger.besu.plugin.data.SyncStatus;
@@ -156,6 +158,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 +277,16 @@ public interface BesuEvents extends BesuService {
/** Emitted when initial sync restarts */
void onInitialSyncRestart();
}
/** The interface defining bad block listeners */
interface BadBlockListener {
/**
* Fires when a bad block is encountered on the network
*
* @param badBlockHeader The bad block's header
* @param cause The reason why the block was marked bad
*/
void onBadBlockAdded(BlockHeader badBlockHeader, BadBlockCause cause);
}
}