mirror of https://github.com/hyperledger/besu
IBFT to use VoteTallyCache (#907)
It was identified during a demonstration that Pantheon, when running in IBFT would show a "Bad Block Import" when a validator was added or removed from the validator pool. It was determined this was due to IBFT maintaining a single, 'global' copy of the curent list of validators, which was updated when a block was imported - thus when a block which had been imported vi IBFT was then received via Eth block propogation, the validator list would not align with the global list (as it had been updated in the IBFT import). The solution has been to utilise the VoteTallyCache as used in the Clique implementation. Signed-off-by: Adrian Sutton <adrian.sutton@consensys.net>pull/2/head
parent
84d998130e
commit
6a71c66ad5
@ -1,68 +0,0 @@ |
||||
/* |
||||
* Copyright 2018 ConsenSys AG. |
||||
* |
||||
* 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. |
||||
*/ |
||||
package tech.pegasys.pantheon.consensus.ibft; |
||||
|
||||
import tech.pegasys.pantheon.consensus.common.VoteTallyUpdater; |
||||
import tech.pegasys.pantheon.ethereum.ProtocolContext; |
||||
import tech.pegasys.pantheon.ethereum.core.Block; |
||||
import tech.pegasys.pantheon.ethereum.core.BlockHeader; |
||||
import tech.pegasys.pantheon.ethereum.core.BlockImporter; |
||||
import tech.pegasys.pantheon.ethereum.core.TransactionReceipt; |
||||
import tech.pegasys.pantheon.ethereum.mainnet.HeaderValidationMode; |
||||
|
||||
import java.util.List; |
||||
|
||||
/** |
||||
* The IBFT BlockImporter implementation. Adds votes to VoteTally as blocks are added to the chain. |
||||
*/ |
||||
public class IbftBlockImporter implements BlockImporter<IbftContext> { |
||||
|
||||
private final BlockImporter<IbftContext> delegate; |
||||
private final VoteTallyUpdater voteTallyUpdater; |
||||
|
||||
public IbftBlockImporter( |
||||
final BlockImporter<IbftContext> delegate, final VoteTallyUpdater voteTallyUpdater) { |
||||
this.delegate = delegate; |
||||
this.voteTallyUpdater = voteTallyUpdater; |
||||
} |
||||
|
||||
@Override |
||||
public boolean importBlock( |
||||
final ProtocolContext<IbftContext> context, |
||||
final Block block, |
||||
final HeaderValidationMode headerValidationMode, |
||||
final HeaderValidationMode ommerValidationMode) { |
||||
final boolean result = |
||||
delegate.importBlock(context, block, headerValidationMode, ommerValidationMode); |
||||
updateVoteTally(result, block.getHeader(), context); |
||||
return result; |
||||
} |
||||
|
||||
@Override |
||||
public boolean fastImportBlock( |
||||
final ProtocolContext<IbftContext> context, |
||||
final Block block, |
||||
final List<TransactionReceipt> receipts, |
||||
final HeaderValidationMode headerValidationMode) { |
||||
final boolean result = delegate.fastImportBlock(context, block, receipts, headerValidationMode); |
||||
updateVoteTally(result, block.getHeader(), context); |
||||
return result; |
||||
} |
||||
|
||||
private void updateVoteTally( |
||||
final boolean result, final BlockHeader header, final ProtocolContext<IbftContext> context) { |
||||
if (result) { |
||||
voteTallyUpdater.updateForBlock(header, context.getConsensusState().getVoteTally()); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,40 @@ |
||||
/* |
||||
* Copyright 2019 ConsenSys AG. |
||||
* |
||||
* 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. |
||||
*/ |
||||
package tech.pegasys.pantheon.consensus.ibft; |
||||
|
||||
import static org.mockito.ArgumentMatchers.any; |
||||
import static org.mockito.Mockito.mock; |
||||
import static org.mockito.Mockito.when; |
||||
import static org.mockito.Mockito.withSettings; |
||||
|
||||
import tech.pegasys.pantheon.consensus.common.VoteProposer; |
||||
import tech.pegasys.pantheon.consensus.common.VoteTally; |
||||
import tech.pegasys.pantheon.consensus.common.VoteTallyCache; |
||||
import tech.pegasys.pantheon.ethereum.core.Address; |
||||
|
||||
import java.util.Collection; |
||||
|
||||
public class IbftContextBuilder { |
||||
|
||||
public static IbftContext setupContextWithValidators(final Collection<Address> validators) { |
||||
final IbftContext ibftContext = mock(IbftContext.class, withSettings().lenient()); |
||||
final VoteTallyCache mockCache = mock(VoteTallyCache.class, withSettings().lenient()); |
||||
final VoteTally mockVoteTally = mock(VoteTally.class, withSettings().lenient()); |
||||
when(ibftContext.getVoteTallyCache()).thenReturn(mockCache); |
||||
when(mockCache.getVoteTallyAfterBlock(any())).thenReturn(mockVoteTally); |
||||
when(mockVoteTally.getValidators()).thenReturn(validators); |
||||
when(ibftContext.getVoteProposer()).thenReturn(new VoteProposer()); |
||||
|
||||
return ibftContext; |
||||
} |
||||
} |
@ -1,28 +0,0 @@ |
||||
/* |
||||
* Copyright 2018 ConsenSys AG. |
||||
* |
||||
* 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. |
||||
*/ |
||||
package tech.pegasys.pantheon.consensus.ibft; |
||||
|
||||
import tech.pegasys.pantheon.consensus.common.VoteProposer; |
||||
import tech.pegasys.pantheon.consensus.common.VoteTally; |
||||
import tech.pegasys.pantheon.ethereum.ProtocolContext; |
||||
import tech.pegasys.pantheon.ethereum.core.Address; |
||||
|
||||
import java.util.List; |
||||
|
||||
public class IbftProtocolContextFixture { |
||||
|
||||
public static ProtocolContext<IbftContext> protocolContext(final List<Address> validators) { |
||||
return new ProtocolContext<>( |
||||
null, null, new IbftContext(new VoteTally(validators), new VoteProposer())); |
||||
} |
||||
} |
@ -1,107 +0,0 @@ |
||||
/* |
||||
* Copyright 2018 ConsenSys AG. |
||||
* |
||||
* 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. |
||||
*/ |
||||
package tech.pegasys.pantheon.consensus.ibft; |
||||
|
||||
import static java.util.Collections.emptyList; |
||||
import static org.mockito.Mockito.mock; |
||||
import static org.mockito.Mockito.verify; |
||||
import static org.mockito.Mockito.verifyZeroInteractions; |
||||
import static org.mockito.Mockito.when; |
||||
|
||||
import tech.pegasys.pantheon.consensus.common.VoteProposer; |
||||
import tech.pegasys.pantheon.consensus.common.VoteTally; |
||||
import tech.pegasys.pantheon.consensus.common.VoteTallyUpdater; |
||||
import tech.pegasys.pantheon.ethereum.ProtocolContext; |
||||
import tech.pegasys.pantheon.ethereum.chain.MutableBlockchain; |
||||
import tech.pegasys.pantheon.ethereum.core.Block; |
||||
import tech.pegasys.pantheon.ethereum.core.BlockBody; |
||||
import tech.pegasys.pantheon.ethereum.core.BlockHeaderTestFixture; |
||||
import tech.pegasys.pantheon.ethereum.core.BlockImporter; |
||||
import tech.pegasys.pantheon.ethereum.mainnet.HeaderValidationMode; |
||||
import tech.pegasys.pantheon.ethereum.worldstate.WorldStateArchive; |
||||
|
||||
import org.junit.Test; |
||||
|
||||
public class IbftBlockImporterTest { |
||||
|
||||
private final VoteTallyUpdater voteTallyUpdater = mock(VoteTallyUpdater.class); |
||||
private final VoteTally voteTally = mock(VoteTally.class); |
||||
private final VoteProposer voteProposer = mock(VoteProposer.class); |
||||
|
||||
@SuppressWarnings("unchecked") |
||||
private final BlockImporter<IbftContext> delegate = mock(BlockImporter.class); |
||||
|
||||
private final MutableBlockchain blockchain = mock(MutableBlockchain.class); |
||||
private final WorldStateArchive worldStateArchive = mock(WorldStateArchive.class); |
||||
private final ProtocolContext<IbftContext> context = |
||||
new ProtocolContext<>( |
||||
blockchain, worldStateArchive, new IbftContext(voteTally, voteProposer)); |
||||
|
||||
private final IbftBlockImporter importer = new IbftBlockImporter(delegate, voteTallyUpdater); |
||||
|
||||
@Test |
||||
public void voteTallyNotUpdatedWhenBlockImportFails() { |
||||
final BlockHeaderTestFixture headerBuilder = new BlockHeaderTestFixture(); |
||||
final Block block = |
||||
new Block(headerBuilder.buildHeader(), new BlockBody(emptyList(), emptyList())); |
||||
|
||||
when(delegate.importBlock(context, block, HeaderValidationMode.FULL, HeaderValidationMode.FULL)) |
||||
.thenReturn(false); |
||||
|
||||
importer.importBlock(context, block, HeaderValidationMode.FULL); |
||||
|
||||
verifyZeroInteractions(voteTallyUpdater); |
||||
} |
||||
|
||||
@Test |
||||
public void voteTallyNotUpdatedWhenFastBlockImportFails() { |
||||
final BlockHeaderTestFixture headerBuilder = new BlockHeaderTestFixture(); |
||||
final Block block = |
||||
new Block(headerBuilder.buildHeader(), new BlockBody(emptyList(), emptyList())); |
||||
|
||||
when(delegate.fastImportBlock(context, block, emptyList(), HeaderValidationMode.LIGHT)) |
||||
.thenReturn(false); |
||||
|
||||
importer.fastImportBlock(context, block, emptyList(), HeaderValidationMode.LIGHT); |
||||
|
||||
verifyZeroInteractions(voteTallyUpdater); |
||||
} |
||||
|
||||
@Test |
||||
public void voteTallyUpdatedWhenBlockImportSucceeds() { |
||||
final Block block = |
||||
new Block( |
||||
new BlockHeaderTestFixture().buildHeader(), new BlockBody(emptyList(), emptyList())); |
||||
|
||||
when(delegate.importBlock(context, block, HeaderValidationMode.FULL, HeaderValidationMode.FULL)) |
||||
.thenReturn(true); |
||||
|
||||
importer.importBlock(context, block, HeaderValidationMode.FULL); |
||||
|
||||
verify(voteTallyUpdater).updateForBlock(block.getHeader(), voteTally); |
||||
} |
||||
|
||||
@Test |
||||
public void voteTallyUpdatedWhenFastBlockImportSucceeds() { |
||||
final Block block = |
||||
new Block( |
||||
new BlockHeaderTestFixture().buildHeader(), new BlockBody(emptyList(), emptyList())); |
||||
|
||||
when(delegate.fastImportBlock(context, block, emptyList(), HeaderValidationMode.LIGHT)) |
||||
.thenReturn(true); |
||||
|
||||
importer.fastImportBlock(context, block, emptyList(), HeaderValidationMode.LIGHT); |
||||
|
||||
verify(voteTallyUpdater).updateForBlock(block.getHeader(), voteTally); |
||||
} |
||||
} |
Loading…
Reference in new issue