Coverage Summary for Class: BlockUnclesValidationRule (co.rsk.validators)

Class Class, % Method, % Line, %
BlockUnclesValidationRule 100% (1/1) 100% (8/8) 62.1% (41/66)


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.validators; 20  21 import co.rsk.core.bc.FamilyUtils; 22 import co.rsk.crypto.Keccak256; 23 import co.rsk.panic.PanicProcessor; 24 import org.ethereum.core.Block; 25 import org.ethereum.core.BlockHeader; 26 import org.ethereum.db.BlockStore; 27 import org.slf4j.Logger; 28 import org.slf4j.LoggerFactory; 29  30 import java.util.HashSet; 31 import java.util.List; 32 import java.util.Set; 33  34 /** 35  * Validate the uncles in a block. 36  * It validates that the uncle root hash correspond with the uncle list 37  * It calculates the already used uncles in the block ancestors 38  * It calculates the ancestors of the block 39  * It validates that the uncle list is not too large 40  * It validates that each uncle 41  * - is not an ancestor 42  * - is not a used uncle 43  * - has a common ancestor with the block 44  * 45  * @return true if the uncles in block are valid, false if not 46  */ 47  48 public class BlockUnclesValidationRule implements BlockValidationRule { 49  50  private static final Logger logger = LoggerFactory.getLogger("blockvalidator"); 51  private static final PanicProcessor panicProcessor = new PanicProcessor(); 52  public static final String INVALIDUNCLE = "invaliduncle"; 53  54  private final BlockStore blockStore; 55  private final int uncleListLimit; 56  private final int uncleGenerationLimit; 57  private final BlockHeaderValidationRule validations; 58  private final BlockHeaderParentDependantValidationRule parentValidations; 59  private final BlockUnclesHashValidationRule blockValidationRule; 60  61  public BlockUnclesValidationRule( 62  BlockStore blockStore, int uncleListLimit, 63  int uncleGenerationLimit, BlockHeaderValidationRule validations, 64  BlockHeaderParentDependantValidationRule parentValidations) { 65  this.blockStore = blockStore; 66  this.uncleListLimit = uncleListLimit; 67  this.uncleGenerationLimit = uncleGenerationLimit; 68  this.validations = validations; 69  this.parentValidations = parentValidations; 70  this.blockValidationRule = new BlockUnclesHashValidationRule(); 71  } 72  73  @Override 74  public boolean isValid(Block block) { 75  76  if (!blockValidationRule.isValid(block)) { 77  return false; 78  } 79  80  List<BlockHeader> uncles = block.getUncleList(); 81  if (!uncles.isEmpty() && !validateUncleList(block.getNumber(), uncles, 82  FamilyUtils.getAncestors(blockStore, block, uncleGenerationLimit), 83  FamilyUtils.getUsedUncles(blockStore, block, uncleGenerationLimit))) { 84  logger.warn("Uncles list validation failed"); 85  return false; 86  } 87  88  return true; 89  } 90  91  /** 92  * Validate an uncle list. 93  * It validates that the uncle list is not too large 94  * It validates that each uncle 95  * - is not an ancestor 96  * - is not a used uncle 97  * - has a common ancestor with the block 98  * 99  * @param blockNumber the number of the block containing the uncles 100  * @param uncles the uncle list to validate 101  * @param ancestors the list of direct ancestors of the block containing the uncles 102  * @param used used uncles 103  * @return true if the uncles in the list are valid, false if not 104  */ 105  public boolean validateUncleList(long blockNumber, List<BlockHeader> uncles, Set<Keccak256> ancestors, Set<Keccak256> used) { 106  if (uncles.size() > uncleListLimit) { 107  logger.error("Uncle list to big: block.getUncleList().size() > UNCLE_LIST_LIMIT"); 108  panicProcessor.panic(INVALIDUNCLE, "Uncle list to big: block.getUncleList().size() > UNCLE_LIST_LIMIT"); 109  return false; 110  } 111  112  Set<Keccak256> hashes = new HashSet<>(); 113  114  for (BlockHeader uncle : uncles) { 115  116  if (!this.validations.isValid(uncle) || !validateParentNumber(uncle, blockNumber)) { 117  return false; 118  } 119  120  Keccak256 uncleHash = uncle.getHash(); 121  122  /* Just checking that the uncle is not added twice */ 123  if (hashes.contains(uncleHash)) { 124  return false; 125  } 126  hashes.add(uncleHash); 127  128  if(!validateUnclesAncestors(ancestors, uncleHash) || !validateIfUncleWasNeverUsed(used, uncleHash) 129  || !validateUncleParent(ancestors, uncle)) { 130  return false; 131  } 132  } 133  return true; 134  } 135  136  private boolean validateParentNumber(BlockHeader uncle, long blockNumber) { 137  boolean isSiblingOrDescendant = uncle.getNumber() >= blockNumber; 138  139  if (isSiblingOrDescendant) { 140  logger.error("Uncle is sibling or descendant"); 141  panicProcessor.panic(INVALIDUNCLE, "Uncle is sibling or descendant"); 142  return false; 143  } 144  145  // if uncle's parent's number is not less than currentBlock - UNCLE_GEN_LIMIT, mark invalid 146  boolean isValid = (uncle.getNumber() - 1 >= (blockNumber - uncleGenerationLimit)); 147  148  if (!isValid) { 149  logger.error("Uncle too old: generationGap must be under UNCLE_GENERATION_LIMIT"); 150  panicProcessor.panic(INVALIDUNCLE, "Uncle too old: generationGap must be under UNCLE_GENERATION_LIMIT"); 151  return false; 152  } 153  154  return true; 155  } 156  157  private boolean validateUnclesAncestors(Set<Keccak256> ancestors, Keccak256 uncleHash) { 158  if (ancestors != null && ancestors.contains(uncleHash)) { 159  String uHashStr = uncleHash.toString(); 160  logger.error("Uncle is direct ancestor: {}", uHashStr); 161  panicProcessor.panic(INVALIDUNCLE, String.format("Uncle is direct ancestor: %s", uHashStr)); 162  return false; 163  } 164  return true; 165  } 166  167  private boolean validateIfUncleWasNeverUsed(Set<Keccak256> used, Keccak256 uncleHash) { 168  String uhashString = uncleHash.toString(); 169  if (used != null && used.contains(uncleHash)) { 170  logger.error("Uncle is not unique: {}", uhashString); 171  panicProcessor.panic(INVALIDUNCLE, String.format("Uncle is not unique: %s", uhashString)); 172  return false; 173  } 174  return true; 175  } 176  177  private boolean validateUncleParent(Set<Keccak256> ancestors, BlockHeader uncle) { 178  String uhashString = uncle.getHash().toString(); 179  Block parent = blockStore.getBlockByHash(uncle.getParentHash().getBytes()); 180  181  if (ancestors != null && (parent == null || !ancestors.contains(parent.getHash()))) { 182  logger.error("Uncle has no common parent: {}", uhashString); 183  panicProcessor.panic(INVALIDUNCLE, String.format("Uncle has no common parent: %s", uhashString)); 184  return false; 185  } 186  187  return this.parentValidations.isValid(uncle, parent); 188  } 189 }