mirror of https://github.com/hyperledger/besu
[NC-1384] Disable mining while catching up to chain head (#125)
Signed-off-by: Adrian Sutton <adrian.sutton@consensys.net>pull/2/head
parent
acc8122bb9
commit
ce033eee55
@ -0,0 +1,27 @@ |
||||
apply plugin: 'java-library' |
||||
|
||||
jar { |
||||
baseName 'pantheon-blockcreation' |
||||
manifest { |
||||
attributes('Implementation-Title': baseName, |
||||
'Implementation-Version': project.version) |
||||
} |
||||
} |
||||
|
||||
dependencies { |
||||
implementation project(':ethereum:core') |
||||
implementation project(':ethereum:eth') |
||||
implementation project(':util') |
||||
implementation project(':crypto') |
||||
implementation project(':services:kvstore') |
||||
|
||||
implementation 'io.vertx:vertx-core' |
||||
implementation 'com.google.guava:guava' |
||||
|
||||
testImplementation 'junit:junit' |
||||
testImplementation 'org.assertj:assertj-core' |
||||
testImplementation 'org.awaitility:awaitility' |
||||
testImplementation 'org.mockito:mockito-core' |
||||
testImplementation project(path: ':ethereum:core', configuration: 'testSupportArtifacts') |
||||
testImplementation project(path: ':ethereum:core', configuration: 'testArtifacts') |
||||
} |
@ -0,0 +1,138 @@ |
||||
/* |
||||
* 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.ethereum.blockcreation; |
||||
|
||||
import static org.mockito.ArgumentMatchers.any; |
||||
import static org.mockito.Mockito.mock; |
||||
import static org.mockito.Mockito.times; |
||||
import static org.mockito.Mockito.verify; |
||||
import static org.mockito.Mockito.verifyNoMoreInteractions; |
||||
import static org.mockito.Mockito.verifyZeroInteractions; |
||||
import static org.mockito.Mockito.when; |
||||
|
||||
import tech.pegasys.pantheon.ethereum.chain.BlockAddedEvent; |
||||
import tech.pegasys.pantheon.ethereum.chain.Blockchain; |
||||
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.eth.sync.state.SyncState; |
||||
|
||||
import java.util.Collections; |
||||
|
||||
import org.junit.Before; |
||||
import org.junit.Test; |
||||
|
||||
public class AbstractMiningCoordinatorTest { |
||||
|
||||
private static final Block BLOCK = |
||||
new Block( |
||||
new BlockHeaderTestFixture().buildHeader(), |
||||
new BlockBody(Collections.emptyList(), Collections.emptyList())); |
||||
private final Blockchain blockchain = mock(Blockchain.class); |
||||
private final EthHashMinerExecutor minerExecutor = mock(EthHashMinerExecutor.class); |
||||
private final SyncState syncState = mock(SyncState.class); |
||||
private final EthHashBlockMiner blockMiner = mock(EthHashBlockMiner.class); |
||||
private final TestMiningCoordinator miningCoordinator = |
||||
new TestMiningCoordinator(blockchain, minerExecutor, syncState); |
||||
|
||||
@Before |
||||
public void setUp() { |
||||
when(minerExecutor.startAsyncMining(any(), any())).thenReturn(blockMiner); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldNotStartMiningWhenEnabledAndOutOfSync() { |
||||
when(syncState.isInSync()).thenReturn(false); |
||||
miningCoordinator.enable(); |
||||
verifyZeroInteractions(minerExecutor, blockMiner); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldStartMiningWhenEnabledAndInSync() { |
||||
when(syncState.isInSync()).thenReturn(true); |
||||
miningCoordinator.enable(); |
||||
verify(minerExecutor).startAsyncMining(any(), any()); |
||||
verifyNoMoreInteractions(minerExecutor, blockMiner); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldStartMiningWhenEnabledAndBecomeInSync() { |
||||
when(syncState.isInSync()).thenReturn(false); |
||||
miningCoordinator.enable(); |
||||
|
||||
miningCoordinator.inSyncChanged(true); |
||||
|
||||
verify(minerExecutor).startAsyncMining(any(), any()); |
||||
verifyNoMoreInteractions(minerExecutor, blockMiner); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldHaltMiningWhenBecomingOutOfSync() { |
||||
when(syncState.isInSync()).thenReturn(true); |
||||
miningCoordinator.enable(); |
||||
verify(minerExecutor).startAsyncMining(any(), any()); |
||||
|
||||
miningCoordinator.inSyncChanged(false); |
||||
|
||||
verify(blockMiner).cancel(); |
||||
verifyNoMoreInteractions(minerExecutor, blockMiner); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldNotStartWhenBlockAddedAndOutOfSync() { |
||||
when(syncState.isInSync()).thenReturn(false); |
||||
miningCoordinator.enable(); |
||||
|
||||
miningCoordinator.onBlockAdded(BlockAddedEvent.createForHeadAdvancement(BLOCK), blockchain); |
||||
|
||||
verifyNoMoreInteractions(minerExecutor, blockMiner); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldRestartMiningWhenBlockAddedAndInSync() { |
||||
when(syncState.isInSync()).thenReturn(true); |
||||
miningCoordinator.enable(); |
||||
|
||||
miningCoordinator.onBlockAdded(BlockAddedEvent.createForHeadAdvancement(BLOCK), blockchain); |
||||
|
||||
verify(blockMiner).cancel(); |
||||
verify(minerExecutor, times(2)).startAsyncMining(any(), any()); |
||||
|
||||
verifyNoMoreInteractions(minerExecutor, blockMiner); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldNotStartMiningWhenBecomingInSyncIfMinerNotEnabled() { |
||||
when(syncState.isInSync()).thenReturn(true); |
||||
miningCoordinator.inSyncChanged(true); |
||||
verifyNoMoreInteractions(minerExecutor, blockMiner); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldNotStartMiningWhenBlockAddedAndInSyncIfMinerNotEnabled() { |
||||
when(syncState.isInSync()).thenReturn(true); |
||||
miningCoordinator.onBlockAdded(BlockAddedEvent.createForHeadAdvancement(BLOCK), blockchain); |
||||
verifyNoMoreInteractions(minerExecutor, blockMiner); |
||||
} |
||||
|
||||
public static class TestMiningCoordinator |
||||
extends AbstractMiningCoordinator<Void, EthHashBlockMiner> { |
||||
|
||||
public TestMiningCoordinator( |
||||
final Blockchain blockchain, |
||||
final AbstractMinerExecutor<Void, EthHashBlockMiner> executor, |
||||
final SyncState syncState) { |
||||
super(blockchain, executor, syncState); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,19 @@ |
||||
/* |
||||
* 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.ethereum.chain; |
||||
|
||||
import tech.pegasys.pantheon.ethereum.core.Block; |
||||
|
||||
public interface MinedBlockObserver { |
||||
void blockMined(Block block); |
||||
} |
@ -0,0 +1,114 @@ |
||||
/* |
||||
* 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.ethereum.eth.sync.state; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
import static org.mockito.Mockito.mock; |
||||
import static org.mockito.Mockito.verify; |
||||
import static org.mockito.Mockito.verifyNoMoreInteractions; |
||||
import static org.mockito.Mockito.when; |
||||
|
||||
import tech.pegasys.pantheon.ethereum.chain.BlockAddedEvent; |
||||
import tech.pegasys.pantheon.ethereum.chain.BlockAddedObserver; |
||||
import tech.pegasys.pantheon.ethereum.chain.Blockchain; |
||||
import tech.pegasys.pantheon.ethereum.core.Block; |
||||
import tech.pegasys.pantheon.ethereum.core.BlockBody; |
||||
import tech.pegasys.pantheon.ethereum.core.BlockHeader; |
||||
import tech.pegasys.pantheon.ethereum.core.BlockHeaderTestFixture; |
||||
import tech.pegasys.pantheon.ethereum.eth.manager.ChainState; |
||||
import tech.pegasys.pantheon.ethereum.eth.manager.EthPeer; |
||||
import tech.pegasys.pantheon.ethereum.eth.manager.EthPeers; |
||||
|
||||
import java.util.Collections; |
||||
|
||||
import org.junit.Before; |
||||
import org.junit.Test; |
||||
import org.mockito.ArgumentCaptor; |
||||
|
||||
public class SyncStateTest { |
||||
|
||||
private static final long OUR_CHAIN_HEAD_NUMBER = 500; |
||||
private static final long TARGET_SYNC_NUMBER = OUR_CHAIN_HEAD_NUMBER + 100; |
||||
private final Blockchain blockchain = mock(Blockchain.class); |
||||
private final EthPeers ethPeers = mock(EthPeers.class); |
||||
private final SyncState.InSyncListener inSyncListener = mock(SyncState.InSyncListener.class); |
||||
private final EthPeer peer = mock(EthPeer.class); |
||||
private final ChainState peerChainHead = new ChainState(); |
||||
private SyncState syncState; |
||||
private BlockAddedObserver blockAddedObserver; |
||||
|
||||
@Before |
||||
public void setUp() { |
||||
final ArgumentCaptor<BlockAddedObserver> captor = |
||||
ArgumentCaptor.forClass(BlockAddedObserver.class); |
||||
when(blockchain.observeBlockAdded(captor.capture())).thenReturn(1L); |
||||
when(peer.chainState()).thenReturn(peerChainHead); |
||||
when(blockchain.getChainHeadBlockNumber()).thenReturn(OUR_CHAIN_HEAD_NUMBER); |
||||
syncState = new SyncState(blockchain, ethPeers); |
||||
blockAddedObserver = captor.getValue(); |
||||
syncState.addInSyncListener(inSyncListener); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldBeInSyncWhenNoSyncTargetHasBeenSet() { |
||||
assertThat(syncState.isInSync()).isTrue(); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldSwitchToNotInSyncWhenSyncTargetWithBetterChainSet() { |
||||
final BlockHeader bestBlockHeader = targetBlockHeader(); |
||||
peerChainHead.update(bestBlockHeader); |
||||
syncState.setSyncTarget(peer, bestBlockHeader); |
||||
assertThat(syncState.isInSync()).isFalse(); |
||||
verify(inSyncListener).onSyncStatusChanged(false); |
||||
verifyNoMoreInteractions(inSyncListener); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldSwitchToInSyncWhenSyncTargetCleared() { |
||||
setupOutOfSyncState(); |
||||
|
||||
syncState.clearSyncTarget(); |
||||
|
||||
verify(inSyncListener).onSyncStatusChanged(true); |
||||
verifyNoMoreInteractions(inSyncListener); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldBecomeInSyncWhenOurBlockchainCatchesUp() { |
||||
setupOutOfSyncState(); |
||||
|
||||
when(blockchain.getChainHeadBlockNumber()).thenReturn(TARGET_SYNC_NUMBER); |
||||
blockAddedObserver.onBlockAdded( |
||||
BlockAddedEvent.createForHeadAdvancement( |
||||
new Block( |
||||
targetBlockHeader(), |
||||
new BlockBody(Collections.emptyList(), Collections.emptyList()))), |
||||
blockchain); |
||||
|
||||
assertThat(syncState.isInSync()).isTrue(); |
||||
verify(inSyncListener).onSyncStatusChanged(true); |
||||
} |
||||
|
||||
private void setupOutOfSyncState() { |
||||
final BlockHeader bestBlockHeader = targetBlockHeader(); |
||||
peerChainHead.update(bestBlockHeader); |
||||
syncState.setSyncTarget(peer, bestBlockHeader); |
||||
assertThat(syncState.isInSync()).isFalse(); |
||||
verify(inSyncListener).onSyncStatusChanged(false); |
||||
} |
||||
|
||||
private BlockHeader targetBlockHeader() { |
||||
return new BlockHeaderTestFixture().number(TARGET_SYNC_NUMBER).buildHeader(); |
||||
} |
||||
} |
Loading…
Reference in new issue