Coverage Summary for Class: AsyncNodeBlockProcessor (co.rsk.net)

Class Method, % Line, %
AsyncNodeBlockProcessor 0% (0/15) 0% (0/67)
AsyncNodeBlockProcessor$BlockInfo 0% (0/2) 0% (0/4)
Total 0% (0/17) 0% (0/71)


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.net; 20  21 import co.rsk.config.InternalService; 22 import co.rsk.crypto.Keccak256; 23 import co.rsk.net.sync.SyncConfiguration; 24 import co.rsk.validators.BlockValidator; 25 import org.ethereum.core.Block; 26 import org.ethereum.core.Blockchain; 27 import org.ethereum.core.ImportResult; 28 import org.slf4j.Logger; 29 import org.slf4j.LoggerFactory; 30  31 import javax.annotation.Nonnull; 32 import javax.annotation.Nullable; 33 import java.time.Instant; 34 import java.util.Collections; 35 import java.util.Map; 36 import java.util.concurrent.BlockingQueue; 37 import java.util.concurrent.LinkedBlockingQueue; 38  39 /** 40  * AsyncNodeBlockProcessor processes blockchain blocks that are received from other nodes. 41  * If a block passes validation, it will immediately be propagated to other nodes and its execution will be scheduled on a separate thread. 42  * Blocks are being executed and connected to the blockchain sequentially one after another. 43  * <p> 44  * If a block is not ready to be added to the blockchain, it will be on hold in a BlockStore. 45  * <p> 46  */ 47 public class AsyncNodeBlockProcessor extends NodeBlockProcessor implements InternalService, Runnable { 48  49  private static final Logger logger = LoggerFactory.getLogger("asyncblockprocessor"); 50  51  private final BlockingQueue<BlockInfo> blocksToProcess = new LinkedBlockingQueue<>(); 52  53  private final BlockValidator blockHeaderValidator; 54  55  private final BlockValidator blockValidator; 56  57  private final Thread thread = new Thread(this,"async block processor"); 58  59  private final Listener listener; 60  61  private volatile boolean stopped; 62  63  public AsyncNodeBlockProcessor(@Nonnull NetBlockStore store, @Nonnull Blockchain blockchain, @Nonnull BlockNodeInformation nodeInformation, 64  @Nonnull BlockSyncService blockSyncService, @Nonnull SyncConfiguration syncConfiguration, 65  @Nonnull BlockValidator blockHeaderValidator, @Nonnull BlockValidator blockValidator, 66  @Nullable Listener listener) { 67  super(store, blockchain, nodeInformation, blockSyncService, syncConfiguration); 68  this.blockHeaderValidator = blockHeaderValidator; 69  this.blockValidator = blockValidator; 70  this.listener = listener; 71  } 72  73  public AsyncNodeBlockProcessor(@Nonnull NetBlockStore store, @Nonnull Blockchain blockchain, @Nonnull BlockNodeInformation nodeInformation, 74  @Nonnull BlockSyncService blockSyncService, @Nonnull SyncConfiguration syncConfiguration, 75  @Nonnull BlockValidator blockHeaderValidator, @Nonnull BlockValidator blockValidator) { 76  this(store, blockchain, nodeInformation, blockSyncService, syncConfiguration, blockHeaderValidator, blockValidator, null); 77  } 78  79  @Override 80  public BlockProcessResult processBlock(@Nullable Peer sender, @Nonnull Block block) { 81  final Instant start = Instant.now(); 82  83  final long blockNumber = block.getNumber(); 84  final String blockHash = block.getPrintableHash(); 85  final String peer = sender != null ? sender.getPeerNodeID().toString() : "N/A"; 86  87  // Validate block header first to see if its PoW is valid at all 88  if (!isBlockHeaderValid(block)) { 89  logger.warn("Invalid block with number {} {} from {} ", blockNumber, blockHash, peer); 90  return invalidBlockResult(block, start); 91  } 92  93  // Check if block is already in the queue 94  if (store.hasBlock(block)) { 95  logger.trace("Ignored block with number {} and hash {} from {} as it's already in the queue", blockNumber, blockHash, peer); 96  return ignoreBlockResult(block, start); 97  } 98  99  // Check if block is ready for processing - if the block is not too advanced, its parent block is in place etc. 100  boolean readyForProcessing = blockSyncService.preprocessBlock(block, sender, false); 101  if (readyForProcessing) { 102  // Validate block if it can be added to the queue for processing 103  if (isBlockValid(block)) { 104  scheduleForProcessing(new BlockInfo(sender, block), blockNumber, blockHash, peer); 105  106  return scheduledForProcessingResult(block, start); 107  } 108  109  logger.warn("Invalid block with number {} {} from {} ", blockNumber, blockHash, peer); 110  return invalidBlockResult(block, start); 111  } 112  113  logger.trace("Ignored block with number {} and hash {} from {} as it's not ready for processing yet", blockNumber, blockHash, peer); 114  return ignoreBlockResult(block, start); 115  } 116  117  @Override 118  public void start() { 119  thread.start(); 120  } 121  122  @Override 123  public void stop() { 124  stopThread(); 125  } 126  127  /** 128  * Stop the service and wait until a working thread is stopped for {@code waitMillis} milliseconds, 129  * if {@code waitMillis} greater than zero. If {@code waitMillis} is zero, then immediately returns. 130  */ 131  public void stopAndWait(long waitMillis) throws InterruptedException { 132  stopThread(); 133  134  if (waitMillis > 0L) { 135  thread.join(waitMillis); 136  } 137  } 138  139  private void stopThread() { 140  stopped = true; 141  thread.interrupt(); 142  } 143  144  @Override 145  public void run() { 146  while (!stopped) { 147  Peer sender = null; 148  Block block = null; 149  150  try { 151  logger.trace("Awaiting block for processing from the queue..."); 152  153  BlockInfo blockInfo = blocksToProcess.take(); 154  155  sender = blockInfo.peer; 156  block = blockInfo.block; 157  158  if (logger.isTraceEnabled()) { 159  logger.trace("Start block processing with number {} and hash {} from {}", block.getNumber(), block.getPrintableHash(), sender); 160  } 161  162  BlockProcessResult blockProcessResult = blockSyncService.processBlock(block, sender, false); 163  logger.trace("Finished block processing"); 164  165  if (listener != null) { 166  listener.onBlockProcessed(this, sender, block, blockProcessResult); 167  } 168  } catch (InterruptedException e) { 169  logger.trace("Thread has been interrupted"); 170  171  Thread.currentThread().interrupt(); 172  } catch (Exception e) { 173  logger.error("Unexpected error processing block {} from peer {}", block, sender, e); 174  } 175  } 176  } 177  178  private boolean isBlockHeaderValid(Block block) { 179  return blockHeaderValidator.isValid(block); 180  } 181  182  private boolean isBlockValid(Block block) { 183  return blockValidator.isValid(block); 184  } 185  186  private void scheduleForProcessing(BlockInfo blockInfo, long blockNumber, String blockHash, String peer) { 187  if (stopped) { 188  logger.warn("{} is stopped. Block with number {} and hash {} from {} may not be processed", 189  AsyncNodeBlockProcessor.class.getSimpleName(), blockNumber, blockHash, peer); 190  } 191  192  boolean offer = blocksToProcess.offer(blockInfo); 193  if (offer) { 194  logger.trace("Added block with number {} and hash {} from {} to the queue", blockNumber, blockHash, peer); 195  } else { 196  // This should not happen as the queue is unbounded 197  logger.warn("Cannot add block for processing into the queue with number {} {} from {}", blockNumber, blockHash, peer); 198  } 199  } 200  201  private static BlockProcessResult scheduledForProcessingResult(@Nonnull Block block, @Nonnull Instant start) { 202  return BlockProcessResult.connectResult(block, start, null); 203  } 204  205  private static BlockProcessResult invalidBlockResult(@Nonnull Block block, @Nonnull Instant start) { 206  Map<Keccak256, ImportResult> result = Collections.singletonMap(block.getHash(), ImportResult.INVALID_BLOCK); 207  return BlockProcessResult.connectResult(block, start, result); 208  } 209  210  private static BlockProcessResult ignoreBlockResult(@Nonnull Block block, @Nonnull Instant start) { 211  return BlockProcessResult.ignoreBlockResult(block, start); 212  } 213  214  public interface Listener { 215  216  /** 217  * Called after a block is processed. 218  * 219  * This callback method is executed by an {@link AsyncNodeBlockProcessor}'s working thread. 220  */ 221  void onBlockProcessed(@Nonnull AsyncNodeBlockProcessor blockProcessor, 222  @Nullable Peer sender, @Nonnull Block block, 223  @Nonnull BlockProcessResult blockProcessResult); 224  225  } 226  227  private static class BlockInfo { 228  private final Peer peer; 229  private final Block block; 230  231  BlockInfo(@Nullable Peer peer, @Nonnull Block block) { 232  this.peer = peer; 233  this.block = block; 234  } 235  } 236 }