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 }