diff --git a/acceptance-tests/build.gradle b/acceptance-tests/build.gradle index 85531a7582..5a1f70aa59 100644 --- a/acceptance-tests/build.gradle +++ b/acceptance-tests/build.gradle @@ -19,6 +19,7 @@ dependencies { testImplementation project(':crypto') testImplementation project(':ethereum:eth') testImplementation project(':ethereum:core') + testImplementation project(':ethereum:blockcreation') testImplementation project(':ethereum:jsonrpc') testImplementation project(':pantheon') testImplementation project(':util') diff --git a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/PantheonNode.java b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/PantheonNode.java index 10e3955dd0..cca53949ea 100644 --- a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/PantheonNode.java +++ b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/PantheonNode.java @@ -19,7 +19,7 @@ import static org.web3j.protocol.core.DefaultBlockParameterName.LATEST; import tech.pegasys.pantheon.controller.KeyPairUtil; import tech.pegasys.pantheon.crypto.SECP256K1.KeyPair; -import tech.pegasys.pantheon.ethereum.blockcreation.MiningParameters; +import tech.pegasys.pantheon.ethereum.core.MiningParameters; import tech.pegasys.pantheon.ethereum.jsonrpc.JsonRpcConfiguration; import tech.pegasys.pantheon.ethereum.jsonrpc.websocket.WebSocketConfiguration; import tech.pegasys.pantheon.tests.acceptance.dsl.account.Account; diff --git a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/PantheonNodeConfig.java b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/PantheonNodeConfig.java index 6682fdf70e..07c2525f13 100644 --- a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/PantheonNodeConfig.java +++ b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/PantheonNodeConfig.java @@ -12,7 +12,7 @@ */ package tech.pegasys.pantheon.tests.acceptance.dsl.node; -import tech.pegasys.pantheon.ethereum.blockcreation.MiningParameters; +import tech.pegasys.pantheon.ethereum.core.MiningParameters; import tech.pegasys.pantheon.ethereum.core.MiningParametersTestBuilder; import tech.pegasys.pantheon.ethereum.jsonrpc.JsonRpcConfiguration; import tech.pegasys.pantheon.ethereum.jsonrpc.RpcApi; diff --git a/consensus/clique/build.gradle b/consensus/clique/build.gradle index ff5837079c..050915cc48 100644 --- a/consensus/clique/build.gradle +++ b/consensus/clique/build.gradle @@ -26,6 +26,7 @@ repositories { mavenCentral() } dependencies { implementation project(':crypto') implementation project(':ethereum:core') + implementation project(':ethereum:blockcreation') implementation project(':ethereum:eth') implementation project(':ethereum:jsonrpc') implementation project(':ethereum:rlp') diff --git a/consensus/clique/src/main/java/tech/pegasys/pantheon/consensus/clique/blockcreation/CliqueBlockMiner.java b/consensus/clique/src/main/java/tech/pegasys/pantheon/consensus/clique/blockcreation/CliqueBlockMiner.java index 5010b4c351..210d0ba9b9 100644 --- a/consensus/clique/src/main/java/tech/pegasys/pantheon/consensus/clique/blockcreation/CliqueBlockMiner.java +++ b/consensus/clique/src/main/java/tech/pegasys/pantheon/consensus/clique/blockcreation/CliqueBlockMiner.java @@ -16,8 +16,8 @@ import tech.pegasys.pantheon.consensus.clique.CliqueContext; import tech.pegasys.pantheon.consensus.clique.CliqueHelpers; import tech.pegasys.pantheon.ethereum.ProtocolContext; import tech.pegasys.pantheon.ethereum.blockcreation.AbstractBlockScheduler; -import tech.pegasys.pantheon.ethereum.blockcreation.AbstractMiningCoordinator.MinedBlockObserver; import tech.pegasys.pantheon.ethereum.blockcreation.BlockMiner; +import tech.pegasys.pantheon.ethereum.chain.MinedBlockObserver; import tech.pegasys.pantheon.ethereum.core.Address; import tech.pegasys.pantheon.ethereum.core.BlockHeader; import tech.pegasys.pantheon.ethereum.mainnet.ProtocolSchedule; diff --git a/consensus/clique/src/main/java/tech/pegasys/pantheon/consensus/clique/blockcreation/CliqueMinerExecutor.java b/consensus/clique/src/main/java/tech/pegasys/pantheon/consensus/clique/blockcreation/CliqueMinerExecutor.java index 4b1df0b0e0..643baa9eb9 100644 --- a/consensus/clique/src/main/java/tech/pegasys/pantheon/consensus/clique/blockcreation/CliqueMinerExecutor.java +++ b/consensus/clique/src/main/java/tech/pegasys/pantheon/consensus/clique/blockcreation/CliqueMinerExecutor.java @@ -20,10 +20,10 @@ import tech.pegasys.pantheon.crypto.SECP256K1.KeyPair; import tech.pegasys.pantheon.ethereum.ProtocolContext; import tech.pegasys.pantheon.ethereum.blockcreation.AbstractBlockScheduler; import tech.pegasys.pantheon.ethereum.blockcreation.AbstractMinerExecutor; -import tech.pegasys.pantheon.ethereum.blockcreation.AbstractMiningCoordinator.MinedBlockObserver; -import tech.pegasys.pantheon.ethereum.blockcreation.MiningParameters; +import tech.pegasys.pantheon.ethereum.chain.MinedBlockObserver; import tech.pegasys.pantheon.ethereum.core.Address; import tech.pegasys.pantheon.ethereum.core.BlockHeader; +import tech.pegasys.pantheon.ethereum.core.MiningParameters; import tech.pegasys.pantheon.ethereum.core.PendingTransactions; import tech.pegasys.pantheon.ethereum.core.Util; import tech.pegasys.pantheon.ethereum.mainnet.ProtocolSchedule; diff --git a/consensus/clique/src/main/java/tech/pegasys/pantheon/consensus/clique/blockcreation/CliqueMiningCoordinator.java b/consensus/clique/src/main/java/tech/pegasys/pantheon/consensus/clique/blockcreation/CliqueMiningCoordinator.java index a9f3c0e4d0..03ead46dcc 100644 --- a/consensus/clique/src/main/java/tech/pegasys/pantheon/consensus/clique/blockcreation/CliqueMiningCoordinator.java +++ b/consensus/clique/src/main/java/tech/pegasys/pantheon/consensus/clique/blockcreation/CliqueMiningCoordinator.java @@ -15,19 +15,13 @@ package tech.pegasys.pantheon.consensus.clique.blockcreation; import tech.pegasys.pantheon.consensus.clique.CliqueContext; import tech.pegasys.pantheon.ethereum.blockcreation.AbstractMiningCoordinator; import tech.pegasys.pantheon.ethereum.chain.Blockchain; - -import java.util.Optional; +import tech.pegasys.pantheon.ethereum.eth.sync.state.SyncState; public class CliqueMiningCoordinator extends AbstractMiningCoordinator { - public CliqueMiningCoordinator(final Blockchain blockchain, final CliqueMinerExecutor executor) { - super(blockchain, executor); - } - - @Override - protected void haltCurrentMiningOperation() { - currentRunningMiner.ifPresent(CliqueBlockMiner::cancel); - currentRunningMiner = Optional.empty(); + public CliqueMiningCoordinator( + final Blockchain blockchain, final CliqueMinerExecutor executor, final SyncState syncState) { + super(blockchain, executor, syncState); } } diff --git a/consensus/clique/src/test/java/tech/pegasys/pantheon/consensus/clique/blockcreation/CliqueMinerExecutorTest.java b/consensus/clique/src/test/java/tech/pegasys/pantheon/consensus/clique/blockcreation/CliqueMinerExecutorTest.java index 3c4ab24673..6f2ba57f16 100644 --- a/consensus/clique/src/test/java/tech/pegasys/pantheon/consensus/clique/blockcreation/CliqueMinerExecutorTest.java +++ b/consensus/clique/src/test/java/tech/pegasys/pantheon/consensus/clique/blockcreation/CliqueMinerExecutorTest.java @@ -26,11 +26,11 @@ import tech.pegasys.pantheon.consensus.common.VoteProposer; import tech.pegasys.pantheon.consensus.common.VoteTally; import tech.pegasys.pantheon.crypto.SECP256K1.KeyPair; import tech.pegasys.pantheon.ethereum.ProtocolContext; -import tech.pegasys.pantheon.ethereum.blockcreation.MiningParameters; import tech.pegasys.pantheon.ethereum.core.Address; import tech.pegasys.pantheon.ethereum.core.AddressHelpers; import tech.pegasys.pantheon.ethereum.core.BlockHeader; import tech.pegasys.pantheon.ethereum.core.BlockHeaderTestFixture; +import tech.pegasys.pantheon.ethereum.core.MiningParameters; import tech.pegasys.pantheon.ethereum.core.PendingTransactions; import tech.pegasys.pantheon.ethereum.core.Util; import tech.pegasys.pantheon.ethereum.core.Wei; diff --git a/consensus/ibft/build.gradle b/consensus/ibft/build.gradle index 227a6e1508..012831a001 100644 --- a/consensus/ibft/build.gradle +++ b/consensus/ibft/build.gradle @@ -25,6 +25,7 @@ dependencies { implementation project(':consensus:common') implementation project(':crypto') implementation project(':ethereum:core') + implementation project(':ethereum:blockcreation') implementation project(':ethereum:eth') implementation project(':ethereum:jsonrpc') implementation project(':ethereum:rlp') diff --git a/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/blockcreation/IbftBlockMiner.java b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/blockcreation/IbftBlockMiner.java index 1f37d68668..e32d5af33a 100644 --- a/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/blockcreation/IbftBlockMiner.java +++ b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/blockcreation/IbftBlockMiner.java @@ -15,8 +15,8 @@ package tech.pegasys.pantheon.consensus.ibft.blockcreation; import tech.pegasys.pantheon.consensus.ibft.IbftContext; import tech.pegasys.pantheon.ethereum.ProtocolContext; import tech.pegasys.pantheon.ethereum.blockcreation.AbstractBlockScheduler; -import tech.pegasys.pantheon.ethereum.blockcreation.AbstractMiningCoordinator.MinedBlockObserver; import tech.pegasys.pantheon.ethereum.blockcreation.BlockMiner; +import tech.pegasys.pantheon.ethereum.chain.MinedBlockObserver; import tech.pegasys.pantheon.ethereum.core.BlockHeader; import tech.pegasys.pantheon.ethereum.mainnet.ProtocolSchedule; import tech.pegasys.pantheon.util.Subscribers; diff --git a/consensus/ibftlegacy/build.gradle b/consensus/ibftlegacy/build.gradle index 4b25aa1db0..d9b84b4310 100644 --- a/consensus/ibftlegacy/build.gradle +++ b/consensus/ibftlegacy/build.gradle @@ -13,6 +13,7 @@ dependencies { implementation project(':consensus:ibft') implementation project(':crypto') implementation project(':ethereum:core') + implementation project(':ethereum:blockcreation') implementation project(':ethereum:eth') implementation project(':ethereum:jsonrpc') implementation project(':ethereum:rlp') diff --git a/ethereum/blockcreation/build.gradle b/ethereum/blockcreation/build.gradle new file mode 100644 index 0000000000..7dd341f421 --- /dev/null +++ b/ethereum/blockcreation/build.gradle @@ -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') +} diff --git a/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/blockcreation/AbstractBlockCreator.java b/ethereum/blockcreation/src/main/java/tech/pegasys/pantheon/ethereum/blockcreation/AbstractBlockCreator.java similarity index 100% rename from ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/blockcreation/AbstractBlockCreator.java rename to ethereum/blockcreation/src/main/java/tech/pegasys/pantheon/ethereum/blockcreation/AbstractBlockCreator.java diff --git a/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/blockcreation/AbstractBlockScheduler.java b/ethereum/blockcreation/src/main/java/tech/pegasys/pantheon/ethereum/blockcreation/AbstractBlockScheduler.java similarity index 100% rename from ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/blockcreation/AbstractBlockScheduler.java rename to ethereum/blockcreation/src/main/java/tech/pegasys/pantheon/ethereum/blockcreation/AbstractBlockScheduler.java diff --git a/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/blockcreation/AbstractMinerExecutor.java b/ethereum/blockcreation/src/main/java/tech/pegasys/pantheon/ethereum/blockcreation/AbstractMinerExecutor.java similarity index 95% rename from ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/blockcreation/AbstractMinerExecutor.java rename to ethereum/blockcreation/src/main/java/tech/pegasys/pantheon/ethereum/blockcreation/AbstractMinerExecutor.java index 8d7576da21..e075866a34 100644 --- a/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/blockcreation/AbstractMinerExecutor.java +++ b/ethereum/blockcreation/src/main/java/tech/pegasys/pantheon/ethereum/blockcreation/AbstractMinerExecutor.java @@ -13,8 +13,9 @@ package tech.pegasys.pantheon.ethereum.blockcreation; import tech.pegasys.pantheon.ethereum.ProtocolContext; -import tech.pegasys.pantheon.ethereum.blockcreation.AbstractMiningCoordinator.MinedBlockObserver; +import tech.pegasys.pantheon.ethereum.chain.MinedBlockObserver; import tech.pegasys.pantheon.ethereum.core.BlockHeader; +import tech.pegasys.pantheon.ethereum.core.MiningParameters; import tech.pegasys.pantheon.ethereum.core.PendingTransactions; import tech.pegasys.pantheon.ethereum.core.Wei; import tech.pegasys.pantheon.ethereum.mainnet.ProtocolSchedule; diff --git a/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/blockcreation/AbstractMiningCoordinator.java b/ethereum/blockcreation/src/main/java/tech/pegasys/pantheon/ethereum/blockcreation/AbstractMiningCoordinator.java similarity index 71% rename from ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/blockcreation/AbstractMiningCoordinator.java rename to ethereum/blockcreation/src/main/java/tech/pegasys/pantheon/ethereum/blockcreation/AbstractMiningCoordinator.java index ecd33877c2..b1029f2bfd 100644 --- a/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/blockcreation/AbstractMiningCoordinator.java +++ b/ethereum/blockcreation/src/main/java/tech/pegasys/pantheon/ethereum/blockcreation/AbstractMiningCoordinator.java @@ -12,14 +12,16 @@ */ package tech.pegasys.pantheon.ethereum.blockcreation; +import static org.apache.logging.log4j.LogManager.getLogger; + import tech.pegasys.pantheon.ethereum.chain.BlockAddedEvent; -import tech.pegasys.pantheon.ethereum.chain.BlockAddedEvent.EventType; import tech.pegasys.pantheon.ethereum.chain.BlockAddedObserver; import tech.pegasys.pantheon.ethereum.chain.Blockchain; +import tech.pegasys.pantheon.ethereum.chain.MinedBlockObserver; import tech.pegasys.pantheon.ethereum.core.Address; -import tech.pegasys.pantheon.ethereum.core.Block; import tech.pegasys.pantheon.ethereum.core.BlockHeader; import tech.pegasys.pantheon.ethereum.core.Wei; +import tech.pegasys.pantheon.ethereum.eth.sync.state.SyncState; import tech.pegasys.pantheon.ethereum.mainnet.EthHashSolution; import tech.pegasys.pantheon.ethereum.mainnet.EthHashSolverInputs; import tech.pegasys.pantheon.util.Subscribers; @@ -27,22 +29,29 @@ import tech.pegasys.pantheon.util.bytes.BytesValue; import java.util.Optional; +import org.apache.logging.log4j.Logger; + public abstract class AbstractMiningCoordinator< C, M extends BlockMiner>> implements BlockAddedObserver { - + private static final Logger LOG = getLogger(); protected boolean isEnabled = false; protected volatile Optional currentRunningMiner = Optional.empty(); private final Subscribers minedBlockObservers = new Subscribers<>(); private final AbstractMinerExecutor executor; protected final Blockchain blockchain; + private final SyncState syncState; public AbstractMiningCoordinator( - final Blockchain blockchain, final AbstractMinerExecutor executor) { + final Blockchain blockchain, + final AbstractMinerExecutor executor, + final SyncState syncState) { this.executor = executor; this.blockchain = blockchain; + this.syncState = syncState; this.blockchain.observeBlockAdded(this); + syncState.addInSyncListener(this::inSyncChanged); } public void enable() { @@ -50,7 +59,9 @@ public abstract class AbstractMiningCoordinator< if (isEnabled) { return; } - startAsyncMiningOperation(); + if (syncState.isInSync()) { + startAsyncMiningOperation(); + } isEnabled = true; } } @@ -67,7 +78,7 @@ public abstract class AbstractMiningCoordinator< public boolean isRunning() { synchronized (this) { - return isEnabled; + return currentRunningMiner.isPresent(); } } @@ -78,28 +89,35 @@ public abstract class AbstractMiningCoordinator< protected void haltCurrentMiningOperation() { currentRunningMiner.ifPresent(M::cancel); + currentRunningMiner = Optional.empty(); } @Override public void onBlockAdded(final BlockAddedEvent event, final Blockchain blockchain) { synchronized (this) { - if (isEnabled && shouldStartNewMiner(event)) { + if (isEnabled && event.isNewCanonicalHead()) { haltCurrentMiningOperation(); - startAsyncMiningOperation(); + if (syncState.isInSync()) { + startAsyncMiningOperation(); + } } } } - private boolean shouldStartNewMiner(final BlockAddedEvent event) { - return event.getEventType() != EventType.FORK; - } - - public void removeMinedBlockObserver(final long id) { - minedBlockObservers.unsubscribe(id); + public void inSyncChanged(final boolean inSync) { + synchronized (this) { + if (isEnabled && inSync) { + LOG.info("Resuming mining operations"); + startAsyncMiningOperation(); + } else if (!inSync) { + LOG.info("Pausing mining while behind chain head"); + haltCurrentMiningOperation(); + } + } } - public long addMinedBlockObserver(final MinedBlockObserver obs) { - return minedBlockObservers.subscribe(obs); + public void addMinedBlockObserver(final MinedBlockObserver obs) { + minedBlockObservers.subscribe(obs); } // Required for JSON RPC, and are deemed to be valid for all mining mechanisms @@ -117,31 +135,26 @@ public abstract class AbstractMiningCoordinator< public void setCoinbase(final Address coinbase) { throw new UnsupportedOperationException( - "Current consensus mechanism prevents" + " setting coinbase."); + "Current consensus mechanism prevents setting coinbase."); } public Optional
getCoinbase() { throw new UnsupportedOperationException( - "Current consensus mechanism prevents" + " querying of coinbase."); + "Current consensus mechanism prevents querying of coinbase."); } public Optional hashesPerSecond() { throw new UnsupportedOperationException( - "Current consensus mechanism prevents querying " + "of hashrate."); + "Current consensus mechanism prevents querying of hashrate."); } public Optional getWorkDefinition() { throw new UnsupportedOperationException( - "Current consensus mechanism prevents querying " + "work definition."); + "Current consensus mechanism prevents querying work definition."); } public boolean submitWork(final EthHashSolution solution) { throw new UnsupportedOperationException( - "Current consensus mechanism prevents submission of work" + " solutions."); - } - - public interface MinedBlockObserver { - - void blockMined(Block block); + "Current consensus mechanism prevents submission of work solutions."); } } diff --git a/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/blockcreation/AsyncBlockCreator.java b/ethereum/blockcreation/src/main/java/tech/pegasys/pantheon/ethereum/blockcreation/AsyncBlockCreator.java similarity index 100% rename from ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/blockcreation/AsyncBlockCreator.java rename to ethereum/blockcreation/src/main/java/tech/pegasys/pantheon/ethereum/blockcreation/AsyncBlockCreator.java diff --git a/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/blockcreation/BlockCreator.java b/ethereum/blockcreation/src/main/java/tech/pegasys/pantheon/ethereum/blockcreation/BlockCreator.java similarity index 100% rename from ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/blockcreation/BlockCreator.java rename to ethereum/blockcreation/src/main/java/tech/pegasys/pantheon/ethereum/blockcreation/BlockCreator.java diff --git a/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/blockcreation/BlockMiner.java b/ethereum/blockcreation/src/main/java/tech/pegasys/pantheon/ethereum/blockcreation/BlockMiner.java similarity index 97% rename from ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/blockcreation/BlockMiner.java rename to ethereum/blockcreation/src/main/java/tech/pegasys/pantheon/ethereum/blockcreation/BlockMiner.java index 5a64a66ca0..7cbb932172 100644 --- a/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/blockcreation/BlockMiner.java +++ b/ethereum/blockcreation/src/main/java/tech/pegasys/pantheon/ethereum/blockcreation/BlockMiner.java @@ -13,7 +13,7 @@ package tech.pegasys.pantheon.ethereum.blockcreation; import tech.pegasys.pantheon.ethereum.ProtocolContext; -import tech.pegasys.pantheon.ethereum.blockcreation.AbstractMiningCoordinator.MinedBlockObserver; +import tech.pegasys.pantheon.ethereum.chain.MinedBlockObserver; import tech.pegasys.pantheon.ethereum.core.Block; import tech.pegasys.pantheon.ethereum.core.BlockHeader; import tech.pegasys.pantheon.ethereum.core.BlockImporter; diff --git a/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/blockcreation/BlockTransactionSelector.java b/ethereum/blockcreation/src/main/java/tech/pegasys/pantheon/ethereum/blockcreation/BlockTransactionSelector.java similarity index 100% rename from ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/blockcreation/BlockTransactionSelector.java rename to ethereum/blockcreation/src/main/java/tech/pegasys/pantheon/ethereum/blockcreation/BlockTransactionSelector.java diff --git a/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/blockcreation/CoinbaseNotSetException.java b/ethereum/blockcreation/src/main/java/tech/pegasys/pantheon/ethereum/blockcreation/CoinbaseNotSetException.java similarity index 100% rename from ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/blockcreation/CoinbaseNotSetException.java rename to ethereum/blockcreation/src/main/java/tech/pegasys/pantheon/ethereum/blockcreation/CoinbaseNotSetException.java diff --git a/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/blockcreation/DefaultBlockScheduler.java b/ethereum/blockcreation/src/main/java/tech/pegasys/pantheon/ethereum/blockcreation/DefaultBlockScheduler.java similarity index 100% rename from ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/blockcreation/DefaultBlockScheduler.java rename to ethereum/blockcreation/src/main/java/tech/pegasys/pantheon/ethereum/blockcreation/DefaultBlockScheduler.java diff --git a/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/mainnet/EthHashBlockCreator.java b/ethereum/blockcreation/src/main/java/tech/pegasys/pantheon/ethereum/blockcreation/EthHashBlockCreator.java similarity index 89% rename from ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/mainnet/EthHashBlockCreator.java rename to ethereum/blockcreation/src/main/java/tech/pegasys/pantheon/ethereum/blockcreation/EthHashBlockCreator.java index ad60de8d27..5e076232ed 100644 --- a/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/mainnet/EthHashBlockCreator.java +++ b/ethereum/blockcreation/src/main/java/tech/pegasys/pantheon/ethereum/blockcreation/EthHashBlockCreator.java @@ -10,17 +10,22 @@ * 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.mainnet; +package tech.pegasys.pantheon.ethereum.blockcreation; import tech.pegasys.pantheon.ethereum.ProtocolContext; -import tech.pegasys.pantheon.ethereum.blockcreation.AbstractBlockCreator; import tech.pegasys.pantheon.ethereum.core.Address; import tech.pegasys.pantheon.ethereum.core.BlockHeader; import tech.pegasys.pantheon.ethereum.core.BlockHeaderBuilder; import tech.pegasys.pantheon.ethereum.core.PendingTransactions; import tech.pegasys.pantheon.ethereum.core.SealableBlockHeader; import tech.pegasys.pantheon.ethereum.core.Wei; +import tech.pegasys.pantheon.ethereum.mainnet.EthHash; +import tech.pegasys.pantheon.ethereum.mainnet.EthHashSolution; +import tech.pegasys.pantheon.ethereum.mainnet.EthHashSolver; import tech.pegasys.pantheon.ethereum.mainnet.EthHashSolver.EthHashSolverJob; +import tech.pegasys.pantheon.ethereum.mainnet.EthHashSolverInputs; +import tech.pegasys.pantheon.ethereum.mainnet.MainnetBlockHashFunction; +import tech.pegasys.pantheon.ethereum.mainnet.ProtocolSchedule; import tech.pegasys.pantheon.util.bytes.BytesValues; import tech.pegasys.pantheon.util.uint.UInt256; diff --git a/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/blockcreation/EthHashBlockMiner.java b/ethereum/blockcreation/src/main/java/tech/pegasys/pantheon/ethereum/blockcreation/EthHashBlockMiner.java similarity index 92% rename from ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/blockcreation/EthHashBlockMiner.java rename to ethereum/blockcreation/src/main/java/tech/pegasys/pantheon/ethereum/blockcreation/EthHashBlockMiner.java index 38c7d9b792..fdfea0e89e 100644 --- a/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/blockcreation/EthHashBlockMiner.java +++ b/ethereum/blockcreation/src/main/java/tech/pegasys/pantheon/ethereum/blockcreation/EthHashBlockMiner.java @@ -13,9 +13,8 @@ package tech.pegasys.pantheon.ethereum.blockcreation; import tech.pegasys.pantheon.ethereum.ProtocolContext; -import tech.pegasys.pantheon.ethereum.blockcreation.AbstractMiningCoordinator.MinedBlockObserver; +import tech.pegasys.pantheon.ethereum.chain.MinedBlockObserver; import tech.pegasys.pantheon.ethereum.core.BlockHeader; -import tech.pegasys.pantheon.ethereum.mainnet.EthHashBlockCreator; import tech.pegasys.pantheon.ethereum.mainnet.EthHashSolution; import tech.pegasys.pantheon.ethereum.mainnet.EthHashSolverInputs; import tech.pegasys.pantheon.ethereum.mainnet.ProtocolSchedule; diff --git a/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/blockcreation/EthHashMinerExecutor.java b/ethereum/blockcreation/src/main/java/tech/pegasys/pantheon/ethereum/blockcreation/EthHashMinerExecutor.java similarity index 95% rename from ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/blockcreation/EthHashMinerExecutor.java rename to ethereum/blockcreation/src/main/java/tech/pegasys/pantheon/ethereum/blockcreation/EthHashMinerExecutor.java index ac09137d7e..e83ee56ecd 100644 --- a/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/blockcreation/EthHashMinerExecutor.java +++ b/ethereum/blockcreation/src/main/java/tech/pegasys/pantheon/ethereum/blockcreation/EthHashMinerExecutor.java @@ -13,11 +13,11 @@ package tech.pegasys.pantheon.ethereum.blockcreation; import tech.pegasys.pantheon.ethereum.ProtocolContext; -import tech.pegasys.pantheon.ethereum.blockcreation.AbstractMiningCoordinator.MinedBlockObserver; +import tech.pegasys.pantheon.ethereum.chain.MinedBlockObserver; import tech.pegasys.pantheon.ethereum.core.Address; import tech.pegasys.pantheon.ethereum.core.BlockHeader; +import tech.pegasys.pantheon.ethereum.core.MiningParameters; import tech.pegasys.pantheon.ethereum.core.PendingTransactions; -import tech.pegasys.pantheon.ethereum.mainnet.EthHashBlockCreator; import tech.pegasys.pantheon.ethereum.mainnet.EthHashSolver; import tech.pegasys.pantheon.ethereum.mainnet.EthHasher; import tech.pegasys.pantheon.ethereum.mainnet.ProtocolSchedule; diff --git a/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/blockcreation/EthHashMiningCoordinator.java b/ethereum/blockcreation/src/main/java/tech/pegasys/pantheon/ethereum/blockcreation/EthHashMiningCoordinator.java similarity index 93% rename from ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/blockcreation/EthHashMiningCoordinator.java rename to ethereum/blockcreation/src/main/java/tech/pegasys/pantheon/ethereum/blockcreation/EthHashMiningCoordinator.java index 349be51aab..92fd685142 100644 --- a/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/blockcreation/EthHashMiningCoordinator.java +++ b/ethereum/blockcreation/src/main/java/tech/pegasys/pantheon/ethereum/blockcreation/EthHashMiningCoordinator.java @@ -15,6 +15,7 @@ package tech.pegasys.pantheon.ethereum.blockcreation; import tech.pegasys.pantheon.ethereum.chain.BlockAddedObserver; import tech.pegasys.pantheon.ethereum.chain.Blockchain; import tech.pegasys.pantheon.ethereum.core.Address; +import tech.pegasys.pantheon.ethereum.eth.sync.state.SyncState; import tech.pegasys.pantheon.ethereum.mainnet.EthHashSolution; import tech.pegasys.pantheon.ethereum.mainnet.EthHashSolverInputs; @@ -31,8 +32,8 @@ public class EthHashMiningCoordinator extends AbstractMiningCoordinator cachedHashesPerSecond = Optional.empty(); public EthHashMiningCoordinator( - final Blockchain blockchain, final EthHashMinerExecutor executor) { - super(blockchain, executor); + final Blockchain blockchain, final EthHashMinerExecutor executor, final SyncState syncState) { + super(blockchain, executor, syncState); this.executor = executor; } @@ -78,5 +79,6 @@ public class EthHashMiningCoordinator extends AbstractMiningCoordinator cachedHashesPerSecond = Optional.of(val)); }); + currentRunningMiner = Optional.empty(); } } diff --git a/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/blockcreation/IncrementingNonceGenerator.java b/ethereum/blockcreation/src/main/java/tech/pegasys/pantheon/ethereum/blockcreation/IncrementingNonceGenerator.java similarity index 100% rename from ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/blockcreation/IncrementingNonceGenerator.java rename to ethereum/blockcreation/src/main/java/tech/pegasys/pantheon/ethereum/blockcreation/IncrementingNonceGenerator.java diff --git a/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/blockcreation/RandomNonceGenerator.java b/ethereum/blockcreation/src/main/java/tech/pegasys/pantheon/ethereum/blockcreation/RandomNonceGenerator.java similarity index 100% rename from ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/blockcreation/RandomNonceGenerator.java rename to ethereum/blockcreation/src/main/java/tech/pegasys/pantheon/ethereum/blockcreation/RandomNonceGenerator.java diff --git a/ethereum/blockcreation/src/test/java/tech/pegasys/pantheon/ethereum/blockcreation/AbstractMiningCoordinatorTest.java b/ethereum/blockcreation/src/test/java/tech/pegasys/pantheon/ethereum/blockcreation/AbstractMiningCoordinatorTest.java new file mode 100644 index 0000000000..8683044c96 --- /dev/null +++ b/ethereum/blockcreation/src/test/java/tech/pegasys/pantheon/ethereum/blockcreation/AbstractMiningCoordinatorTest.java @@ -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 { + + public TestMiningCoordinator( + final Blockchain blockchain, + final AbstractMinerExecutor executor, + final SyncState syncState) { + super(blockchain, executor, syncState); + } + } +} diff --git a/ethereum/core/src/test/java/tech/pegasys/pantheon/ethereum/blockcreation/BlockMinerTest.java b/ethereum/blockcreation/src/test/java/tech/pegasys/pantheon/ethereum/blockcreation/BlockMinerTest.java similarity index 97% rename from ethereum/core/src/test/java/tech/pegasys/pantheon/ethereum/blockcreation/BlockMinerTest.java rename to ethereum/blockcreation/src/test/java/tech/pegasys/pantheon/ethereum/blockcreation/BlockMinerTest.java index 4f3a399d47..a0f2bc9163 100644 --- a/ethereum/core/src/test/java/tech/pegasys/pantheon/ethereum/blockcreation/BlockMinerTest.java +++ b/ethereum/blockcreation/src/test/java/tech/pegasys/pantheon/ethereum/blockcreation/BlockMinerTest.java @@ -20,12 +20,11 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import tech.pegasys.pantheon.ethereum.ProtocolContext; -import tech.pegasys.pantheon.ethereum.blockcreation.AbstractMiningCoordinator.MinedBlockObserver; +import tech.pegasys.pantheon.ethereum.chain.MinedBlockObserver; 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.EthHashBlockCreator; import tech.pegasys.pantheon.ethereum.mainnet.HeaderValidationMode; import tech.pegasys.pantheon.ethereum.mainnet.MutableProtocolSchedule; import tech.pegasys.pantheon.ethereum.mainnet.ProtocolSchedule; diff --git a/ethereum/core/src/test/java/tech/pegasys/pantheon/ethereum/blockcreation/BlockTransactionSelectorTest.java b/ethereum/blockcreation/src/test/java/tech/pegasys/pantheon/ethereum/blockcreation/BlockTransactionSelectorTest.java similarity index 100% rename from ethereum/core/src/test/java/tech/pegasys/pantheon/ethereum/blockcreation/BlockTransactionSelectorTest.java rename to ethereum/blockcreation/src/test/java/tech/pegasys/pantheon/ethereum/blockcreation/BlockTransactionSelectorTest.java diff --git a/ethereum/core/src/test/java/tech/pegasys/pantheon/ethereum/blockcreation/DefaultBlockSchedulerTest.java b/ethereum/blockcreation/src/test/java/tech/pegasys/pantheon/ethereum/blockcreation/DefaultBlockSchedulerTest.java similarity index 100% rename from ethereum/core/src/test/java/tech/pegasys/pantheon/ethereum/blockcreation/DefaultBlockSchedulerTest.java rename to ethereum/blockcreation/src/test/java/tech/pegasys/pantheon/ethereum/blockcreation/DefaultBlockSchedulerTest.java diff --git a/ethereum/core/src/test/java/tech/pegasys/pantheon/ethereum/mainnet/EthHashBlockCreatorTest.java b/ethereum/blockcreation/src/test/java/tech/pegasys/pantheon/ethereum/blockcreation/EthHashBlockCreatorTest.java similarity index 88% rename from ethereum/core/src/test/java/tech/pegasys/pantheon/ethereum/mainnet/EthHashBlockCreatorTest.java rename to ethereum/blockcreation/src/test/java/tech/pegasys/pantheon/ethereum/blockcreation/EthHashBlockCreatorTest.java index 882706bd45..5d2c138b36 100644 --- a/ethereum/core/src/test/java/tech/pegasys/pantheon/ethereum/mainnet/EthHashBlockCreatorTest.java +++ b/ethereum/blockcreation/src/test/java/tech/pegasys/pantheon/ethereum/blockcreation/EthHashBlockCreatorTest.java @@ -10,13 +10,16 @@ * 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.mainnet; +package tech.pegasys.pantheon.ethereum.blockcreation; import tech.pegasys.pantheon.ethereum.core.Address; import tech.pegasys.pantheon.ethereum.core.Block; import tech.pegasys.pantheon.ethereum.core.ExecutionContextTestFixture; import tech.pegasys.pantheon.ethereum.core.PendingTransactions; import tech.pegasys.pantheon.ethereum.core.Wei; +import tech.pegasys.pantheon.ethereum.mainnet.EthHashSolver; +import tech.pegasys.pantheon.ethereum.mainnet.EthHasher.Light; +import tech.pegasys.pantheon.ethereum.mainnet.ValidationTestUtils; import tech.pegasys.pantheon.util.bytes.BytesValue; import java.io.IOException; @@ -42,8 +45,7 @@ public class EthHashBlockCreatorTest { @Test public void createMainnetBlock1() throws IOException { - final EthHashSolver solver = - new EthHashSolver(Lists.newArrayList(BLOCK_1_NONCE), new EthHasher.Light()); + final EthHashSolver solver = new EthHashSolver(Lists.newArrayList(BLOCK_1_NONCE), new Light()); final EthHashBlockCreator blockCreator = new EthHashBlockCreator( BLOCK_1_COINBASE, diff --git a/ethereum/core/src/test/java/tech/pegasys/pantheon/ethereum/blockcreation/EthHashMinerExecutorTest.java b/ethereum/blockcreation/src/test/java/tech/pegasys/pantheon/ethereum/blockcreation/EthHashMinerExecutorTest.java similarity index 97% rename from ethereum/core/src/test/java/tech/pegasys/pantheon/ethereum/blockcreation/EthHashMinerExecutorTest.java rename to ethereum/blockcreation/src/test/java/tech/pegasys/pantheon/ethereum/blockcreation/EthHashMinerExecutorTest.java index bb17ec6516..5cf0a8fa0d 100644 --- a/ethereum/core/src/test/java/tech/pegasys/pantheon/ethereum/blockcreation/EthHashMinerExecutorTest.java +++ b/ethereum/blockcreation/src/test/java/tech/pegasys/pantheon/ethereum/blockcreation/EthHashMinerExecutorTest.java @@ -14,6 +14,7 @@ package tech.pegasys.pantheon.ethereum.blockcreation; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import tech.pegasys.pantheon.ethereum.core.MiningParameters; import tech.pegasys.pantheon.ethereum.core.MiningParametersTestBuilder; import tech.pegasys.pantheon.ethereum.core.PendingTransactions; import tech.pegasys.pantheon.util.Subscribers; diff --git a/ethereum/core/src/test/java/tech/pegasys/pantheon/ethereum/blockcreation/EthHashMiningCoordinatorTest.java b/ethereum/blockcreation/src/test/java/tech/pegasys/pantheon/ethereum/blockcreation/EthHashMiningCoordinatorTest.java similarity index 83% rename from ethereum/core/src/test/java/tech/pegasys/pantheon/ethereum/blockcreation/EthHashMiningCoordinatorTest.java rename to ethereum/blockcreation/src/test/java/tech/pegasys/pantheon/ethereum/blockcreation/EthHashMiningCoordinatorTest.java index a1683b19d5..2a5c4c3f7e 100644 --- a/ethereum/core/src/test/java/tech/pegasys/pantheon/ethereum/blockcreation/EthHashMiningCoordinatorTest.java +++ b/ethereum/blockcreation/src/test/java/tech/pegasys/pantheon/ethereum/blockcreation/EthHashMiningCoordinatorTest.java @@ -19,22 +19,31 @@ import static org.mockito.Mockito.when; import tech.pegasys.pantheon.ethereum.core.ExecutionContextTestFixture; import tech.pegasys.pantheon.ethereum.core.Hash; +import tech.pegasys.pantheon.ethereum.eth.sync.state.SyncState; import tech.pegasys.pantheon.ethereum.mainnet.EthHashSolution; import tech.pegasys.pantheon.util.bytes.Bytes32; import java.util.Optional; +import org.junit.Before; import org.junit.Test; public class EthHashMiningCoordinatorTest { private final ExecutionContextTestFixture executionContext = new ExecutionContextTestFixture(); + private final SyncState syncState = mock(SyncState.class); + private final EthHashMinerExecutor executor = mock(EthHashMinerExecutor.class); + private final EthHashBlockMiner miner = mock(EthHashBlockMiner.class); + + @Before + public void setUp() { + when(syncState.isInSync()).thenReturn(true); + } @Test public void miningCoordinatorIsCreatedDisabledWithNoReportableMiningStatistics() { - final EthHashMinerExecutor executor = mock(EthHashMinerExecutor.class); final EthHashMiningCoordinator miningCoordinator = - new EthHashMiningCoordinator(executionContext.getBlockchain(), executor); + new EthHashMiningCoordinator(executionContext.getBlockchain(), executor, syncState); final EthHashSolution solution = new EthHashSolution(1L, Hash.EMPTY, new byte[Bytes32.SIZE]); assertThat(miningCoordinator.isRunning()).isFalse(); @@ -45,21 +54,17 @@ public class EthHashMiningCoordinatorTest { @Test @SuppressWarnings("unchecked") - public void reportedHashRateIsCachedIfNoCurrentDataInMiner() throws InterruptedException { - - final EthHashBlockMiner miner = mock(EthHashBlockMiner.class); - + public void reportedHashRateIsCachedIfNoCurrentDataInMiner() { final Optional hashRate1 = Optional.of(10L); final Optional hashRate2 = Optional.empty(); final Optional hashRate3 = Optional.of(20L); when(miner.getHashesPerSecond()).thenReturn(hashRate1, hashRate2, hashRate3); - final EthHashMinerExecutor executor = mock(EthHashMinerExecutor.class); when(executor.startAsyncMining(any(), any())).thenReturn(miner); final EthHashMiningCoordinator miningCoordinator = - new EthHashMiningCoordinator(executionContext.getBlockchain(), executor); + new EthHashMiningCoordinator(executionContext.getBlockchain(), executor, syncState); miningCoordinator.enable(); // Must enable prior returning data assertThat(miningCoordinator.hashesPerSecond()).isEqualTo(hashRate1); diff --git a/ethereum/core/src/test/java/tech/pegasys/pantheon/ethereum/blockcreation/IncrementingNonceGeneratorTest.java b/ethereum/blockcreation/src/test/java/tech/pegasys/pantheon/ethereum/blockcreation/IncrementingNonceGeneratorTest.java similarity index 100% rename from ethereum/core/src/test/java/tech/pegasys/pantheon/ethereum/blockcreation/IncrementingNonceGeneratorTest.java rename to ethereum/blockcreation/src/test/java/tech/pegasys/pantheon/ethereum/blockcreation/IncrementingNonceGeneratorTest.java diff --git a/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/chain/BlockAddedEvent.java b/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/chain/BlockAddedEvent.java index 1d76c8181e..13db0335c1 100644 --- a/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/chain/BlockAddedEvent.java +++ b/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/chain/BlockAddedEvent.java @@ -64,6 +64,10 @@ public class BlockAddedEvent { return block; } + public boolean isNewCanonicalHead() { + return eventType != EventType.FORK; + } + public EventType getEventType() { return eventType; } diff --git a/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/chain/MinedBlockObserver.java b/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/chain/MinedBlockObserver.java new file mode 100644 index 0000000000..f5800975a5 --- /dev/null +++ b/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/chain/MinedBlockObserver.java @@ -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); +} diff --git a/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/blockcreation/MiningParameters.java b/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/core/MiningParameters.java similarity index 90% rename from ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/blockcreation/MiningParameters.java rename to ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/core/MiningParameters.java index ca5f0ed3e6..3b7799512b 100644 --- a/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/blockcreation/MiningParameters.java +++ b/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/core/MiningParameters.java @@ -10,10 +10,8 @@ * 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; +package tech.pegasys.pantheon.ethereum.core; -import tech.pegasys.pantheon.ethereum.core.Address; -import tech.pegasys.pantheon.ethereum.core.Wei; import tech.pegasys.pantheon.util.bytes.BytesValue; import java.util.Optional; diff --git a/ethereum/core/src/test-support/java/tech/pegasys/pantheon/ethereum/core/MiningParametersTestBuilder.java b/ethereum/core/src/test-support/java/tech/pegasys/pantheon/ethereum/core/MiningParametersTestBuilder.java index 37a9fbe945..27e5933911 100644 --- a/ethereum/core/src/test-support/java/tech/pegasys/pantheon/ethereum/core/MiningParametersTestBuilder.java +++ b/ethereum/core/src/test-support/java/tech/pegasys/pantheon/ethereum/core/MiningParametersTestBuilder.java @@ -12,7 +12,6 @@ */ package tech.pegasys.pantheon.ethereum.core; -import tech.pegasys.pantheon.ethereum.blockcreation.MiningParameters; import tech.pegasys.pantheon.util.bytes.BytesValue; public class MiningParametersTestBuilder { diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/ChainState.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/ChainState.java index 2b33fa329e..2ae8e9409e 100644 --- a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/ChainState.java +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/ChainState.java @@ -14,6 +14,7 @@ package tech.pegasys.pantheon.ethereum.eth.manager; import tech.pegasys.pantheon.ethereum.core.BlockHeader; import tech.pegasys.pantheon.ethereum.core.Hash; +import tech.pegasys.pantheon.util.Subscribers; import tech.pegasys.pantheon.util.uint.UInt256; import com.google.common.base.MoreObjects; @@ -25,6 +26,16 @@ public class ChainState { private volatile long estimatedHeight = 0L; private volatile boolean estimatedHeightKnown = false; + private final Subscribers estimatedHeightListeners = new Subscribers<>(); + + public long addEstimatedHeightListener(final EstimatedHeightListener listener) { + return estimatedHeightListeners.subscribe(listener); + } + + public void removeEstimatedHeightListener(final long listenerId) { + estimatedHeightListeners.unsubscribe(listenerId); + } + public boolean hasEstimatedHeight() { return estimatedHeightKnown; } @@ -55,7 +66,7 @@ public class ChainState { public void update(final BlockHeader header) { synchronized (this) { - if (bestBlock.hash.equals(header.getHash())) { + if (header.getHash().equals(bestBlock.hash)) { bestBlock.number = header.getNumber(); } updateHeightEstimate(header.getNumber()); @@ -77,6 +88,7 @@ public class ChainState { estimatedHeightKnown = true; if (blockNumber > estimatedHeight) { estimatedHeight = blockNumber; + estimatedHeightListeners.forEach(e -> e.onEstimatedHeightChanged(estimatedHeight)); } } @@ -115,4 +127,9 @@ public class ChainState { .toString(); } } + + @FunctionalInterface + public interface EstimatedHeightListener { + void onEstimatedHeightChanged(long estimatedHeight); + } } diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/EthPeer.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/EthPeer.java index 52f1db5eec..703f1cd6f3 100644 --- a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/EthPeer.java +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/EthPeer.java @@ -15,6 +15,7 @@ package tech.pegasys.pantheon.ethereum.eth.manager; import static com.google.common.base.Preconditions.checkArgument; import tech.pegasys.pantheon.ethereum.core.Hash; +import tech.pegasys.pantheon.ethereum.eth.manager.ChainState.EstimatedHeightListener; import tech.pegasys.pantheon.ethereum.eth.manager.RequestManager.ResponseStream; import tech.pegasys.pantheon.ethereum.eth.messages.EthPV62; import tech.pegasys.pantheon.ethereum.eth.messages.EthPV63; @@ -81,6 +82,14 @@ public class EthPeer { this.onStatusesExchanged.set(onStatusesExchanged); } + public long addChainEstimatedHeightListener(final EstimatedHeightListener listener) { + return chainHeadState.addEstimatedHeightListener(listener); + } + + public void removeChainEstimatedHeightListener(final long listenerId) { + chainHeadState.removeEstimatedHeightListener(listenerId); + } + public void recordRequestTimeout(final int requestCode) { LOG.debug("Timed out while waiting for response from peer {}", this); reputation.recordRequestTimeout(requestCode).ifPresent(this::disconnect); diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/EthProtocolManager.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/EthProtocolManager.java index 32ac6660fd..cc65812413 100644 --- a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/EthProtocolManager.java +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/EthProtocolManager.java @@ -14,8 +14,8 @@ package tech.pegasys.pantheon.ethereum.eth.manager; import static com.google.common.base.Preconditions.checkArgument; -import tech.pegasys.pantheon.ethereum.blockcreation.AbstractMiningCoordinator.MinedBlockObserver; import tech.pegasys.pantheon.ethereum.chain.Blockchain; +import tech.pegasys.pantheon.ethereum.chain.MinedBlockObserver; import tech.pegasys.pantheon.ethereum.core.Block; import tech.pegasys.pantheon.ethereum.core.Hash; import tech.pegasys.pantheon.ethereum.eth.EthProtocol; diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/BlockPropagationManager.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/BlockPropagationManager.java index a5ee02de78..bf2205b119 100644 --- a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/BlockPropagationManager.java +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/BlockPropagationManager.java @@ -70,14 +70,15 @@ public class BlockPropagationManager { final ProtocolSchedule protocolSchedule, final ProtocolContext protocolContext, final EthContext ethContext, - final SyncState syncState) { + final SyncState syncState, + final PendingBlocks pendingBlocks) { this.config = config; this.protocolSchedule = protocolSchedule; this.protocolContext = protocolContext; this.ethContext = ethContext; this.syncState = syncState; - pendingBlocks = syncState.pendingBlocks(); + this.pendingBlocks = pendingBlocks; } public void start() { diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/DefaultSynchronizer.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/DefaultSynchronizer.java index d221eca946..3a1f40f755 100644 --- a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/DefaultSynchronizer.java +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/DefaultSynchronizer.java @@ -39,12 +39,17 @@ public class DefaultSynchronizer implements Synchronizer { final SynchronizerConfiguration syncConfig, final ProtocolSchedule protocolSchedule, final ProtocolContext protocolContext, - final EthContext ethContext) { - this.syncState = - new SyncState(protocolContext.getBlockchain(), ethContext, new PendingBlocks()); + final EthContext ethContext, + final SyncState syncState) { + this.syncState = syncState; this.blockPropagationManager = new BlockPropagationManager<>( - syncConfig, protocolSchedule, protocolContext, ethContext, syncState); + syncConfig, + protocolSchedule, + protocolContext, + ethContext, + syncState, + new PendingBlocks()); this.downloader = new Downloader<>(syncConfig, protocolSchedule, protocolContext, ethContext, syncState); diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/Downloader.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/Downloader.java index 50a45586cb..f2a0a16472 100644 --- a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/Downloader.java +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/Downloader.java @@ -327,7 +327,7 @@ public class Downloader { return CompletableFuture.completedFuture(Collections.emptyList()); } - CompletableFuture> importedBlocks; + final CompletableFuture> importedBlocks; if (checkpointHeaders.size() < 2) { // Download blocks without constraining the end block final ImportBlocksTask importTask = @@ -382,9 +382,7 @@ public class Downloader { chainSegmentTimeouts = 0; final BlockHeader lastImportedCheckpoint = checkpointHeaders.getLast(); checkpointHeaders.clear(); - syncState - .syncTarget() - .ifPresent(target -> target.setCommonAncestor(lastImportedCheckpoint)); + syncState.setCommonAncestor(lastImportedCheckpoint); } }); } @@ -400,9 +398,7 @@ public class Downloader { final BlockHeader lastImportedCheckpointHeader = imported.get(imported.size() - 1); // The first checkpoint header is always present in the blockchain. checkpointHeaders.addFirst(lastImportedCheckpointHeader); - syncState - .syncTarget() - .ifPresent(target -> target.setCommonAncestor(lastImportedCheckpointHeader)); + syncState.setCommonAncestor(lastImportedCheckpointHeader); return imported.size() > 1; } } diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/TrailingPeerLimiter.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/TrailingPeerLimiter.java index 32259ac5ba..01a07f4463 100644 --- a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/TrailingPeerLimiter.java +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/TrailingPeerLimiter.java @@ -15,7 +15,6 @@ package tech.pegasys.pantheon.ethereum.eth.sync; import static org.apache.logging.log4j.LogManager.getLogger; import tech.pegasys.pantheon.ethereum.chain.BlockAddedEvent; -import tech.pegasys.pantheon.ethereum.chain.BlockAddedEvent.EventType; import tech.pegasys.pantheon.ethereum.chain.BlockAddedObserver; import tech.pegasys.pantheon.ethereum.chain.Blockchain; import tech.pegasys.pantheon.ethereum.eth.manager.EthPeer; @@ -74,7 +73,7 @@ public class TrailingPeerLimiter implements BlockAddedObserver { @Override public void onBlockAdded(final BlockAddedEvent event, final Blockchain blockchain) { - if (event.getEventType() != EventType.FORK + if (event.isNewCanonicalHead() && event.getBlock().getHeader().getNumber() % RECHECK_PEERS_WHEN_BLOCK_NUMBER_MULTIPLE_OF == 0) { enforceTrailingPeerLimit(); diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/state/SyncState.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/state/SyncState.java index 38f49833a4..938d1dc72c 100644 --- a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/state/SyncState.java +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/state/SyncState.java @@ -15,26 +15,38 @@ package tech.pegasys.pantheon.ethereum.eth.sync.state; import tech.pegasys.pantheon.ethereum.chain.Blockchain; import tech.pegasys.pantheon.ethereum.core.BlockHeader; import tech.pegasys.pantheon.ethereum.core.SyncStatus; -import tech.pegasys.pantheon.ethereum.eth.manager.EthContext; import tech.pegasys.pantheon.ethereum.eth.manager.EthPeer; +import tech.pegasys.pantheon.ethereum.eth.manager.EthPeers; +import tech.pegasys.pantheon.util.Subscribers; import tech.pegasys.pantheon.util.uint.UInt256; import java.util.Optional; public class SyncState { + private static final long SYNC_TOLERANCE = 5; private final Blockchain blockchain; - private final EthContext ethContext; + private final EthPeers ethPeers; private final long startingBlock; - private final PendingBlocks pendingBlocks; + private boolean lastInSync = true; + private final Subscribers inSyncListeners = new Subscribers<>(); private Optional syncTarget = Optional.empty(); + private long chainHeightListenerId; - public SyncState( - final Blockchain blockchain, final EthContext ethContext, final PendingBlocks pendingBlocks) { + public SyncState(final Blockchain blockchain, final EthPeers ethPeers) { this.blockchain = blockchain; - this.ethContext = ethContext; + this.ethPeers = ethPeers; this.startingBlock = chainHeadNumber(); - this.pendingBlocks = pendingBlocks; + blockchain.observeBlockAdded( + (event, chain) -> { + if (event.isNewCanonicalHead()) { + checkInSync(); + } + }); + } + + public void addInSyncListener(final InSyncListener observer) { + inSyncListeners.subscribe(observer); } public SyncStatus syncStatus() { @@ -53,22 +65,45 @@ public class SyncState { return blockchain.getChainHead().getTotalDifficulty(); } - public PendingBlocks pendingBlocks() { - return pendingBlocks; - } - public Optional syncTarget() { return syncTarget; } public SyncTarget setSyncTarget(final EthPeer peer, final BlockHeader commonAncestor) { - final SyncTarget target = new SyncTarget(peer, commonAncestor); - this.syncTarget = Optional.of(target); - return target; + final SyncTarget syncTarget = new SyncTarget(peer, commonAncestor); + replaceSyncTarget(Optional.of(syncTarget)); + return syncTarget; + } + + public boolean isInSync() { + return syncTarget + .map( + t -> t.estimatedTargetHeight() - blockchain.getChainHeadBlockNumber() <= SYNC_TOLERANCE) + .orElse(true); + } + + public void setCommonAncestor(final BlockHeader commonAncestor) { + syncTarget.ifPresent(target -> target.setCommonAncestor(commonAncestor)); } public void clearSyncTarget() { - this.syncTarget = Optional.empty(); + replaceSyncTarget(Optional.empty()); + } + + private void replaceSyncTarget(final Optional newTarget) { + syncTarget.ifPresent(this::removeEstimatedHeightListener); + syncTarget = newTarget; + newTarget.ifPresent(this::addEstimatedHeightListener); + checkInSync(); + } + + private void removeEstimatedHeightListener(final SyncTarget target) { + target.removePeerChainEstimatedHeightListener(chainHeightListenerId); + } + + private void addEstimatedHeightListener(final SyncTarget target) { + chainHeightListenerId = + target.addPeerChainEstimatedHeightListener(estimatedHeight -> checkInSync()); } public long bestChainHeight() { @@ -79,10 +114,19 @@ public class SyncState { public long bestChainHeight(final long localChainHeight) { return Math.max( localChainHeight, - ethContext - .getEthPeers() - .bestPeer() - .map(p -> p.chainState().getEstimatedHeight()) - .orElse(localChainHeight)); + ethPeers.bestPeer().map(p -> p.chainState().getEstimatedHeight()).orElse(localChainHeight)); + } + + private synchronized void checkInSync() { + final boolean currentSyncStatus = isInSync(); + if (lastInSync != currentSyncStatus) { + lastInSync = currentSyncStatus; + inSyncListeners.forEach(c -> c.onSyncStatusChanged(currentSyncStatus)); + } + } + + @FunctionalInterface + public interface InSyncListener { + void onSyncStatusChanged(boolean newSyncStatus); } } diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/state/SyncTarget.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/state/SyncTarget.java index 549e2c228a..3850907b85 100644 --- a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/state/SyncTarget.java +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/state/SyncTarget.java @@ -14,6 +14,7 @@ package tech.pegasys.pantheon.ethereum.eth.sync.state; import tech.pegasys.pantheon.ethereum.core.BlockHeader; import tech.pegasys.pantheon.ethereum.eth.manager.ChainState; +import tech.pegasys.pantheon.ethereum.eth.manager.ChainState.EstimatedHeightListener; import tech.pegasys.pantheon.ethereum.eth.manager.EthPeer; import com.google.common.base.MoreObjects; @@ -23,7 +24,7 @@ public class SyncTarget { private final EthPeer peer; private BlockHeader commonAncestor; - public SyncTarget(final EthPeer peer, final BlockHeader commonAncestor) { + SyncTarget(final EthPeer peer, final BlockHeader commonAncestor) { this.peer = peer; this.commonAncestor = commonAncestor; } @@ -36,10 +37,22 @@ public class SyncTarget { return commonAncestor; } - public void setCommonAncestor(final BlockHeader commonAncestor) { + void setCommonAncestor(final BlockHeader commonAncestor) { this.commonAncestor = commonAncestor; } + public long addPeerChainEstimatedHeightListener(final EstimatedHeightListener listener) { + return peer.addChainEstimatedHeightListener(listener); + } + + public void removePeerChainEstimatedHeightListener(final long listenerId) { + peer.removeChainEstimatedHeightListener(listenerId); + } + + public long estimatedTargetHeight() { + return peer.chainState().getEstimatedHeight(); + } + @Override public String toString() { final ChainState chainState = peer.chainState(); diff --git a/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/manager/ChainStateTest.java b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/manager/ChainStateTest.java index 6af5b5309e..f6e2458c98 100644 --- a/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/manager/ChainStateTest.java +++ b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/manager/ChainStateTest.java @@ -13,10 +13,14 @@ package tech.pegasys.pantheon.ethereum.eth.manager; 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 tech.pegasys.pantheon.ethereum.core.BlockHeader; import tech.pegasys.pantheon.ethereum.core.BlockHeaderTestFixture; import tech.pegasys.pantheon.ethereum.core.Hash; +import tech.pegasys.pantheon.ethereum.eth.manager.ChainState.EstimatedHeightListener; import tech.pegasys.pantheon.ethereum.testutil.BlockDataGenerator; import tech.pegasys.pantheon.util.uint.UInt256; @@ -228,4 +232,59 @@ public class ChainStateTest { assertThat(chainState.hasEstimatedHeight()).isTrue(); } + + @Test + public void observersInformedWhenHeightUpdatedViaHashAndNumber() { + final long blockNumber = 12; + final BlockHeader bestBlockHeader = + new BlockHeaderTestFixture().number(blockNumber).buildHeader(); + chainState.statusReceived(bestBlockHeader.getHash(), INITIAL_TOTAL_DIFFICULTY); + final EstimatedHeightListener listener = mock(EstimatedHeightListener.class); + chainState.addEstimatedHeightListener(listener); + chainState.update(bestBlockHeader.getHash(), blockNumber); + verify(listener).onEstimatedHeightChanged(blockNumber); + } + + @Test + public void observersInformedWhenHeightUpdatedViaHeader() { + final long blockNumber = 12; + final BlockHeader bestBlockHeader = + new BlockHeaderTestFixture().number(blockNumber).buildHeader(); + chainState.statusReceived(bestBlockHeader.getHash(), INITIAL_TOTAL_DIFFICULTY); + final EstimatedHeightListener listener = mock(EstimatedHeightListener.class); + chainState.addEstimatedHeightListener(listener); + chainState.update(bestBlockHeader); + verify(listener).onEstimatedHeightChanged(blockNumber); + } + + @Test + public void observersInformedWhenHeightUpdatedViaHeaderAndTD() { + final long blockNumber = 12; + final BlockHeader bestBlockHeader = + new BlockHeaderTestFixture().number(blockNumber).buildHeader(); + chainState.statusReceived(bestBlockHeader.getHash(), INITIAL_TOTAL_DIFFICULTY); + final EstimatedHeightListener listener = mock(EstimatedHeightListener.class); + chainState.addEstimatedHeightListener(listener); + chainState.update(bestBlockHeader, INITIAL_TOTAL_DIFFICULTY); + verify(listener).onEstimatedHeightChanged(blockNumber); + } + + @Test + public void observersNotInformedWhenHeightLowers() { + final long blockNumber = 12; + final BlockHeader bestBlockHeader = + new BlockHeaderTestFixture().number(blockNumber).buildHeader(); + chainState.statusReceived(bestBlockHeader.getHash(), INITIAL_TOTAL_DIFFICULTY); + final EstimatedHeightListener listener = mock(EstimatedHeightListener.class); + chainState.addEstimatedHeightListener(listener); + chainState.update(bestBlockHeader); + verify(listener).onEstimatedHeightChanged(blockNumber); + + final long lowerBlockNumber = 12; + final BlockHeader lowerBlockHeader = + new BlockHeaderTestFixture().number(lowerBlockNumber).buildHeader(); + chainState.update(lowerBlockHeader); + + verifyNoMoreInteractions(listener); + } } diff --git a/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/BlockPropagationManagerTest.java b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/BlockPropagationManagerTest.java index c4ed906ca9..9d51def6ba 100644 --- a/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/BlockPropagationManagerTest.java +++ b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/BlockPropagationManagerTest.java @@ -54,6 +54,7 @@ public class BlockPropagationManagerTest { private EthProtocolManager ethProtocolManager; private BlockPropagationManager blockPropagationManager; private SynchronizerConfiguration syncConfig; + private final PendingBlocks pendingBlocks = new PendingBlocks(); private SyncState syncState; @BeforeClass @@ -78,14 +79,15 @@ public class BlockPropagationManagerTest { .blockPropagationRange(-3, 5) .build() .validated(blockchain); - syncState = new SyncState(blockchain, ethProtocolManager.ethContext(), new PendingBlocks()); + syncState = new SyncState(blockchain, ethProtocolManager.ethContext().getEthPeers()); blockPropagationManager = new BlockPropagationManager<>( syncConfig, protocolSchedule, protocolContext, ethProtocolManager.ethContext(), - syncState); + syncState, + pendingBlocks); } @Test @@ -459,7 +461,8 @@ public class BlockPropagationManagerTest { protocolSchedule, protocolContext, ethProtocolManager.ethContext(), - syncState); + syncState, + pendingBlocks); final BlockDataGenerator gen = new BlockDataGenerator(); // Import some blocks @@ -479,20 +482,20 @@ public class BlockPropagationManagerTest { // Check that we pushed our block into the pending collection assertThat(blockchain.contains(blockToPurge.getHash())).isFalse(); - assertThat(syncState.pendingBlocks().contains(blockToPurge.getHash())).isTrue(); + assertThat(pendingBlocks.contains(blockToPurge.getHash())).isTrue(); // Import blocks until we bury the target block far enough to be cleaned up for (int i = 0; i < oldBlocksToImport; i++) { blockchainUtil.importBlockAtIndex((int) blockchain.getChainHeadBlockNumber() + 1); assertThat(blockchain.contains(blockToPurge.getHash())).isFalse(); - assertThat(syncState.pendingBlocks().contains(blockToPurge.getHash())).isTrue(); + assertThat(pendingBlocks.contains(blockToPurge.getHash())).isTrue(); } // Import again to trigger cleanup blockchainUtil.importBlockAtIndex((int) blockchain.getChainHeadBlockNumber() + 1); assertThat(blockchain.contains(blockToPurge.getHash())).isFalse(); - assertThat(syncState.pendingBlocks().contains(blockToPurge.getHash())).isFalse(); + assertThat(pendingBlocks.contains(blockToPurge.getHash())).isFalse(); } @Test diff --git a/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/DownloaderTest.java b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/DownloaderTest.java index f64861237c..d21612a507 100644 --- a/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/DownloaderTest.java +++ b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/DownloaderTest.java @@ -35,7 +35,6 @@ import tech.pegasys.pantheon.ethereum.eth.manager.RespondingEthPeer.Responder; import tech.pegasys.pantheon.ethereum.eth.manager.ethtaskutils.BlockchainSetupUtil; import tech.pegasys.pantheon.ethereum.eth.messages.EthPV62; import tech.pegasys.pantheon.ethereum.eth.messages.GetBlockHeadersMessage; -import tech.pegasys.pantheon.ethereum.eth.sync.state.PendingBlocks; import tech.pegasys.pantheon.ethereum.eth.sync.state.SyncState; import tech.pegasys.pantheon.ethereum.mainnet.ProtocolSchedule; import tech.pegasys.pantheon.ethereum.mainnet.ScheduleBasedBlockHashFunction; @@ -81,7 +80,7 @@ public class DownloaderTest { protocolContext = localBlockchainSetup.getProtocolContext(); ethProtocolManager = EthProtocolManagerTestUtil.create(localBlockchain); ethContext = ethProtocolManager.ethContext(); - syncState = new SyncState(protocolContext.getBlockchain(), ethContext, new PendingBlocks()); + syncState = new SyncState(protocolContext.getBlockchain(), ethContext.getEthPeers()); } private Downloader downloader(final SynchronizerConfiguration syncConfig) { diff --git a/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/state/SyncStateTest.java b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/state/SyncStateTest.java new file mode 100644 index 0000000000..7df6da18a3 --- /dev/null +++ b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/state/SyncStateTest.java @@ -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 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(); + } +} diff --git a/ethereum/jsonrpc/build.gradle b/ethereum/jsonrpc/build.gradle index 5290d27beb..7b030605a1 100644 --- a/ethereum/jsonrpc/build.gradle +++ b/ethereum/jsonrpc/build.gradle @@ -24,6 +24,7 @@ jar { dependencies { implementation project(':crypto') implementation project(':ethereum:core') + implementation project(':ethereum:blockcreation') implementation project(':ethereum:eth') implementation project(':ethereum:p2p') implementation project(':ethereum:rlp') diff --git a/pantheon/build.gradle b/pantheon/build.gradle index f266b5ad64..c70ee072d6 100644 --- a/pantheon/build.gradle +++ b/pantheon/build.gradle @@ -30,6 +30,7 @@ dependencies { implementation project(':consensus:clique') implementation project(':ethereum:eth') implementation project(':ethereum:core') + implementation project(':ethereum:blockcreation') implementation project(':ethereum:rlp') implementation project(':ethereum:p2p') implementation project(':ethereum:jsonrpc') diff --git a/pantheon/src/main/java/tech/pegasys/pantheon/cli/PantheonCommand.java b/pantheon/src/main/java/tech/pegasys/pantheon/cli/PantheonCommand.java index 5b94ee38ab..f735a1d3a0 100644 --- a/pantheon/src/main/java/tech/pegasys/pantheon/cli/PantheonCommand.java +++ b/pantheon/src/main/java/tech/pegasys/pantheon/cli/PantheonCommand.java @@ -20,8 +20,8 @@ import tech.pegasys.pantheon.cli.custom.CorsAllowedOriginsProperty; import tech.pegasys.pantheon.consensus.clique.jsonrpc.CliqueRpcApis; import tech.pegasys.pantheon.consensus.ibft.jsonrpc.IbftRpcApis; import tech.pegasys.pantheon.controller.PantheonController; -import tech.pegasys.pantheon.ethereum.blockcreation.MiningParameters; import tech.pegasys.pantheon.ethereum.core.Address; +import tech.pegasys.pantheon.ethereum.core.MiningParameters; import tech.pegasys.pantheon.ethereum.core.Wei; import tech.pegasys.pantheon.ethereum.eth.sync.SyncMode; import tech.pegasys.pantheon.ethereum.eth.sync.SynchronizerConfiguration; diff --git a/pantheon/src/main/java/tech/pegasys/pantheon/cli/PantheonControllerBuilder.java b/pantheon/src/main/java/tech/pegasys/pantheon/cli/PantheonControllerBuilder.java index bdd04b87f3..ea81a01bc3 100644 --- a/pantheon/src/main/java/tech/pegasys/pantheon/cli/PantheonControllerBuilder.java +++ b/pantheon/src/main/java/tech/pegasys/pantheon/cli/PantheonControllerBuilder.java @@ -18,8 +18,8 @@ import static tech.pegasys.pantheon.controller.KeyPairUtil.loadKeyPair; import tech.pegasys.pantheon.controller.MainnetPantheonController; import tech.pegasys.pantheon.controller.PantheonController; import tech.pegasys.pantheon.crypto.SECP256K1.KeyPair; -import tech.pegasys.pantheon.ethereum.blockcreation.MiningParameters; import tech.pegasys.pantheon.ethereum.chain.GenesisConfig; +import tech.pegasys.pantheon.ethereum.core.MiningParameters; import tech.pegasys.pantheon.ethereum.eth.sync.SynchronizerConfiguration; import java.io.IOException; @@ -46,7 +46,6 @@ public class PantheonControllerBuilder { GenesisConfig.development(), synchronizerConfiguration, miningParameters, - ethNetworkConfig.getNetworkId(), nodeKeys); } else { final String genesisConfig = diff --git a/pantheon/src/main/java/tech/pegasys/pantheon/controller/CliquePantheonController.java b/pantheon/src/main/java/tech/pegasys/pantheon/controller/CliquePantheonController.java index 920aff16d2..bd385fb38c 100644 --- a/pantheon/src/main/java/tech/pegasys/pantheon/controller/CliquePantheonController.java +++ b/pantheon/src/main/java/tech/pegasys/pantheon/controller/CliquePantheonController.java @@ -26,11 +26,11 @@ import tech.pegasys.pantheon.consensus.common.VoteProposer; import tech.pegasys.pantheon.crypto.SECP256K1.KeyPair; import tech.pegasys.pantheon.ethereum.ProtocolContext; import tech.pegasys.pantheon.ethereum.blockcreation.AbstractMiningCoordinator; -import tech.pegasys.pantheon.ethereum.blockcreation.MiningParameters; import tech.pegasys.pantheon.ethereum.chain.GenesisConfig; import tech.pegasys.pantheon.ethereum.chain.MutableBlockchain; import tech.pegasys.pantheon.ethereum.core.BlockHashFunction; import tech.pegasys.pantheon.ethereum.core.Hash; +import tech.pegasys.pantheon.ethereum.core.MiningParameters; import tech.pegasys.pantheon.ethereum.core.Synchronizer; import tech.pegasys.pantheon.ethereum.core.TransactionPool; import tech.pegasys.pantheon.ethereum.core.Util; @@ -41,6 +41,7 @@ import tech.pegasys.pantheon.ethereum.eth.manager.EthProtocolManager; import tech.pegasys.pantheon.ethereum.eth.sync.DefaultSynchronizer; import tech.pegasys.pantheon.ethereum.eth.sync.SyncMode; import tech.pegasys.pantheon.ethereum.eth.sync.SynchronizerConfiguration; +import tech.pegasys.pantheon.ethereum.eth.sync.state.SyncState; import tech.pegasys.pantheon.ethereum.eth.transactions.TransactionPoolFactory; import tech.pegasys.pantheon.ethereum.mainnet.ProtocolSchedule; import tech.pegasys.pantheon.ethereum.mainnet.ScheduleBasedBlockHashFunction; @@ -140,16 +141,23 @@ public class CliquePantheonController genesisConfig.getChainId(), fastSyncEnabled, networkId); + final SyncState syncState = + new SyncState( + protocolContext.getBlockchain(), ethProtocolManager.ethContext().getEthPeers()); final Synchronizer synchronizer = new DefaultSynchronizer<>( - syncConfig, protocolSchedule, protocolContext, ethProtocolManager.ethContext()); + syncConfig, + protocolSchedule, + protocolContext, + ethProtocolManager.ethContext(), + syncState); final TransactionPool transactionPool = TransactionPoolFactory.createTransactionPool( protocolSchedule, protocolContext, ethProtocolManager.ethContext()); final ExecutorService minerThreadPool = Executors.newCachedThreadPool(); - CliqueMinerExecutor miningExecutor = + final CliqueMinerExecutor miningExecutor = new CliqueMinerExecutor( protocolContext, minerThreadPool, @@ -163,8 +171,8 @@ public class CliquePantheonController Util.publicKeyToAddress(nodeKeys.getPublicKey()), secondsBetweenBlocks), epochManger); - CliqueMiningCoordinator miningCoordinator = - new CliqueMiningCoordinator(blockchain, miningExecutor); + final CliqueMiningCoordinator miningCoordinator = + new CliqueMiningCoordinator(blockchain, miningExecutor, syncState); miningCoordinator.addMinedBlockObserver(ethProtocolManager); // Clique mining is implicitly enabled. diff --git a/pantheon/src/main/java/tech/pegasys/pantheon/controller/IbftPantheonController.java b/pantheon/src/main/java/tech/pegasys/pantheon/controller/IbftPantheonController.java index 29591405b1..b1c64c5e36 100644 --- a/pantheon/src/main/java/tech/pegasys/pantheon/controller/IbftPantheonController.java +++ b/pantheon/src/main/java/tech/pegasys/pantheon/controller/IbftPantheonController.java @@ -46,6 +46,7 @@ import tech.pegasys.pantheon.ethereum.eth.manager.EthProtocolManager; import tech.pegasys.pantheon.ethereum.eth.sync.DefaultSynchronizer; import tech.pegasys.pantheon.ethereum.eth.sync.SyncMode; import tech.pegasys.pantheon.ethereum.eth.sync.SynchronizerConfiguration; +import tech.pegasys.pantheon.ethereum.eth.sync.state.SyncState; import tech.pegasys.pantheon.ethereum.eth.transactions.TransactionPoolFactory; import tech.pegasys.pantheon.ethereum.mainnet.ProtocolSchedule; import tech.pegasys.pantheon.ethereum.mainnet.ScheduleBasedBlockHashFunction; @@ -140,8 +141,8 @@ public class IbftPantheonController implements PantheonController( - syncConfig, protocolSchedule, protocolContext, ethProtocolManager.ethContext()); + syncConfig, + protocolSchedule, + protocolContext, + ethProtocolManager.ethContext(), + syncState); final IbftEventQueue ibftEventQueue = new IbftEventQueue(); diff --git a/pantheon/src/main/java/tech/pegasys/pantheon/controller/MainnetPantheonController.java b/pantheon/src/main/java/tech/pegasys/pantheon/controller/MainnetPantheonController.java index 5f8312da72..5eb207ab37 100644 --- a/pantheon/src/main/java/tech/pegasys/pantheon/controller/MainnetPantheonController.java +++ b/pantheon/src/main/java/tech/pegasys/pantheon/controller/MainnetPantheonController.java @@ -20,11 +20,11 @@ import tech.pegasys.pantheon.ethereum.blockcreation.DefaultBlockScheduler; import tech.pegasys.pantheon.ethereum.blockcreation.EthHashBlockMiner; import tech.pegasys.pantheon.ethereum.blockcreation.EthHashMinerExecutor; import tech.pegasys.pantheon.ethereum.blockcreation.EthHashMiningCoordinator; -import tech.pegasys.pantheon.ethereum.blockcreation.MiningParameters; import tech.pegasys.pantheon.ethereum.chain.GenesisConfig; import tech.pegasys.pantheon.ethereum.chain.MutableBlockchain; import tech.pegasys.pantheon.ethereum.core.BlockHashFunction; import tech.pegasys.pantheon.ethereum.core.Hash; +import tech.pegasys.pantheon.ethereum.core.MiningParameters; import tech.pegasys.pantheon.ethereum.core.Synchronizer; import tech.pegasys.pantheon.ethereum.core.TransactionPool; import tech.pegasys.pantheon.ethereum.db.DefaultMutableBlockchain; @@ -34,6 +34,7 @@ import tech.pegasys.pantheon.ethereum.eth.manager.EthProtocolManager; import tech.pegasys.pantheon.ethereum.eth.sync.DefaultSynchronizer; import tech.pegasys.pantheon.ethereum.eth.sync.SyncMode; import tech.pegasys.pantheon.ethereum.eth.sync.SynchronizerConfiguration; +import tech.pegasys.pantheon.ethereum.eth.sync.state.SyncState; import tech.pegasys.pantheon.ethereum.eth.transactions.TransactionPoolFactory; import tech.pegasys.pantheon.ethereum.mainnet.MainnetBlockHeaderValidator; import tech.pegasys.pantheon.ethereum.mainnet.ProtocolSchedule; @@ -97,7 +98,6 @@ public class MainnetPantheonController implements PantheonController genesisConfig, final SynchronizerConfiguration taintedSyncConfig, final MiningParameters miningParams, - final int networkId, final KeyPair nodeKeys) throws IOException { final RocksDbKeyValueStorage kv = @@ -131,9 +130,16 @@ public class MainnetPantheonController implements PantheonController( - syncConfig, protocolSchedule, protocolContext, ethProtocolManager.ethContext()); + syncConfig, + protocolSchedule, + protocolContext, + ethProtocolManager.ethContext(), + syncState); final TransactionPool transactionPool = TransactionPoolFactory.createTransactionPool( @@ -153,7 +159,7 @@ public class MainnetPantheonController implements PantheonController