Coverage Summary for Class: BootstrapIndexCandidateSelector (co.rsk.db.importer.provider.index)
Class |
Method, %
|
Line, %
|
BootstrapIndexCandidateSelector |
0%
(0/6)
|
0%
(0/40)
|
BootstrapIndexCandidateSelector$HeightCandidate |
0%
(0/3)
|
0%
(0/5)
|
Total |
0%
(0/9)
|
0%
(0/45)
|
1 /*
2 * This file is part of RskJ
3 * Copyright (C) 2019 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.db.importer.provider.index;
20
21 import co.rsk.db.importer.BootstrapImportException;
22 import co.rsk.db.importer.provider.index.data.BootstrapDataEntry;
23 import co.rsk.db.importer.provider.index.data.BootstrapDataIndex;
24
25 import java.util.*;
26 import java.util.stream.Collectors;
27
28 public class BootstrapIndexCandidateSelector {
29
30 private final List<String> publicKeys;
31 private final int minimumRequiredSources;
32
33 public BootstrapIndexCandidateSelector(List<String> publicKeys, int minimumRequiredSources) {
34 this.publicKeys = new ArrayList<>(publicKeys);
35 this.minimumRequiredSources = minimumRequiredSources;
36 }
37
38 public HeightCandidate getHeightData(List<BootstrapDataIndex> indexes) {
39 Map<Long, Map<String, BootstrapDataEntry>> entriesPerHeight = getEntriesPerHeight(indexes);
40 return getHeightCandidate(entriesPerHeight);
41 }
42
43 private HeightCandidate getHeightCandidate(Map<Long, Map<String, BootstrapDataEntry>> entriesPerHeight) {
44 HeightCandidate candidate = null;
45 for (Map.Entry<Long, Map<String, BootstrapDataEntry>> entry : entriesPerHeight.entrySet()) {
46 Long height = entry.getKey();
47 boolean isPossibleCandidate = candidate == null || candidate.getHeight() < height;
48 if (!isPossibleCandidate) {
49 continue;
50 }
51
52 Map<String, BootstrapDataEntry> entries = entry.getValue();
53 Map<String, Long> hashGroups = entries.values().stream()
54 .collect(Collectors.groupingBy(BootstrapDataEntry::getHash, Collectors.counting()));
55 Optional<Map.Entry<String, Long>> bestHash = hashGroups.entrySet().stream()
56 .max(Comparator.comparing(Map.Entry::getValue));
57
58 if (bestHash.isPresent() && bestHash.get().getValue() >= minimumRequiredSources) {
59 Map<String, BootstrapDataEntry> filteredEntries = entries.entrySet().stream()
60 .filter(e -> e.getValue().getHash().equals(bestHash.get().getKey()))
61 .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
62
63 candidate = new HeightCandidate(height, filteredEntries);
64 }
65 }
66
67 if (candidate == null) {
68 throw new BootstrapImportException("Downloaded files doesn't contain enough entries for a common height");
69 }
70
71 return candidate;
72 }
73
74 private Map<Long, Map<String, BootstrapDataEntry>> getEntriesPerHeight(List<BootstrapDataIndex> indexes) {
75 // the outer map represents the tuples (height, heightEntries)
76 // the inner map represents the tuples (source, dbEntry)
77 Map<Long, Map<String, BootstrapDataEntry>> entriesPerHeight = new HashMap<>();
78
79 // the algorithm assumes that indexes and public keys are ordered in the same way
80 // each iteration is an index from a different source, each index contains many entries
81 for (int i = 0; i < indexes.size(); i++) {
82 BootstrapDataIndex bdi = indexes.get(i);
83 String publicKey = publicKeys.get(i);
84
85 // each iteration is an entry from an index, each entry has a different height
86 // all the items for this index belongs to the same source
87 for (BootstrapDataEntry bde : bdi.getDbs()) {
88 Map<String, BootstrapDataEntry> entries = entriesPerHeight.computeIfAbsent(
89 bde.getHeight(), k -> new HashMap<>()
90 );
91
92 // if any height is duplicated on a single file the process is stopped
93 if (entries.get(publicKey) != null){
94 throw new BootstrapImportException(String.format(
95 "There is an invalid file from %s: it has 2 entries from same height %d", publicKey, bde.getHeight()));
96 }
97 entries.put(publicKey, bde);
98 }
99 }
100
101 if (entriesPerHeight.isEmpty()) {
102 throw new BootstrapImportException("Downloaded files contain no height entries");
103 }
104
105 return entriesPerHeight;
106 }
107
108 public static class HeightCandidate {
109 private Long height;
110 private Map<String, BootstrapDataEntry> entries;
111
112 public HeightCandidate(long height, Map<String, BootstrapDataEntry> entries) {
113 this.height = height;
114 this.entries = entries;
115 }
116
117 public long getHeight() {
118 return height;
119 }
120
121 public Map<String, BootstrapDataEntry> getEntries() {
122 return entries;
123 }
124 }
125 }