Coverage Summary for Class: MinerServerImpl (co.rsk.mine)

Class Method, % Line, %
MinerServerImpl 0% (0/30) 0% (0/161)
MinerServerImpl$1 0% (0/2) 0% (0/2)
MinerServerImpl$NewBlockListener 0% (0/3) 0% (0/10)
MinerServerImpl$RefreshBlock 0% (0/2) 0% (0/6)
Total 0% (0/37) 0% (0/179)


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.bitcoinj.core.BtcBlock; 22 import co.rsk.bitcoinj.core.BtcTransaction; 23 import co.rsk.config.MiningConfig; 24 import co.rsk.config.RskMiningConstants; 25 import co.rsk.config.RskSystemProperties; 26 import co.rsk.core.Coin; 27 import co.rsk.core.RskAddress; 28 import co.rsk.core.bc.MiningMainchainView; 29 import co.rsk.crypto.Keccak256; 30 import co.rsk.net.BlockProcessor; 31 import co.rsk.panic.PanicProcessor; 32 import co.rsk.util.DifficultyUtils; 33 import co.rsk.util.ListArrayUtil; 34 import co.rsk.validators.ProofOfWorkRule; 35 import com.google.common.annotations.VisibleForTesting; 36 import org.bouncycastle.crypto.digests.SHA256Digest; 37 import org.bouncycastle.util.Arrays; 38 import org.ethereum.config.blockchain.upgrades.ActivationConfig; 39 import org.ethereum.core.*; 40 import org.ethereum.facade.Ethereum; 41 import org.ethereum.listener.EthereumListenerAdapter; 42 import org.ethereum.rpc.TypeConverter; 43 import org.ethereum.util.BuildInfo; 44 import org.ethereum.util.RLP; 45 import org.ethereum.util.RLPList; 46 import org.slf4j.Logger; 47 import org.slf4j.LoggerFactory; 48  49 import javax.annotation.Nonnull; 50 import javax.annotation.concurrent.GuardedBy; 51 import java.math.BigDecimal; 52 import java.math.BigInteger; 53 import java.util.*; 54 import java.util.concurrent.TimeUnit; 55 import java.util.function.Function; 56  57 /** 58  * The MinerServer provides support to components that perform the actual mining. 59  * It builds blocks to mine and publishes blocks once a valid nonce was found by the miner. 60  * 61  * @author Oscar Guindzberg 62  */ 63  64 public class MinerServerImpl implements MinerServer { 65  private static final long DELAY_BETWEEN_BUILD_BLOCKS_MS = TimeUnit.MINUTES.toMillis(1); 66  67  private static final Logger logger = LoggerFactory.getLogger("minerserver"); 68  private static final PanicProcessor panicProcessor = new PanicProcessor(); 69  70  private static final int CACHE_SIZE = 20; 71  72  private static final int EXTRA_DATA_MAX_SIZE = 32; 73  private static final int EXTRA_DATA_VERSION = 1; 74  75  private final Ethereum ethereum; 76  private final MiningMainchainView mainchainView; 77  private final ProofOfWorkRule powRule; 78  private final BlockToMineBuilder builder; 79  private final ActivationConfig activationConfig; 80  private final MinerClock clock; 81  private final BlockFactory blockFactory; 82  83  private Timer refreshWorkTimer; 84  private NewBlockListener blockListener; 85  86  private boolean started; 87  88  private byte[] extraData; 89  90  @GuardedBy("lock") 91  private LinkedHashMap<Keccak256, Block> blocksWaitingforPoW; 92  @GuardedBy("lock") 93  private Keccak256 latestParentHash; 94  @GuardedBy("lock") 95  private Block latestBlock; 96  @GuardedBy("lock") 97  private Coin latestPaidFeesWithNotify; 98  @GuardedBy("lock") 99  private volatile MinerWork currentWork; // This variable can be read at anytime without the lock. 100  private final Object lock = new Object(); 101  102  private final RskAddress coinbaseAddress; 103  private final BigDecimal minFeesNotifyInDollars; 104  private final BigDecimal gasUnitInDollars; 105  106  private final BlockProcessor nodeBlockProcessor; 107  108  public MinerServerImpl( 109  RskSystemProperties config, 110  Ethereum ethereum, 111  MiningMainchainView mainchainView, 112  BlockProcessor nodeBlockProcessor, 113  ProofOfWorkRule powRule, 114  BlockToMineBuilder builder, 115  MinerClock clock, 116  BlockFactory blockFactory, 117  BuildInfo buildInfo, 118  MiningConfig miningConfig) { 119  this.ethereum = ethereum; 120  this.mainchainView = mainchainView; 121  this.nodeBlockProcessor = nodeBlockProcessor; 122  this.powRule = powRule; 123  this.builder = builder; 124  this.clock = clock; 125  this.blockFactory = blockFactory; 126  this.activationConfig = config.getActivationConfig(); 127  128  blocksWaitingforPoW = createNewBlocksWaitingList(); 129  130  latestPaidFeesWithNotify = Coin.ZERO; 131  latestParentHash = null; 132  coinbaseAddress = miningConfig.getCoinbaseAddress(); 133  minFeesNotifyInDollars = BigDecimal.valueOf(miningConfig.getMinFeesNotifyInDollars()); 134  gasUnitInDollars = BigDecimal.valueOf(miningConfig.getGasUnitInDollars()); 135  136  extraData = buildExtraData(config, buildInfo); 137  } 138  139  private byte[] buildExtraData(RskSystemProperties config, BuildInfo buildInfo) { 140  String identity = config.projectVersionModifier() + "-" + buildInfo.getBuildHash(); 141  return RLP.encodeList(RLP.encodeElement(RLP.encodeInt(EXTRA_DATA_VERSION)), RLP.encodeString(identity)); 142  } 143  144  private LinkedHashMap<Keccak256, Block> createNewBlocksWaitingList() { 145  return new LinkedHashMap<Keccak256, Block>(CACHE_SIZE) { 146  @Override 147  protected boolean removeEldestEntry(Map.Entry<Keccak256, Block> eldest) { 148  return size() > CACHE_SIZE; 149  } 150  }; 151  152  } 153  154  @VisibleForTesting 155  public Map<Keccak256, Block> getBlocksWaitingforPoW() { 156  return blocksWaitingforPoW; 157  } 158  159  @Override 160  public boolean isRunning() { 161  return started; 162  } 163  164  @Override 165  public void stop() { 166  if (!started) { 167  return; 168  } 169  170  synchronized (lock) { 171  started = false; 172  ethereum.removeListener(blockListener); 173  if (refreshWorkTimer != null) { 174  refreshWorkTimer.cancel(); 175  refreshWorkTimer = null; 176  } 177  } 178  } 179  180  @Override 181  public void start() { 182  if (started) { 183  return; 184  } 185  186  synchronized (lock) { 187  started = true; 188  blockListener = new NewBlockListener(); 189  ethereum.addListener(blockListener); 190  buildBlockToMine(false); 191  192  if (refreshWorkTimer != null) { 193  refreshWorkTimer.cancel(); 194  } 195  196  refreshWorkTimer = new Timer("Refresh work for mining"); 197  refreshWorkTimer.schedule(new RefreshBlock(), DELAY_BETWEEN_BUILD_BLOCKS_MS, DELAY_BETWEEN_BUILD_BLOCKS_MS); 198  } 199  } 200  201  @Override 202  public SubmitBlockResult submitBitcoinBlockPartialMerkle( 203  String blockHashForMergedMining, 204  BtcBlock blockWithHeaderOnly, 205  BtcTransaction coinbase, 206  List<String> merkleHashes, 207  int blockTxnCount) { 208  logger.debug("Received merkle solution with hash {} for merged mining", blockHashForMergedMining); 209  210  return processSolution( 211  blockHashForMergedMining, 212  blockWithHeaderOnly, 213  coinbase, 214  (pb) -> pb.buildFromMerkleHashes(blockWithHeaderOnly, merkleHashes, blockTxnCount), 215  true 216  ); 217  } 218  219  @Override 220  public SubmitBlockResult submitBitcoinBlockTransactions( 221  String blockHashForMergedMining, 222  BtcBlock blockWithHeaderOnly, 223  BtcTransaction coinbase, 224  List<String> txHashes) { 225  logger.debug("Received tx solution with hash {} for merged mining", blockHashForMergedMining); 226  227  return processSolution( 228  blockHashForMergedMining, 229  blockWithHeaderOnly, 230  coinbase, 231  (pb) -> pb.buildFromTxHashes(blockWithHeaderOnly, txHashes), 232  true 233  ); 234  } 235  236  @Override 237  public SubmitBlockResult submitBitcoinBlock(String blockHashForMergedMining, BtcBlock bitcoinMergedMiningBlock) { 238  return submitBitcoinBlock(blockHashForMergedMining, bitcoinMergedMiningBlock, true); 239  } 240  241  SubmitBlockResult submitBitcoinBlock(String blockHashForMergedMining, BtcBlock bitcoinMergedMiningBlock, boolean lastTag) { 242  logger.debug("Received block with hash {} for merged mining", blockHashForMergedMining); 243  244  return processSolution( 245  blockHashForMergedMining, 246  bitcoinMergedMiningBlock, 247  bitcoinMergedMiningBlock.getTransactions().get(0), 248  (pb) -> pb.buildFromBlock(bitcoinMergedMiningBlock), 249  lastTag 250  ); 251  } 252  253  private SubmitBlockResult processSolution( 254  String blockHashForMergedMining, 255  BtcBlock blockWithHeaderOnly, 256  BtcTransaction coinbase, 257  Function<MerkleProofBuilder, byte[]> proofBuilderFunction, 258  boolean lastTag) { 259  Block newBlock; 260  Keccak256 key = new Keccak256(TypeConverter.removeZeroX(blockHashForMergedMining)); 261  262  synchronized (lock) { 263  Block workingBlock = blocksWaitingforPoW.get(key); 264  265  if (workingBlock == null) { 266  String message = "Cannot publish block, could not find hash " + blockHashForMergedMining + " in the cache"; 267  logger.warn(message); 268  269  return new SubmitBlockResult("ERROR", message); 270  } 271  272  newBlock = blockFactory.cloneBlockForModification(workingBlock); 273  274  logger.debug("blocksWaitingForPoW size {}", blocksWaitingforPoW.size()); 275  } 276  277  logger.info("Received block {} {}", newBlock.getNumber(), newBlock.getHash()); 278  279  newBlock.setBitcoinMergedMiningHeader(blockWithHeaderOnly.cloneAsHeader().bitcoinSerialize()); 280  newBlock.setBitcoinMergedMiningCoinbaseTransaction(compressCoinbase(coinbase.bitcoinSerialize(), lastTag)); 281  newBlock.setBitcoinMergedMiningMerkleProof(MinerUtils.buildMerkleProof(activationConfig, proofBuilderFunction, newBlock.getNumber())); 282  newBlock.seal(); 283  284  if (!isValid(newBlock)) { 285  String message = "Invalid block supplied by miner: " + newBlock.getPrintableHash() + " " + newBlock.getPrintableHashForMergedMining() + " at height " + newBlock.getNumber(); 286  logger.error(message); 287  288  return new SubmitBlockResult("ERROR", message); 289  } else { 290  ImportResult importResult = ethereum.addNewMinedBlock(newBlock); 291  292  logger.info("Mined block import result is {}: {} {} at height {}", importResult, newBlock.getPrintableHash(), newBlock.getPrintableHashForMergedMining(), newBlock.getNumber()); 293  SubmittedBlockInfo blockInfo = new SubmittedBlockInfo(importResult, newBlock.getHash().getBytes(), newBlock.getNumber()); 294  295  return new SubmitBlockResult("OK", "OK", blockInfo); 296  } 297  } 298  299  private boolean isValid(Block block) { 300  try { 301  return powRule.isValid(block); 302  } catch (Exception e) { 303  logger.error("Failed to validate PoW from block {}: {}", block.getPrintableHash(), e); 304  return false; 305  } 306  } 307  308  public static byte[] compressCoinbase(byte[] bitcoinMergedMiningCoinbaseTransactionSerialized) { 309  return compressCoinbase(bitcoinMergedMiningCoinbaseTransactionSerialized, true); 310  } 311  312  public static byte[] compressCoinbase(byte[] bitcoinMergedMiningCoinbaseTransactionSerialized, boolean lastOccurrence) { 313  List<Byte> coinBaseTransactionSerializedAsList = ListArrayUtil.asByteList(bitcoinMergedMiningCoinbaseTransactionSerialized); 314  List<Byte> tagAsList = ListArrayUtil.asByteList(RskMiningConstants.RSK_TAG); 315  316  int rskTagPosition; 317  if (lastOccurrence) { 318  rskTagPosition = Collections.lastIndexOfSubList(coinBaseTransactionSerializedAsList, tagAsList); 319  } else { 320  rskTagPosition = Collections.indexOfSubList(coinBaseTransactionSerializedAsList, tagAsList); 321  } 322  323  int sha256Blocks = rskTagPosition / 64; 324  int bytesToHash = sha256Blocks * 64; 325  SHA256Digest digest = new SHA256Digest(); 326  digest.update(bitcoinMergedMiningCoinbaseTransactionSerialized, 0, bytesToHash); 327  byte[] hashedContent = digest.getEncodedState(); 328  byte[] trimmedHashedContent = new byte[RskMiningConstants.MIDSTATE_SIZE_TRIMMED]; 329  System.arraycopy(hashedContent, 8, trimmedHashedContent, 0, RskMiningConstants.MIDSTATE_SIZE_TRIMMED); 330  byte[] unHashedContent = new byte[bitcoinMergedMiningCoinbaseTransactionSerialized.length - bytesToHash]; 331  System.arraycopy(bitcoinMergedMiningCoinbaseTransactionSerialized, bytesToHash, unHashedContent, 0, unHashedContent.length); 332  return Arrays.concatenate(trimmedHashedContent, unHashedContent); 333  } 334  335  @Override 336  public RskAddress getCoinbaseAddress() { 337  return coinbaseAddress; 338  } 339  340  /** 341  * getWork returns the latest MinerWork for miners. Subsequent calls to this function with no new work will return 342  * currentWork with the notify flag turned off. (they will be different objects too). 343  * 344  * This method must be called with MinerServer started. That and the fact that work is never set to null 345  * will ensure that currentWork is not null. 346  * 347  * @return the latest MinerWork available. 348  */ 349  @Override 350  public MinerWork getWork() { 351  MinerWork work = currentWork; 352  353  if (work.getNotify()) { 354  /** 355  * Set currentWork.notify to false for the next time this function is called. 356  * By doing it this way, we avoid taking the lock every time, we just take it once per MinerWork. 357  * We have to take the lock to reassign currentWork, but it might have happened that 358  * the currentWork got updated when we acquired the lock. In that case, we should just return the new 359  * currentWork, regardless of what it is. 360  */ 361  synchronized (lock) { 362  if (currentWork != work) { 363  return currentWork; 364  } 365  currentWork = new MinerWork(currentWork.getBlockHashForMergedMining(), currentWork.getTarget(), 366  currentWork.getFeesPaidToMiner(), false, currentWork.getParentBlockHash()); 367  } 368  } 369  return work; 370  } 371  372  @VisibleForTesting 373  public void setWork(MinerWork work) { 374  this.currentWork = work; 375  } 376  377  public MinerWork updateGetWork(@Nonnull final Block block, @Nonnull final boolean notify) { 378  Keccak256 blockMergedMiningHash = new Keccak256(block.getHashForMergedMining()); 379  380  BigInteger targetBI = DifficultyUtils.difficultyToTarget(block.getDifficulty()); 381  byte[] targetUnknownLengthArray = targetBI.toByteArray(); 382  byte[] targetArray = new byte[32]; 383  System.arraycopy(targetUnknownLengthArray, 0, targetArray, 32 - targetUnknownLengthArray.length, targetUnknownLengthArray.length); 384  385  logger.debug("Sending work for merged mining. Hash: {}", block.getPrintableHashForMergedMining()); 386  return new MinerWork(blockMergedMiningHash.toJsonString(), TypeConverter.toJsonHex(targetArray), String.valueOf(block.getFeesPaidToMiner()), notify, block.getParentHashJsonString()); 387  } 388  389  public void setExtraData(byte[] clientExtraData) { 390  RLPList decodedExtraData = RLP.decodeList(this.extraData); 391  byte[] version = decodedExtraData.get(0).getRLPData(); 392  byte[] identity = decodedExtraData.get(1).getRLPData(); 393  394  int rlpClientExtraDataEncodingOverhead = 3; 395  int clientExtraDataSize = EXTRA_DATA_MAX_SIZE 396  - (version != null ? version.length : 0) 397  - (identity != null ? identity.length : 0) 398  - rlpClientExtraDataEncodingOverhead; 399  byte[] clientExtraDataResized = Arrays.copyOf(clientExtraData, Math.min(clientExtraData.length, clientExtraDataSize)); 400  401  this.extraData = RLP.encodeList(version, RLP.encode(identity), RLP.encodeElement(clientExtraDataResized)); 402  } 403  404  @VisibleForTesting 405  public byte[] getExtraData() { 406  return Arrays.copyOf(extraData, extraData.length); 407  } 408  409  /** 410  * buildBlockToMine creates a block to mine using the block received as parent. 411  * This method calls buildBlockToMine and that one uses the internal mainchainView 412  * Hence, mainchainView must be updated to reflect the new mainchain status. 413  * Note. This method is NOT intended to be used in any part of the mining flow and 414  * is only here to be consumed from SnapshotManager. 415  * 416  * @param blockToMineOnTopOf parent of the block to be built. 417  * @param createCompetitiveBlock used for testing. 418  */ 419  @Override 420  public void buildBlockToMine(@Nonnull Block blockToMineOnTopOf, boolean createCompetitiveBlock) { 421  mainchainView.addBest(blockToMineOnTopOf.getHeader()); 422  buildBlockToMine(createCompetitiveBlock); 423  } 424  425  /** 426  * buildBlockToMine creates a block to mine using the current best block as parent. 427  * best block is obtained from a blockchain view that has the latest mainchain blocks. 428  * 429  * @param createCompetitiveBlock used for testing. 430  */ 431  @Override 432  public void buildBlockToMine(boolean createCompetitiveBlock) { 433  BlockHeader newBlockParentHeader = mainchainView.get().get(0); 434  // See BlockChainImpl.calclBloom() if blocks has txs 435  if (createCompetitiveBlock) { 436  // Just for testing, mine on top of best block's parent 437  newBlockParentHeader = mainchainView.get().get(1); 438  } 439  440  logger.info("Starting block to mine from parent {} {}", newBlockParentHeader.getNumber(), newBlockParentHeader.getHash()); 441  442  List<BlockHeader> mainchainHeaders = mainchainView.get(); 443  final Block newBlock = builder.build(mainchainHeaders, extraData).getBlock(); 444  clock.clearIncreaseTime(); 445  446  synchronized (lock) { 447  Keccak256 parentHash = newBlockParentHeader.getHash(); 448  boolean notify = this.getNotify(newBlock, parentHash); 449  450  if (notify) { 451  latestPaidFeesWithNotify = newBlock.getFeesPaidToMiner(); 452  } 453  454  latestParentHash = parentHash; 455  latestBlock = newBlock; 456  457  currentWork = updateGetWork(newBlock, notify); 458  Keccak256 latestBlockHashWaitingForPoW = new Keccak256(newBlock.getHashForMergedMining()); 459  460  blocksWaitingforPoW.put(latestBlockHashWaitingForPoW, latestBlock); 461  logger.debug("blocksWaitingForPoW size {}", blocksWaitingforPoW.size()); 462  } 463  464  logger.debug("Built block {}. Parent {}", newBlock.getPrintableHashForMergedMining(), newBlockParentHeader.getPrintableHashForMergedMining()); 465  for (BlockHeader uncleHeader : newBlock.getUncleList()) { 466  logger.debug("With uncle {}", uncleHeader.getPrintableHashForMergedMining()); 467  } 468  } 469  470  /** 471  * getNotifies determines whether miners should be notified or not. (Used for mining pools). 472  * 473  * @param block the block to mine. 474  * @param parentHash block's parent hash. 475  * @return true if miners should be notified about this new block to mine. 476  */ 477  @GuardedBy("lock") 478  private boolean getNotify(Block block, Keccak256 parentHash) { 479  if (!parentHash.equals(latestParentHash)) { 480  return true; 481  } 482  483  // note: integer divisions might truncate values 484  BigInteger percentage = BigInteger.valueOf(100L + RskMiningConstants.NOTIFY_FEES_PERCENTAGE_INCREASE); 485  Coin minFeesNotify = latestPaidFeesWithNotify.multiply(percentage).divide(BigInteger.valueOf(100L)); 486  Coin feesPaidToMiner = block.getFeesPaidToMiner(); 487  BigDecimal feesPaidToMinerInDollars = new BigDecimal(feesPaidToMiner.asBigInteger()).multiply(gasUnitInDollars); 488  return feesPaidToMiner.compareTo(minFeesNotify) > 0 489  && feesPaidToMinerInDollars.compareTo(minFeesNotifyInDollars) >= 0; 490  491  } 492  493  @Override 494  public Optional<Block> getLatestBlock() { 495  return Optional.ofNullable(latestBlock); 496  } 497  498  class NewBlockListener extends EthereumListenerAdapter { 499  500  @Override 501  // This event executes in the thread context of the caller. 502  // In case of private miner, it's the "Private Mining timer" task 503  public void onBestBlock(Block newBestBlock, List<TransactionReceipt> receipts) { 504  if (isSyncing()) { 505  return; 506  } 507  508  logger.trace("Start onBestBlock"); 509  510  logger.debug( 511  "There is a new best block: {}, number: {}", 512  newBestBlock.getPrintableHashForMergedMining(), 513  newBestBlock.getNumber()); 514  mainchainView.addBest(newBestBlock.getHeader()); 515  buildBlockToMine(false); 516  517  logger.trace("End onBestBlock"); 518  } 519  520  private boolean isSyncing() { 521  return nodeBlockProcessor.hasBetterBlockToSync(); 522  } 523  } 524  525  /** 526  * RefreshBlocks rebuilds the block to mine. 527  */ 528  private class RefreshBlock extends TimerTask { 529  @Override 530  public void run() { 531  try { 532  buildBlockToMine(false); 533  } catch (Throwable th) { 534  logger.error("Unexpected error: {}", th); 535  panicProcessor.panic("mserror", th.getMessage()); 536  } 537  } 538  } 539 }