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 }