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 }