mirror of https://github.com/hyperledger/besu
4512 invalid badblock (#4554)
* don't add to bad blocks manager on StorageException * support for MerklePatriciaTrie exceptions Signed-off-by: Justin Florentine <justin+github@florentine.us>pull/4576/head
parent
b322ef6ae1
commit
fe79c02102
@ -0,0 +1,47 @@ |
||||
/* |
||||
* 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; |
||||
|
||||
import org.hyperledger.besu.ethereum.core.MutableWorldState; |
||||
import org.hyperledger.besu.ethereum.core.TransactionReceipt; |
||||
|
||||
import java.util.ArrayList; |
||||
import java.util.List; |
||||
|
||||
public class BlockProcessingOutputs { |
||||
|
||||
private final MutableWorldState worldState; |
||||
private final List<TransactionReceipt> receipts; |
||||
|
||||
public BlockProcessingOutputs( |
||||
final MutableWorldState worldState, final List<TransactionReceipt> receipts) { |
||||
this.worldState = worldState; |
||||
this.receipts = receipts; |
||||
} |
||||
|
||||
public static BlockProcessingOutputs empty() { |
||||
return new BlockProcessingOutputs(null, new ArrayList<>()); |
||||
} |
||||
|
||||
public MutableWorldState getWorldState() { |
||||
return worldState; |
||||
} |
||||
|
||||
public List<TransactionReceipt> getReceipts() { |
||||
return receipts; |
||||
} |
||||
} |
@ -0,0 +1,87 @@ |
||||
/* |
||||
* 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; |
||||
|
||||
import org.hyperledger.besu.ethereum.core.TransactionReceipt; |
||||
|
||||
import java.util.ArrayList; |
||||
import java.util.List; |
||||
import java.util.Optional; |
||||
|
||||
public class BlockProcessingResult extends BlockValidationResult { |
||||
|
||||
private final Optional<BlockProcessingOutputs> yield; |
||||
private final boolean isPartial; |
||||
|
||||
public static final BlockProcessingResult FAILED = new BlockProcessingResult("processing failed"); |
||||
|
||||
public BlockProcessingResult(final Optional<BlockProcessingOutputs> yield) { |
||||
this.yield = yield; |
||||
this.isPartial = false; |
||||
} |
||||
|
||||
public BlockProcessingResult( |
||||
final Optional<BlockProcessingOutputs> yield, final boolean isPartial) { |
||||
this.yield = yield; |
||||
this.isPartial = isPartial; |
||||
} |
||||
|
||||
public BlockProcessingResult( |
||||
final Optional<BlockProcessingOutputs> yield, final String errorMessage) { |
||||
super(errorMessage); |
||||
this.yield = yield; |
||||
this.isPartial = false; |
||||
} |
||||
|
||||
public BlockProcessingResult( |
||||
final Optional<BlockProcessingOutputs> yield, final Throwable cause) { |
||||
super(cause.getLocalizedMessage(), cause); |
||||
this.yield = yield; |
||||
this.isPartial = false; |
||||
} |
||||
|
||||
public BlockProcessingResult( |
||||
final Optional<BlockProcessingOutputs> yield, |
||||
final String errorMessage, |
||||
final boolean isPartial) { |
||||
super(errorMessage); |
||||
this.yield = yield; |
||||
this.isPartial = isPartial; |
||||
} |
||||
|
||||
public BlockProcessingResult(final String errorMessage) { |
||||
super(errorMessage); |
||||
this.isPartial = false; |
||||
this.yield = Optional.empty(); |
||||
} |
||||
|
||||
public Optional<BlockProcessingOutputs> getYield() { |
||||
return yield; |
||||
} |
||||
|
||||
public boolean isPartial() { |
||||
return isPartial; |
||||
} |
||||
|
||||
public List<TransactionReceipt> getReceipts() { |
||||
if (yield.isEmpty()) { |
||||
return new ArrayList<>(); |
||||
} else { |
||||
return yield.get().getReceipts(); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,72 @@ |
||||
/* |
||||
* 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; |
||||
|
||||
import org.hyperledger.besu.ethereum.trie.MerkleTrieException; |
||||
import org.hyperledger.besu.plugin.services.exception.StorageException; |
||||
|
||||
import java.util.Optional; |
||||
|
||||
public class BlockValidationResult { |
||||
|
||||
public final Optional<String> errorMessage; |
||||
public final Optional<Throwable> cause; |
||||
public final boolean success; |
||||
|
||||
public BlockValidationResult() { |
||||
this.success = true; |
||||
this.errorMessage = Optional.empty(); |
||||
this.cause = Optional.empty(); |
||||
} |
||||
|
||||
public BlockValidationResult(final String errorMessage) { |
||||
this.success = false; |
||||
this.errorMessage = Optional.of(errorMessage); |
||||
this.cause = Optional.empty(); |
||||
} |
||||
|
||||
public BlockValidationResult(final String errorMessage, final Throwable cause) { |
||||
this.success = false; |
||||
this.errorMessage = Optional.of(errorMessage); |
||||
this.cause = Optional.of(cause); |
||||
} |
||||
|
||||
public boolean isSuccessful() { |
||||
return this.success; |
||||
} |
||||
|
||||
public boolean isFailed() { |
||||
return !isSuccessful(); |
||||
} |
||||
|
||||
public Optional<Throwable> causedBy() { |
||||
return cause; |
||||
} |
||||
|
||||
public boolean isInternalError() { |
||||
if (causedBy().isPresent()) { |
||||
Throwable t = causedBy().get(); |
||||
// As new "internal only" types of exception are discovered, add them here.
|
||||
if (t instanceof StorageException) { |
||||
return true; |
||||
} else if (t instanceof MerkleTrieException) { |
||||
return true; |
||||
} |
||||
} |
||||
return false; |
||||
} |
||||
} |
@ -0,0 +1,44 @@ |
||||
/* |
||||
* 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.goquorum; |
||||
|
||||
import org.hyperledger.besu.ethereum.BlockProcessingOutputs; |
||||
import org.hyperledger.besu.ethereum.BlockProcessingResult; |
||||
import org.hyperledger.besu.ethereum.core.TransactionReceipt; |
||||
|
||||
import java.util.ArrayList; |
||||
import java.util.List; |
||||
import java.util.Optional; |
||||
|
||||
public class GoQuorumBlockProcessingResult extends BlockProcessingResult { |
||||
|
||||
public final Optional<BlockProcessingOutputs> privateYield; |
||||
|
||||
public GoQuorumBlockProcessingResult( |
||||
final BlockProcessingOutputs mainnetYield, final BlockProcessingOutputs privateYield) { |
||||
super(Optional.of(mainnetYield)); |
||||
this.privateYield = Optional.ofNullable(privateYield); |
||||
} |
||||
|
||||
public List<TransactionReceipt> getPrivateReceipts() { |
||||
if (privateYield.isEmpty()) { |
||||
return new ArrayList<>(); |
||||
} else { |
||||
return privateYield.get().getReceipts(); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,253 @@ |
||||
/* |
||||
* 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; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
import static org.mockito.ArgumentMatchers.any; |
||||
import static org.mockito.ArgumentMatchers.eq; |
||||
import static org.mockito.Mockito.mock; |
||||
import static org.mockito.Mockito.spy; |
||||
import static org.mockito.Mockito.when; |
||||
|
||||
import org.hyperledger.besu.datatypes.Hash; |
||||
import org.hyperledger.besu.datatypes.Wei; |
||||
import org.hyperledger.besu.ethereum.bonsai.BonsaiPersistedWorldState; |
||||
import org.hyperledger.besu.ethereum.bonsai.BonsaiWorldStateArchive; |
||||
import org.hyperledger.besu.ethereum.bonsai.BonsaiWorldStateKeyValueStorage; |
||||
import org.hyperledger.besu.ethereum.bonsai.TrieLogManager; |
||||
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.BlockDataGenerator; |
||||
import org.hyperledger.besu.ethereum.core.BlockHeader; |
||||
import org.hyperledger.besu.ethereum.core.BlockHeaderTestFixture; |
||||
import org.hyperledger.besu.ethereum.core.InMemoryKeyValueStorageProvider; |
||||
import org.hyperledger.besu.ethereum.mainnet.AbstractBlockProcessor; |
||||
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.HeaderValidationMode; |
||||
import org.hyperledger.besu.ethereum.mainnet.MainnetBlockHeaderFunctions; |
||||
import org.hyperledger.besu.ethereum.mainnet.MainnetBlockProcessor; |
||||
import org.hyperledger.besu.ethereum.mainnet.MainnetTransactionProcessor; |
||||
import org.hyperledger.besu.ethereum.storage.StorageProvider; |
||||
import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive; |
||||
import org.hyperledger.besu.ethereum.worldstate.WorldStateStorage; |
||||
import org.hyperledger.besu.plugin.services.exception.StorageException; |
||||
|
||||
import java.util.Optional; |
||||
|
||||
import org.junit.Before; |
||||
import org.junit.Test; |
||||
import org.mockito.Mockito; |
||||
|
||||
public class BlockImportExceptionHandlingTest { |
||||
|
||||
private final MainnetTransactionProcessor transactionProcessor = |
||||
mock(MainnetTransactionProcessor.class); |
||||
private final AbstractBlockProcessor.TransactionReceiptFactory transactionReceiptFactory = |
||||
mock(AbstractBlockProcessor.TransactionReceiptFactory.class); |
||||
private final BlockProcessor blockProcessor = |
||||
new MainnetBlockProcessor( |
||||
transactionProcessor, |
||||
transactionReceiptFactory, |
||||
Wei.ZERO, |
||||
BlockHeader::getCoinbase, |
||||
true, |
||||
Optional.empty()); |
||||
private final BlockHeaderValidator blockHeaderValidator = mock(BlockHeaderValidator.class); |
||||
private final BlockBodyValidator blockBodyValidator = mock(BlockBodyValidator.class); |
||||
private final ProtocolContext protocolContext = mock(ProtocolContext.class); |
||||
protected final MutableBlockchain blockchain = mock(MutableBlockchain.class); |
||||
private final StorageProvider storageProvider = new InMemoryKeyValueStorageProvider(); |
||||
|
||||
private final WorldStateStorage worldStateStorage = |
||||
new BonsaiWorldStateKeyValueStorage(storageProvider); |
||||
|
||||
private final WorldStateArchive worldStateArchive = |
||||
// contains a BonsaiPersistedWorldState which we need to spy on.
|
||||
// do we need to also test with a DefaultWorldStateArchive?
|
||||
spy( |
||||
new BonsaiWorldStateArchive( |
||||
new TrieLogManager( |
||||
blockchain, (BonsaiWorldStateKeyValueStorage) worldStateStorage, 1), |
||||
storageProvider, |
||||
blockchain)); |
||||
|
||||
private final BonsaiPersistedWorldState persisted = |
||||
spy( |
||||
new BonsaiPersistedWorldState( |
||||
(BonsaiWorldStateArchive) worldStateArchive, |
||||
(BonsaiWorldStateKeyValueStorage) worldStateStorage)); |
||||
|
||||
private final BadBlockManager badBlockManager = new BadBlockManager(); |
||||
|
||||
private MainnetBlockValidator mainnetBlockValidator; |
||||
|
||||
@Before |
||||
public void setup() { |
||||
when(protocolContext.getBlockchain()).thenReturn(blockchain); |
||||
|
||||
when(protocolContext.getWorldStateArchive()).thenReturn(worldStateArchive); |
||||
mainnetBlockValidator = |
||||
new MainnetBlockValidator( |
||||
blockHeaderValidator, blockBodyValidator, blockProcessor, badBlockManager); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldNotBadBlockWhenInternalErrorDuringPersisting() { |
||||
|
||||
Mockito.doThrow(new StorageException("database problem")).when(persisted).persist(any()); |
||||
Mockito.doReturn(persisted).when(worldStateArchive).getMutable(); |
||||
Mockito.doReturn(Optional.of(persisted)).when(worldStateArchive).getMutable(any(), any()); |
||||
|
||||
Block goodBlock = |
||||
new BlockDataGenerator() |
||||
.block( |
||||
BlockDataGenerator.BlockOptions.create() |
||||
.setBlockNumber(0) |
||||
.hasTransactions(false) |
||||
.setBlockHeaderFunctions(new MainnetBlockHeaderFunctions())); |
||||
|
||||
when(blockchain.getBlockHeader(any(Hash.class))) |
||||
.thenReturn(Optional.of(new BlockHeaderTestFixture().buildHeader())); |
||||
when(blockHeaderValidator.validateHeader( |
||||
any(BlockHeader.class), |
||||
any(BlockHeader.class), |
||||
eq(protocolContext), |
||||
eq(HeaderValidationMode.DETACHED_ONLY))) |
||||
.thenReturn(true); |
||||
|
||||
when(blockBodyValidator.validateBody( |
||||
eq(protocolContext), |
||||
eq(goodBlock), |
||||
any(), |
||||
any(), |
||||
eq(HeaderValidationMode.DETACHED_ONLY))) |
||||
.thenReturn(true); |
||||
assertThat(badBlockManager.getBadBlocks()).isEmpty(); |
||||
mainnetBlockValidator.validateAndProcessBlock( |
||||
protocolContext, |
||||
goodBlock, |
||||
HeaderValidationMode.DETACHED_ONLY, |
||||
HeaderValidationMode.DETACHED_ONLY); |
||||
assertThat(badBlockManager.getBadBlocks()).isEmpty(); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldNotBadBlockWhenInternalErrorOnBlockLookup() { |
||||
|
||||
Block goodBlock = |
||||
new BlockDataGenerator() |
||||
.block( |
||||
BlockDataGenerator.BlockOptions.create() |
||||
.setBlockNumber(0) |
||||
.hasTransactions(false) |
||||
.setBlockHeaderFunctions(new MainnetBlockHeaderFunctions())); |
||||
|
||||
when(blockchain.getBlockHeader(any(Hash.class))) |
||||
.thenThrow(new StorageException("database problem")); |
||||
when(blockHeaderValidator.validateHeader( |
||||
any(BlockHeader.class), |
||||
any(BlockHeader.class), |
||||
eq(protocolContext), |
||||
eq(HeaderValidationMode.DETACHED_ONLY))) |
||||
.thenReturn(true); |
||||
|
||||
when(blockBodyValidator.validateBody( |
||||
eq(protocolContext), |
||||
eq(goodBlock), |
||||
any(), |
||||
any(), |
||||
eq(HeaderValidationMode.DETACHED_ONLY))) |
||||
.thenReturn(true); |
||||
assertThat(badBlockManager.getBadBlocks().size()).isEqualTo(0); |
||||
mainnetBlockValidator.validateAndProcessBlock( |
||||
protocolContext, |
||||
goodBlock, |
||||
HeaderValidationMode.DETACHED_ONLY, |
||||
HeaderValidationMode.DETACHED_ONLY); |
||||
assertThat(badBlockManager.getBadBlocks()).isEmpty(); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldNotBadBlockWhenInternalErrorDuringValidateHeader() { |
||||
|
||||
Block goodBlock = |
||||
new BlockDataGenerator() |
||||
.block( |
||||
BlockDataGenerator.BlockOptions.create() |
||||
.setBlockNumber(0) |
||||
.hasTransactions(false) |
||||
.setBlockHeaderFunctions(new MainnetBlockHeaderFunctions())); |
||||
|
||||
when(blockchain.getBlockHeader(any(Hash.class))) |
||||
.thenReturn(Optional.of(new BlockHeaderTestFixture().buildHeader())); |
||||
when(blockHeaderValidator.validateHeader( |
||||
any(BlockHeader.class), |
||||
any(BlockHeader.class), |
||||
eq(protocolContext), |
||||
eq(HeaderValidationMode.DETACHED_ONLY))) |
||||
.thenThrow(new StorageException("database problem")); |
||||
|
||||
assertThat(badBlockManager.getBadBlocks()).isEmpty(); |
||||
mainnetBlockValidator.validateAndProcessBlock( |
||||
protocolContext, |
||||
goodBlock, |
||||
HeaderValidationMode.DETACHED_ONLY, |
||||
HeaderValidationMode.DETACHED_ONLY); |
||||
assertThat(badBlockManager.getBadBlocks()).isEmpty(); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldNotBadBlockWhenInternalErrorDuringValidateBody() { |
||||
Mockito.doNothing().when(persisted).persist(any()); |
||||
Mockito.doReturn(persisted).when(worldStateArchive).getMutable(); |
||||
Mockito.doReturn(Optional.of(persisted)).when(worldStateArchive).getMutable(any(), any()); |
||||
|
||||
Block goodBlock = |
||||
new BlockDataGenerator() |
||||
.block( |
||||
BlockDataGenerator.BlockOptions.create() |
||||
.setBlockNumber(0) |
||||
.hasTransactions(false) |
||||
.setBlockHeaderFunctions(new MainnetBlockHeaderFunctions())); |
||||
|
||||
when(blockchain.getBlockHeader(any(Hash.class))) |
||||
.thenReturn(Optional.of(new BlockHeaderTestFixture().buildHeader())); |
||||
when(blockHeaderValidator.validateHeader( |
||||
any(BlockHeader.class), |
||||
any(BlockHeader.class), |
||||
eq(protocolContext), |
||||
eq(HeaderValidationMode.DETACHED_ONLY))) |
||||
.thenReturn(true); |
||||
|
||||
when(blockBodyValidator.validateBody( |
||||
eq(protocolContext), |
||||
eq(goodBlock), |
||||
any(), |
||||
any(), |
||||
eq(HeaderValidationMode.DETACHED_ONLY))) |
||||
.thenThrow(new StorageException("database problem")); |
||||
assertThat(badBlockManager.getBadBlocks()).isEmpty(); |
||||
mainnetBlockValidator.validateAndProcessBlock( |
||||
protocolContext, |
||||
goodBlock, |
||||
HeaderValidationMode.DETACHED_ONLY, |
||||
HeaderValidationMode.DETACHED_ONLY); |
||||
assertThat(badBlockManager.getBadBlocks()).isEmpty(); |
||||
} |
||||
// cover validate body failing on bad receipts
|
||||
} |
Loading…
Reference in new issue