Coverage Summary for Class: DownloadingBackwardsBodiesSyncState (co.rsk.net.sync)
Class |
Class, %
|
Method, %
|
Line, %
|
DownloadingBackwardsBodiesSyncState |
0%
(0/1)
|
0%
(0/7)
|
0%
(0/61)
|
1 package co.rsk.net.sync;
2
3 import co.rsk.core.BlockDifficulty;
4 import co.rsk.net.Peer;
5 import co.rsk.net.messages.BodyResponseMessage;
6 import co.rsk.scoring.EventType;
7 import org.ethereum.core.Block;
8 import org.ethereum.core.BlockFactory;
9 import org.ethereum.core.BlockHeader;
10 import org.ethereum.core.Genesis;
11 import org.ethereum.db.BlockStore;
12 import org.slf4j.Logger;
13 import org.slf4j.LoggerFactory;
14
15 import java.util.*;
16
17 /**
18 * Given a sequential list of headers to request and an already stored child of the newest header
19 * DownloadingBackwardsBodiesSyncState downloads the bodies of the requested headers and connects them to the child.
20 * <p>
21 * If the child is block number 1 or the requested list contains block number 1 it is connected to genesis.
22 * <p>
23 * Assuming that child is valid, the previous block can be validated by comparing it's hash to the child's parent.
24 */
25 public class DownloadingBackwardsBodiesSyncState extends BaseSyncState {
26
27 private static final Logger logger = LoggerFactory.getLogger("syncprocessor");
28
29 private final PeersInformation peersInformation;
30 private final Genesis genesis;
31 private final BlockFactory blockFactory;
32 private final BlockStore blockStore;
33 private final Queue<BlockHeader> toRequest;
34 private final Peer selectedPeer;
35
36 private final PriorityQueue<Block> responses;
37 private final Map<Long, BlockHeader> inTransit;
38 private Block child;
39
40 public DownloadingBackwardsBodiesSyncState(
41 SyncConfiguration syncConfiguration,
42 SyncEventsHandler syncEventsHandler,
43 PeersInformation peersInformation,
44 Genesis genesis,
45 BlockFactory blockFactory,
46 BlockStore blockStore,
47 Block child,
48 List<BlockHeader> toRequest,
49 Peer peer) {
50
51 super(syncEventsHandler, syncConfiguration);
52 this.peersInformation = peersInformation;
53 this.genesis = genesis;
54 this.blockFactory = blockFactory;
55 this.blockStore = blockStore;
56 this.toRequest = new LinkedList<>(toRequest);
57 this.child = child;
58 this.selectedPeer = peer;
59 this.responses = new PriorityQueue<>(Comparator.comparingLong(Block::getNumber).reversed());
60 this.inTransit = new HashMap<>();
61 }
62
63 /**
64 * Validates and connects the bodies in order.
65 * <p>
66 * As the responses may come in any order, they are stored in a priority queue by block number.
67 * This allows connecting blocks sequentially with the current child.
68 *
69 * @param body The requested message containing the body
70 * @param peer The peer sending the message.
71 */
72 @Override
73 public void newBody(BodyResponseMessage body, Peer peer) {
74 BlockHeader requestedHeader = inTransit.get(body.getId());
75 if (requestedHeader == null) {
76 peersInformation.reportEvent(peer.getPeerNodeID(), EventType.INVALID_MESSAGE);
77 return;
78 }
79
80 Block block = blockFactory.newBlock(requestedHeader, body.getTransactions(), body.getUncles());
81 block.seal();
82
83 if (!block.getHash().equals(requestedHeader.getHash())) {
84 peersInformation.reportEvent(peer.getPeerNodeID(), EventType.INVALID_MESSAGE);
85 return;
86 }
87
88 resetTimeElapsed();
89 inTransit.remove(body.getId());
90 responses.add(block);
91
92 while (!responses.isEmpty() && responses.peek().isParentOf(child)) {
93 Block connectedBlock = responses.poll();
94 BlockDifficulty blockchainDifficulty = blockStore.getTotalDifficultyForHash(child.getHash().getBytes());
95 blockStore.saveBlock(
96 connectedBlock,
97 blockchainDifficulty.subtract(child.getCumulativeDifficulty()),
98 true);
99 child = connectedBlock;
100 }
101
102 if (child.getNumber() == 1) {
103 connectGenesis(child);
104 blockStore.flush();
105 logger.info("Backward syncing complete");
106 syncEventsHandler.stopSyncing();
107 return;
108 }
109
110 while (!toRequest.isEmpty() && inTransit.size() < syncConfiguration.getMaxRequestedBodies()) {
111 requestBodies(toRequest.remove());
112 }
113
114 if (toRequest.isEmpty() && inTransit.isEmpty()) {
115 blockStore.flush();
116 logger.info("Backward syncing phase complete {}", block.getNumber());
117 syncEventsHandler.stopSyncing();
118 }
119 }
120
121 @Override
122 public void onEnter() {
123 if (child.getNumber() == 1L) {
124 connectGenesis(child);
125 blockStore.flush();
126 syncEventsHandler.stopSyncing();
127 return;
128 }
129
130 if (toRequest.isEmpty()) {
131 syncEventsHandler.stopSyncing();
132 return;
133 }
134
135 while (!toRequest.isEmpty() && inTransit.size() < syncConfiguration.getMaxRequestedBodies()) {
136 BlockHeader headerToRequest = toRequest.remove();
137 requestBodies(headerToRequest);
138 }
139 }
140
141 private void requestBodies(BlockHeader headerToRequest) {
142 long requestNumber = syncEventsHandler.sendBodyRequest(selectedPeer, headerToRequest);
143 inTransit.put(requestNumber, headerToRequest);
144 }
145
146 private void connectGenesis(Block child) {
147 if (!genesis.isParentOf(child)) {
148 throw new IllegalStateException("Genesis does not connect to the current chain.");
149 }
150
151 BlockDifficulty blockchainDifficulty = blockStore.getTotalDifficultyForHash(this.child.getHash().getBytes());
152 if (!blockchainDifficulty.subtract(this.child.getCumulativeDifficulty()).equals(genesis.getCumulativeDifficulty())) {
153 throw new IllegalStateException("Blockchain difficulty does not match genesis.");
154 }
155
156 blockStore.saveBlock(genesis, genesis.getCumulativeDifficulty(), true);
157 }
158
159 @Override
160 protected void onMessageTimeOut() {
161 syncEventsHandler.onErrorSyncing(
162 selectedPeer.getPeerNodeID(),
163 "Timeout waiting requests {}",
164 EventType.TIMEOUT_MESSAGE,
165 this.getClass(),
166 selectedPeer.getPeerNodeID());
167 }
168 }