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

Class Class, % Method, % Line, %
NodeBlockProcessor 100% (1/1) 8% (2/25) 7.7% (9/117)


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.crypto.Keccak256; 22 import co.rsk.net.messages.*; 23 import co.rsk.net.sync.SyncConfiguration; 24 import org.ethereum.core.Block; 25 import org.ethereum.core.BlockHeader; 26 import org.ethereum.core.BlockIdentifier; 27 import org.ethereum.core.Blockchain; 28 import org.ethereum.util.ByteUtil; 29 import org.slf4j.Logger; 30 import org.slf4j.LoggerFactory; 31  32 import javax.annotation.CheckForNull; 33 import javax.annotation.Nonnull; 34 import javax.annotation.Nullable; 35 import java.util.*; 36  37 /** 38  * NodeBlockProcessor processes blocks to add into a blockchain. 39  * If a block is not ready to be added to the blockchain, it will be on hold in a BlockStore. 40  * <p> 41  * Created by ajlopez on 5/11/2016. 42  */ 43 public class NodeBlockProcessor implements BlockProcessor { 44  private static final Logger logger = LoggerFactory.getLogger("blockprocessor"); 45  46  private final Blockchain blockchain; 47  private final BlockNodeInformation nodeInformation; 48  private final SyncConfiguration syncConfiguration; 49  // keeps on a map the hashes that belongs to the skeleton 50  private final Map <Long, byte[]> skeletonCache = new HashMap<>(); 51  52  protected final NetBlockStore store; 53  // keep tabs on which nodes know which blocks. 54  protected final BlockSyncService blockSyncService; 55  56  /** 57  * Creates a new NodeBlockProcessor using the given BlockStore and Blockchain. 58  * 59  * @param store A BlockStore to store the blocks that are not ready for the Blockchain. 60  * @param blockchain The blockchain in which to insert the blocks. 61  * @param nodeInformation 62  * @param blockSyncService 63  */ 64  public NodeBlockProcessor( 65  @Nonnull final NetBlockStore store, 66  @Nonnull final Blockchain blockchain, 67  @Nonnull final BlockNodeInformation nodeInformation, 68  @Nonnull final BlockSyncService blockSyncService, 69  @Nonnull final SyncConfiguration syncConfiguration) { 70  this.store = store; 71  this.blockchain = blockchain; 72  this.nodeInformation = nodeInformation; 73  this.blockSyncService = blockSyncService; 74  this.syncConfiguration = syncConfiguration; 75  } 76  77  /** 78  * Detect a block number that is too advanced 79  * based on sync chunk size and maximum number of chuncks 80  * 81  * @param blockNumber the block number to check 82  * @return true if the block number is too advanced 83  */ 84  @Override 85  public boolean isAdvancedBlock(long blockNumber) { 86  int syncMaxDistance = syncConfiguration.getChunkSize() * syncConfiguration.getMaxSkeletonChunks(); 87  long bestBlockNumber = this.getBestBlockNumber(); 88  89  return blockNumber > bestBlockNumber + syncMaxDistance; 90  } 91  92  /** 93  * processNewBlockHashesMessage processes a "NewBlockHashes" message. This means that we received hashes 94  * from new blocks and we should request all the blocks that we don't have. 95  * 96  * @param sender The message sender 97  * @param message A message containing a list of block hashes. 98  */ 99  @Override 100  public void processNewBlockHashesMessage(final Peer sender, final NewBlockHashesMessage message) { 101  message.getBlockIdentifiers().stream() 102  .map(bi -> new Keccak256(bi.getHash())) 103  .distinct() 104  .filter(b -> !hasBlock(b.getBytes())) 105  .forEach( 106  b -> { 107  sender.sendMessage(new GetBlockMessage(b.getBytes())); 108  nodeInformation.addBlockToNode(b, sender.getPeerNodeID()); 109  } 110  ); 111  } 112  113  114  @Override 115  public void processBlockHeaders(@Nonnull final Peer sender, @Nonnull final List<BlockHeader> blockHeaders) { 116  blockHeaders.stream() 117  .filter(h -> !hasHeader(h.getHash())) 118  // sort block headers in ascending order, so we can process them in that order. 119  .sorted(Comparator.comparingLong(BlockHeader::getNumber)) 120  .forEach(h -> processBlockHeader(sender, h)); 121  } 122  123  private boolean hasHeader(Keccak256 hash) { 124  return hasBlock(hash.getBytes()) || store.hasHeader(hash); 125  } 126  127  private void processBlockHeader(@Nonnull final Peer sender, @Nonnull final BlockHeader header) { 128  sender.sendMessage(new GetBlockMessage(header.getHash().getBytes())); 129  this.store.saveHeader(header); 130  } 131  132  /** 133  * processGetBlock sends a requested block to a peer if the block is available. 134  * 135  * @param sender the sender of the GetBlock message. 136  * @param hash the requested block's hash. 137  */ 138  @Override 139  public void processGetBlock(@Nonnull final Peer sender, @Nonnull final byte[] hash) { 140  logger.trace("Processing get block {} from {}", ByteUtil.toHexString(hash), sender.getPeerNodeID()); 141  final Block block = blockSyncService.getBlockFromStoreOrBlockchain(hash); 142  143  if (block == null) { 144  return; 145  } 146  147  nodeInformation.addBlockToNode(new Keccak256(hash), sender.getPeerNodeID()); 148  sender.sendMessage(new BlockMessage(block)); 149  } 150  151  /** 152  * processBlockRequest sends a requested block to a peer if the block is available. 153  * 154  * @param sender the sender of the BlockRequest message. 155  * @param requestId the id of the request 156  * @param hash the requested block's hash. 157  */ 158  @Override 159  public void processBlockRequest(@Nonnull final Peer sender, long requestId, @Nonnull final byte[] hash) { 160  logger.trace("Processing get block by hash {} {} from {}", requestId, ByteUtil.toHexString(hash), sender.getPeerNodeID()); 161  final Block block = blockSyncService.getBlockFromStoreOrBlockchain(hash); 162  163  if (block == null) { 164  return; 165  } 166  167  nodeInformation.addBlockToNode(new Keccak256(hash), sender.getPeerNodeID()); 168  sender.sendMessage(new BlockResponseMessage(requestId, block)); 169  } 170  171  /** 172  * processBlockHeadersRequest sends a list of block headers. 173  * 174  * @param sender the sender of the BlockHeadersRequest message. 175  * @param requestId the id of the request 176  * @param hash the hash of the block to be processed 177  * @param count the number of headers to send 178  */ 179  @Override 180  public void processBlockHeadersRequest(@Nonnull final Peer sender, long requestId, @Nonnull final byte[] hash, int count) { 181  logger.trace("Processing headers request {} {} from {}", requestId, ByteUtil.toHexString(hash), sender.getPeerNodeID()); 182  183  if (count > syncConfiguration.getChunkSize()) { 184  logger.trace("Headers request from {} failed because size {}", sender.getPeerNodeID(), count); 185  return; 186  } 187  188  Block block = blockSyncService.getBlockFromStoreOrBlockchain(hash); 189  190  if (block == null) { 191  return; 192  } 193  194  List<BlockHeader> headers = new ArrayList<>(); 195  headers.add(block.getHeader()); 196  197  for (int k = 1; k < count; k++) { 198  block = blockSyncService.getBlockFromStoreOrBlockchain(block.getParentHash().getBytes()); 199  200  if (block == null) { 201  break; 202  } 203  204  headers.add(block.getHeader()); 205  } 206  207  BlockHeadersResponseMessage response = new BlockHeadersResponseMessage(requestId, headers); 208  209  sender.sendMessage(response); 210  } 211  212  /** 213  * processBodyRequest sends the requested block body to a peer if it is available. 214  * 215  * @param sender the sender of the BodyRequest message. 216  * @param requestId the id of the request 217  * @param hash the requested block's hash. 218  */ 219  @Override 220  public void processBodyRequest(@Nonnull final Peer sender, long requestId, @Nonnull final byte[] hash) { 221  logger.trace("Processing body request {} {} from {}", requestId, ByteUtil.toHexString(hash), sender.getPeerNodeID()); 222  final Block block = blockSyncService.getBlockFromStoreOrBlockchain(hash); 223  224  if (block == null) { 225  // Don't waste time sending an empty response. 226  return; 227  } 228  229  Message responseMessage = new BodyResponseMessage(requestId, block.getTransactionsList(), block.getUncleList()); 230  sender.sendMessage(responseMessage); 231  } 232  233  /** 234  * processBlockHashRequest sends the requested block body to a peer if it is available. 235  * @param sender the sender of the BlockHashRequest message. 236  * @param requestId the id of the request 237  * @param height the requested block's hash. 238  */ 239  @Override 240  public void processBlockHashRequest(@Nonnull final Peer sender, long requestId, long height) { 241  logger.trace("Processing block hash request {} {} from {}", requestId, height, sender.getPeerNodeID()); 242  243  if (height == 0){ 244  return; 245  } 246  247  final Block block = this.getBlockFromBlockchainStore(height); 248  249  if (block == null) { 250  // Don't waste time sending an empty response. 251  return; 252  } 253  254  BlockHashResponseMessage responseMessage = new BlockHashResponseMessage(requestId, block.getHash().getBytes()); 255  sender.sendMessage(responseMessage); 256  } 257  258  /** 259  * @param sender the sender of the SkeletonRequest message. 260  * @param requestId the id of the request. 261  * @param startNumber the starting block's hash to get the skeleton. 262  */ 263  @Override 264  public void processSkeletonRequest(@Nonnull final Peer sender, long requestId, long startNumber) { 265  logger.trace("Processing skeleton request {} {} from {}", requestId, startNumber, sender.getPeerNodeID()); 266  int skeletonStep = syncConfiguration.getChunkSize(); 267  Block blockStart = this.getBlockFromBlockchainStore(startNumber); 268  269  // If we don't have a block with the requested number, we ignore the message 270  if (blockStart == null) { 271  // Don't waste time sending an empty response. 272  return; 273  } 274  275  // We always include the skeleton block immediately before blockStart, even if it's Genesis 276  long skeletonStartHeight = (blockStart.getNumber() / skeletonStep) * skeletonStep; 277  List<BlockIdentifier> blockIdentifiers = new ArrayList<>(); 278  long skeletonNumber = skeletonStartHeight; 279  int maxSkeletonChunks = syncConfiguration.getMaxSkeletonChunks(); 280  long maxSkeletonNumber = Math.min(this.getBestBlockNumber(), skeletonStartHeight + skeletonStep * maxSkeletonChunks); 281  282  for (; skeletonNumber < maxSkeletonNumber; skeletonNumber += skeletonStep) { 283  byte[] skeletonHash = getSkeletonHash(skeletonNumber); 284  blockIdentifiers.add(new BlockIdentifier(skeletonHash, skeletonNumber)); 285  } 286  287  // We always include the best block as part of the Skeleton response 288  skeletonNumber = Math.min(this.getBestBlockNumber(), skeletonNumber); 289  byte[] skeletonHash = getSkeletonHash(skeletonNumber); 290  blockIdentifiers.add(new BlockIdentifier(skeletonHash, skeletonNumber)); 291  SkeletonResponseMessage responseMessage = new SkeletonResponseMessage(requestId, blockIdentifiers); 292  293  sender.sendMessage(responseMessage); 294  } 295  296  @Override 297  public boolean canBeIgnoredForUnclesRewards(long blockNumber) { 298  return blockSyncService.canBeIgnoredForUnclesRewards(blockNumber); 299  } 300  301  /** 302  * 303  * @param skeletonBlockNumber a block number that belongs to the skeleton 304  * @return the proper hash for the block 305  */ 306  private byte[] getSkeletonHash(long skeletonBlockNumber) { 307  // if block number is too close to best block then its not stored in cache 308  // in order to avoid caching forked blocks 309  if (blockchain.getBestBlock().getNumber() - skeletonBlockNumber < syncConfiguration.getChunkSize()){ 310  Block block = getBlockFromBlockchainStore(skeletonBlockNumber); 311  if (block != null){ 312  return block.getHash().getBytes(); 313  } 314  } 315  316  byte[] hash = skeletonCache.get(skeletonBlockNumber); 317  if (hash == null){ 318  Block block = getBlockFromBlockchainStore(skeletonBlockNumber); 319  if (block != null){ 320  hash = block.getHash().getBytes(); 321  skeletonCache.put(skeletonBlockNumber, hash); 322  } 323  } 324  return hash; 325  } 326  327  @Override 328  public BlockNodeInformation getNodeInformation() { 329  return nodeInformation; 330  } 331  332  /** 333  * getBlockFromBlockchainStore retrieves the block with the given height from the blockchain, if available. 334  * 335  * @param height the desired block's height. 336  * @return a Block with the given height if available, null otherwise. 337  */ 338  @CheckForNull 339  private Block getBlockFromBlockchainStore(long height) { 340  return this.blockchain.getBlockByNumber(height); 341  } 342  343  /** 344  * getBestBlockNumber returns the current blockchain best block's number. 345  * 346  * @return the blockchain's best block's number. 347  */ 348  public long getBestBlockNumber() { 349  return blockchain.getBestBlock().getNumber(); 350  } 351  352  /** 353  * hasBlock checks if a given hash is in the store or in the blockchain, or in the blockchain index. 354  * 355  * @param hash the block's hash. 356  * @return true if the block is in the store, or in the blockchain. 357  */ 358  @Override 359  public boolean hasBlock(@Nonnull final byte[] hash) { 360  return hasBlockInProcessorStore(hash) || hasBlockInSomeBlockchain(hash); 361  } 362  363  @Override 364  public boolean hasBlockInProcessorStore(@Nonnull final byte[] hash) { 365  return this.store.hasBlock(hash); 366  } 367  368  // Below are methods delegated to BlockSyncService, but should eventually be deleted 369  370  /** 371  * processBlock processes a block and tries to add it to the blockchain. 372  * It will also add all pending blocks (that depend on this block) into the blockchain. 373  * 374  * @param sender the message sender. If more data is needed, NodeProcessor might send a message to the sender 375  * requesting that data (for example, a missing parent block). 376  * @param block the block to process. 377  */ 378  @Override 379  public BlockProcessResult processBlock(@Nullable final Peer sender, @Nonnull final Block block) { 380  return blockSyncService.processBlock(block, sender, false); 381  } 382  383  @Override 384  public boolean hasBlockInSomeBlockchain(@Nonnull final byte[] hash) { 385  return this.blockchain.hasBlockInSomeBlockchain(hash); 386  } 387  388  @Override 389  public boolean hasBetterBlockToSync() { 390  return blockSyncService.hasBetterBlockToSync(); 391  } 392  393  @Override 394  public long getLastKnownBlockNumber() { 395  return blockSyncService.getLastKnownBlockNumber(); 396  } 397 }