Coverage Summary for Class: MinerClientImpl (co.rsk.mine)
Class |
Method, %
|
Line, %
|
MinerClientImpl |
0%
(0/11)
|
0%
(0/61)
|
MinerClientImpl$1 |
0%
(0/2)
|
0%
(0/5)
|
MinerClientImpl$RefreshWork |
0%
(0/2)
|
0%
(0/7)
|
Total |
0%
(0/15)
|
0%
(0/73)
|
1 /*
2 * This file is part of RskJ
3 * Copyright (C) 2017 RSK Labs Ltd.
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU Lesser General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19 package co.rsk.mine;
20
21 import co.rsk.net.NodeBlockProcessor;
22 import co.rsk.panic.PanicProcessor;
23 import org.ethereum.rpc.TypeConverter;
24 import org.slf4j.Logger;
25 import org.slf4j.LoggerFactory;
26
27 import javax.annotation.Nonnull;
28 import java.math.BigInteger;
29 import java.time.Duration;
30 import java.util.Timer;
31 import java.util.TimerTask;
32
33 /**
34 * MinerClient mines new blocks.
35 * In fact it just performs the proof-of-work needed to find a valid block and uses
36 * uses MinerServer to build blocks to mine and publish blocks once a valid nonce was found.
37 * @author Oscar Guindzberg
38 */
39 public class MinerClientImpl implements MinerClient {
40 private long nextNonceToUse = 0;
41
42 private static final Logger logger = LoggerFactory.getLogger("minerClient");
43 private static final PanicProcessor panicProcessor = new PanicProcessor();
44
45 private final NodeBlockProcessor nodeBlockProcessor;
46 private final MinerServer minerServer;
47 private final Duration delayBetweenBlocks;
48 private final Duration delayBetweenRefreshes;
49
50 private volatile boolean stop = false;
51
52 private volatile boolean isMining = false;
53
54 private volatile boolean newBestBlockArrivedFromAnotherNode = false;
55
56 private volatile MinerWork work;
57 private Timer aTimer;
58
59 public MinerClientImpl(NodeBlockProcessor nodeBlockProcessor, MinerServer minerServer, Duration delayBetweenBlocks, Duration delayBetweenRefreshes) {
60 this.nodeBlockProcessor = nodeBlockProcessor;
61 this.minerServer = minerServer;
62 this.delayBetweenBlocks = delayBetweenBlocks;
63 this.delayBetweenRefreshes = delayBetweenRefreshes;
64 }
65
66 @Override
67 public void start() {
68 aTimer = new Timer("Refresh work for mining");
69 aTimer.schedule(createRefreshWork(), 0, this.delayBetweenRefreshes.toMillis());
70
71 Thread doWorkThread = this.createDoWorkThread();
72 doWorkThread.start();
73 }
74
75 public RefreshWork createRefreshWork() {
76 return new RefreshWork();
77 }
78
79 public Thread createDoWorkThread() {
80 return new Thread("miner client") {
81 @Override
82 public void run() {
83 isMining = true;
84
85 while (!stop) {
86 doWork();
87 }
88
89 isMining = false;
90 }
91 };
92 }
93
94 public boolean isMining() {
95 return this.isMining;
96 }
97
98 public void doWork() {
99 try {
100 if (mineBlock()) {
101 if (!this.delayBetweenBlocks.isZero()) {
102 Thread.sleep(this.delayBetweenBlocks.toMillis());
103 }
104 }
105 } catch (Exception e) {
106 logger.error("Error on mining", e);
107 panicProcessor.panic("mine", e.getMessage());
108 }
109 }
110
111 @Override
112 public boolean mineBlock() {
113 if (this.nodeBlockProcessor != null) {
114 if (this.nodeBlockProcessor.hasBetterBlockToSync()) {
115 try {
116 Thread.sleep(10000);
117 } catch (InterruptedException ex) {
118 logger.error("Interrupted mining sleep", ex);
119 }
120 return false;
121 }
122 }
123
124 newBestBlockArrivedFromAnotherNode = false;
125 work = minerServer.getWork();
126
127 co.rsk.bitcoinj.core.NetworkParameters bitcoinNetworkParameters = co.rsk.bitcoinj.params.RegTestParams.get();
128 co.rsk.bitcoinj.core.BtcTransaction bitcoinMergedMiningCoinbaseTransaction = MinerUtils.getBitcoinMergedMiningCoinbaseTransaction(bitcoinNetworkParameters, work);
129 co.rsk.bitcoinj.core.BtcBlock bitcoinMergedMiningBlock = MinerUtils.getBitcoinMergedMiningBlock(bitcoinNetworkParameters, bitcoinMergedMiningCoinbaseTransaction);
130
131 BigInteger target = new BigInteger(1, TypeConverter.stringHexToByteArray(work.getTarget()));
132 boolean foundNonce = findNonce(bitcoinMergedMiningBlock, target);
133
134 if (newBestBlockArrivedFromAnotherNode) {
135 logger.info("Interrupted mining because another best block arrived");
136 }
137
138 if (stop) {
139 logger.info("Interrupted mining because MinerClient was stopped");
140 }
141
142 if (foundNonce) {
143 logger.info("Mined block: {}", work.getBlockHashForMergedMining());
144 minerServer.submitBitcoinBlock(work.getBlockHashForMergedMining(), bitcoinMergedMiningBlock);
145 }
146
147 return foundNonce;
148 }
149
150 /**
151 * findNonce will try to find a valid nonce for bitcoinMergedMiningBlock, that satisfies the given target difficulty.
152 *
153 * @param bitcoinMergedMiningBlock bitcoinBlock to find nonce for. This block's nonce will be modified.
154 * @param target target difficulty. Block's hash should be lower than this number.
155 * @return true if a nonce was found, false otherwise.
156 * @remarks This method will return if the stop or newBetBlockArrivedFromAnotherNode intance variables are set to true.
157 */
158 private boolean findNonce(@Nonnull final co.rsk.bitcoinj.core.BtcBlock bitcoinMergedMiningBlock,
159 @Nonnull final BigInteger target) {
160 bitcoinMergedMiningBlock.setNonce(nextNonceToUse++);
161
162 while (!stop && !newBestBlockArrivedFromAnotherNode) {
163 // Is our proof of work valid yet?
164 BigInteger blockHashBI = bitcoinMergedMiningBlock.getHash().toBigInteger();
165 if (blockHashBI.compareTo(target) <= 0) {
166 return true;
167 }
168 // No, so increment the nonce and try again.
169 bitcoinMergedMiningBlock.setNonce(nextNonceToUse++);
170 if (bitcoinMergedMiningBlock.getNonce() % 100000 == 0) {
171 logger.debug("Solving block. Nonce: {}", bitcoinMergedMiningBlock.getNonce());
172 }
173 }
174
175 return false; // couldn't find a valid nonce
176 }
177
178 @Override
179 public void stop() {
180 stop = true;
181
182 if (aTimer!=null) {
183 aTimer.cancel();
184 }
185 }
186
187 /**
188 * RefreshWork asks the minerServer for new work.
189 */
190 public class RefreshWork extends TimerTask {
191 @Override
192 public void run() {
193 MinerWork receivedWork = minerServer.getWork();
194 MinerWork previousWork = work;
195 if (previousWork != null && receivedWork != null &&
196 !receivedWork.getBlockHashForMergedMining().equals(previousWork.getBlockHashForMergedMining())) {
197 newBestBlockArrivedFromAnotherNode = true;
198 logger.debug("There is a new best block: {}", receivedWork.getBlockHashForMergedMining());
199 }
200 }
201 }
202 }