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 @@ -36,6 +36,7 @@
- Upgrade spring-security-crypto to address CVE-2025-22228 [#8448](https://github.com/hyperledger/besu/pull/8448)
- Fix restoring txpool from disk when blob transactions are present [#8481](https://github.com/hyperledger/besu/pull/8481)
- Fix for bonsai db inconsistency on abnormal shutdown [#8500](https://github.com/hyperledger/besu/pull/8500)
- Fix to add stateroot mismatches to bad block manager [#8207](https://github.com/hyperledger/besu/pull/8207)

## 25.3.0

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
package org.hyperledger.besu.ethereum;

import org.hyperledger.besu.ethereum.chain.BadBlockCause;
import org.hyperledger.besu.ethereum.chain.BadBlockManager;
import org.hyperledger.besu.ethereum.chain.MutableBlockchain;
import org.hyperledger.besu.ethereum.core.Block;
import org.hyperledger.besu.ethereum.core.BlockHeader;
Expand Down Expand Up @@ -56,27 +55,21 @@ public class MainnetBlockValidator implements BlockValidator {
/** The BlockProcessor used to process blocks. */
protected final BlockProcessor blockProcessor;

/** The BadBlockManager used to manage bad blocks. */
protected final BadBlockManager badBlockManager;

/**
* Constructs a new MainnetBlockValidator with the given BlockHeaderValidator, BlockBodyValidator,
* BlockProcessor, and BadBlockManager.
*
* @param blockHeaderValidator the BlockHeaderValidator used to validate block headers
* @param blockBodyValidator the BlockBodyValidator used to validate block bodies
* @param blockProcessor the BlockProcessor used to process blocks
* @param badBlockManager the BadBlockManager used to manage bad blocks
*/
public MainnetBlockValidator(
final BlockHeaderValidator blockHeaderValidator,
final BlockBodyValidator blockBodyValidator,
final BlockProcessor blockProcessor,
final BadBlockManager badBlockManager) {
final BlockProcessor blockProcessor) {
this.blockHeaderValidator = blockHeaderValidator;
this.blockBodyValidator = blockBodyValidator;
this.blockProcessor = blockProcessor;
this.badBlockManager = badBlockManager;
}

/**
Expand Down Expand Up @@ -131,7 +124,7 @@ public BlockProcessingResult validateAndProcessBlock(
new BlockProcessingResult(
"Parent block with hash " + header.getParentHash() + " not present");
// Blocks should not be marked bad due to missing data
handleFailedBlockProcessing(block, retval, false);
handleFailedBlockProcessing(block, retval, false, context);
return retval;
}
parentHeader = maybeParentHeader.get();
Expand All @@ -140,13 +133,13 @@ public BlockProcessingResult validateAndProcessBlock(
header, parentHeader, context, headerValidationMode)) {
final String error = String.format("Header validation failed (%s)", headerValidationMode);
var retval = new BlockProcessingResult(error);
handleFailedBlockProcessing(block, retval, shouldRecordBadBlock);
handleFailedBlockProcessing(block, retval, shouldRecordBadBlock, context);
return retval;
}
} catch (StorageException ex) {
var retval = new BlockProcessingResult(Optional.empty(), ex);
// Blocks should not be marked bad due to a local storage failure
handleFailedBlockProcessing(block, retval, false);
handleFailedBlockProcessing(block, retval, false, context);
return retval;
}

Expand All @@ -165,12 +158,12 @@ public BlockProcessingResult validateAndProcessBlock(
+ parentHeader.getStateRoot()
+ " is not available");
// Blocks should not be marked bad due to missing data
handleFailedBlockProcessing(block, retval, false);
handleFailedBlockProcessing(block, retval, false, context);
return retval;
}
var result = processBlock(context, worldState, block);
if (result.isFailed()) {
handleFailedBlockProcessing(block, result, shouldRecordBadBlock);
handleFailedBlockProcessing(block, result, shouldRecordBadBlock, context);
return result;
} else {
List<TransactionReceipt> receipts =
Expand All @@ -185,7 +178,7 @@ public BlockProcessingResult validateAndProcessBlock(
ommerValidationMode,
BodyValidationMode.FULL)) {
result = new BlockProcessingResult("failed to validate output of imported block");
handleFailedBlockProcessing(block, result, shouldRecordBadBlock);
handleFailedBlockProcessing(block, result, shouldRecordBadBlock, context);
return result;
}

Expand All @@ -199,7 +192,7 @@ public BlockProcessingResult validateAndProcessBlock(
} catch (StorageException ex) {
var retval = new BlockProcessingResult(Optional.empty(), ex);
// Do not record bad block due to a local storage issue
handleFailedBlockProcessing(block, retval, false);
handleFailedBlockProcessing(block, retval, false, context);
return retval;
} catch (Exception ex) {
// Wrap checked autocloseable exception from try-with-resources
Expand All @@ -210,7 +203,8 @@ public BlockProcessingResult validateAndProcessBlock(
private void handleFailedBlockProcessing(
final Block failedBlock,
final BlockValidationResult result,
final boolean shouldRecordBadBlock) {
final boolean shouldRecordBadBlock,
final ProtocolContext context) {
if (result.causedBy().isPresent()) {
// Block processing failed exceptionally, we cannot assume the block was intrinsically invalid
LOG.info(
Expand All @@ -230,7 +224,7 @@ private void handleFailedBlockProcessing(
// Result.errorMessage should not be empty on failure, but add a default to be safe
String description = result.errorMessage.orElse("Unknown cause");
final BadBlockCause cause = BadBlockCause.fromValidationFailure(description);
badBlockManager.addBadBlock(failedBlock, cause);
context.getBadBlockManager().addBadBlock(failedBlock, cause);
} else {
LOG.debug("Invalid block {} not added to badBlockManager ", failedBlock.toLogString());
}
Expand Down Expand Up @@ -268,7 +262,9 @@ public boolean validateBlockForSyncing(
final BlockHeader header = block.getHeader();
if (!blockHeaderValidator.validateHeader(header, context, headerValidationMode)) {
String description = String.format("Failed header validation (%s)", headerValidationMode);
badBlockManager.addBadBlock(block, BadBlockCause.fromValidationFailure(description));
context
.getBadBlockManager()
.addBadBlock(block, BadBlockCause.fromValidationFailure(description));
return false;
}

Expand All @@ -277,8 +273,10 @@ public boolean validateBlockForSyncing(
}

if (!blockBodyValidator.validateBodyLight(context, block, receipts, ommerValidationMode)) {
badBlockManager.addBadBlock(
block, BadBlockCause.fromValidationFailure("Failed body validation (light)"));
context
.getBadBlockManager()
.addBadBlock(
block, BadBlockCause.fromValidationFailure("Failed body validation (light)"));
return false;
}
return true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
import org.hyperledger.besu.ethereum.privacy.storage.PrivateMetadataUpdater;
import org.hyperledger.besu.ethereum.processing.TransactionProcessingResult;
import org.hyperledger.besu.ethereum.trie.MerkleTrieException;
import org.hyperledger.besu.ethereum.trie.common.StateRootMismatchException;
import org.hyperledger.besu.ethereum.trie.pathbased.bonsai.worldview.BonsaiWorldState;
import org.hyperledger.besu.ethereum.trie.pathbased.bonsai.worldview.BonsaiWorldStateUpdateAccumulator;
import org.hyperledger.besu.evm.blockhash.BlockHashLookup;
Expand Down Expand Up @@ -283,6 +284,12 @@ protected BlockProcessingResult processBlock(
((BonsaiWorldStateUpdateAccumulator) worldState.updater()).reset();
}
throw e;
} catch (StateRootMismatchException ex) {
LOG.error(
"failed persisting block due to stateroot mismatch; expected {}, actual {}",
ex.getExpectedRoot().toHexString(),
ex.getActualRoot().toHexString());
return new BlockProcessingResult(Optional.empty(), ex.getMessage());
} catch (Exception e) {
LOG.error("failed persisting block", e);
return new BlockProcessingResult(Optional.empty(), e);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -376,8 +376,7 @@ public ProtocolSpec build(final ProtocolSchedule protocolSchedule) {
}

final BlockValidator blockValidator =
blockValidatorBuilder.apply(
blockHeaderValidator, blockBodyValidator, blockProcessor, badBlockManager);
blockValidatorBuilder.apply(blockHeaderValidator, blockBodyValidator, blockProcessor);
final BlockImporter blockImporter = blockImporterBuilder.apply(blockValidator);
return new ProtocolSpec(
name,
Expand Down Expand Up @@ -501,8 +500,7 @@ public interface BlockValidatorBuilder {
BlockValidator apply(
BlockHeaderValidator blockHeaderValidator,
BlockBodyValidator blockBodyValidator,
BlockProcessor blockProcessor,
BadBlockManager badBlockManager);
BlockProcessor blockProcessor);
}

public interface BlockImporterBuilder {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* 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.trie.common;

import org.hyperledger.besu.datatypes.Hash;

public class StateRootMismatchException extends RuntimeException {

private final Hash actualRoot;
private final Hash expectedRoot;

public StateRootMismatchException(final Hash expectedRoot, final Hash actualRoot) {
this.expectedRoot = expectedRoot;
this.actualRoot = actualRoot;
}

@Override
public String getMessage() {
return "World State Root does not match expected value, header "
+ expectedRoot.toHexString()
+ " calculated "
+ actualRoot.toHexString();
}

public Hash getActualRoot() {
return actualRoot;
}

public Hash getExpectedRoot() {
return expectedRoot;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import org.hyperledger.besu.datatypes.StorageSlotKey;
import org.hyperledger.besu.ethereum.core.BlockHeader;
import org.hyperledger.besu.ethereum.core.MutableWorldState;
import org.hyperledger.besu.ethereum.trie.common.StateRootMismatchException;
import org.hyperledger.besu.ethereum.trie.pathbased.common.StorageSubscriber;
import org.hyperledger.besu.ethereum.trie.pathbased.common.cache.PathBasedCachedWorldStorageManager;
import org.hyperledger.besu.ethereum.trie.pathbased.common.storage.PathBasedLayeredWorldStateKeyValueStorage;
Expand Down Expand Up @@ -245,11 +246,7 @@ public void persist(final BlockHeader blockHeader) {

protected void verifyWorldStateRoot(final Hash calculatedStateRoot, final BlockHeader header) {
if (!worldStateConfig.isTrieDisabled() && !calculatedStateRoot.equals(header.getStateRoot())) {
throw new RuntimeException(
"World State Root does not match expected value, header "
+ header.getStateRoot().toHexString()
+ " calculated "
+ calculatedStateRoot.toHexString());
throw new StateRootMismatchException(header.getStateRoot(), calculatedStateRoot);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ public static BlockchainSetupUtil forHiveTesting(final DataStorageFormat storage
}

public static BlockchainSetupUtil forMainnet() {
return createForEthashChain(BlockTestUtil.getMainnetResources(), DataStorageFormat.FOREST);
return createForEthashChain(BlockTestUtil.getMainnetResources(), DataStorageFormat.BONSAI);
}

public static BlockchainSetupUtil forOutdatedFork() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,8 +120,7 @@ public void setup() {
when(protocolSpec.getGasCalculator()).thenReturn(gasCalculator);
when(protocolSpec.getFeeMarket()).thenReturn(feeMarket);
mainnetBlockValidator =
new MainnetBlockValidator(
blockHeaderValidator, blockBodyValidator, blockProcessor, badBlockManager);
new MainnetBlockValidator(blockHeaderValidator, blockBodyValidator, blockProcessor);
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,15 @@
import org.hyperledger.besu.ethereum.chain.MutableBlockchain;
import org.hyperledger.besu.ethereum.core.Block;
import org.hyperledger.besu.ethereum.core.BlockHeader;
import org.hyperledger.besu.ethereum.core.BlockHeaderBuilder;
import org.hyperledger.besu.ethereum.core.BlockchainSetupUtil;
import org.hyperledger.besu.ethereum.core.MutableWorldState;
import org.hyperledger.besu.ethereum.mainnet.BlockBodyValidator;
import org.hyperledger.besu.ethereum.mainnet.BlockHeaderValidator;
import org.hyperledger.besu.ethereum.mainnet.BlockProcessor;
import org.hyperledger.besu.ethereum.mainnet.BodyValidationMode;
import org.hyperledger.besu.ethereum.mainnet.HeaderValidationMode;
import org.hyperledger.besu.ethereum.mainnet.MainnetBlockHeaderFunctions;
import org.hyperledger.besu.ethereum.trie.MerkleTrieException;
import org.hyperledger.besu.ethereum.trie.pathbased.common.provider.WorldStateQueryParams;
import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive;
Expand All @@ -63,14 +65,14 @@ public class MainnetBlockValidatorTest {
private final ProtocolContext protocolContext = mock(ProtocolContext.class);
private final WorldStateArchive worldStateArchive = mock(WorldStateArchive.class);
private final MutableWorldState worldState = mock(MutableWorldState.class);
private final BadBlockManager badBlockManager = new BadBlockManager();
private final BadBlockManager badBlockManager =
chainUtil.getProtocolContext().getBadBlockManager();
private final BlockProcessor blockProcessor = mock(BlockProcessor.class);
private final BlockHeaderValidator blockHeaderValidator = mock(BlockHeaderValidator.class);
private final BlockBodyValidator blockBodyValidator = mock(BlockBodyValidator.class);

private final MainnetBlockValidator mainnetBlockValidator =
new MainnetBlockValidator(
blockHeaderValidator, blockBodyValidator, blockProcessor, badBlockManager);
new MainnetBlockValidator(blockHeaderValidator, blockBodyValidator, blockProcessor);

public static Stream<Arguments> getStorageExceptions() {
return Stream.of(
Expand All @@ -92,6 +94,7 @@ public void setup() {
new BlockProcessingResult(Optional.empty(), false);

when(protocolContext.getBlockchain()).thenReturn(blockchain);
when(protocolContext.getBadBlockManager()).thenReturn(badBlockManager);
when(protocolContext.getWorldStateArchive()).thenReturn(worldStateArchive);
when(worldStateArchive.getWorldState(any())).thenReturn(Optional.of(worldState));
when(worldStateArchive.getWorldState(any())).thenReturn(Optional.of(worldState));
Expand All @@ -113,6 +116,32 @@ public void setup() {
assertNoBadBlocks();
}

@Test
public void validateAndProcessBlock_onStateRootMismatch() {
var spyBlock = chainUtil.getBlock(4);
BlockHeader badStateRootHeader =
BlockHeaderBuilder.fromHeader(spyBlock.getHeader())
.stateRoot(Hash.EMPTY_TRIE_HASH)
.blockHeaderFunctions(new MainnetBlockHeaderFunctions())
.buildBlockHeader();

Block stateRootMismatchBlock = new Block(badStateRootHeader, spyBlock.getBody());

var spec = chainUtil.getProtocolSchedule().getByBlockHeader(badStateRootHeader);

BlockProcessingResult result =
spec.getBlockValidator()
.validateAndProcessBlock(
chainUtil.getProtocolContext(),
stateRootMismatchBlock,
HeaderValidationMode.NONE,
HeaderValidationMode.NONE);

assertThat(result.isSuccessful()).isFalse();
assertThat(badBlockManager.getBadBlock(stateRootMismatchBlock.getHash())).isPresent();
assertThat(badBlockManager.getBadBlocks()).containsExactly(stateRootMismatchBlock);
}

@Test
public void validateAndProcessBlock_onSuccess() {
BlockProcessingResult result =
Expand Down