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 }