Coverage Summary for Class: MiningMainchainViewImpl (co.rsk.core.bc)

Class Class, % Method, % Line, %
MiningMainchainViewImpl 100% (1/1) 54.5% (6/11) 50.6% (40/79)


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.core.bc; 20  21 import co.rsk.crypto.Keccak256; 22 import org.ethereum.core.Block; 23 import org.ethereum.core.BlockHeader; 24 import org.ethereum.db.BlockStore; 25 import org.slf4j.Logger; 26 import org.slf4j.LoggerFactory; 27  28 import javax.annotation.concurrent.GuardedBy; 29 import java.util.*; 30 import java.util.stream.Collectors; 31 import java.util.stream.Stream; 32  33 public class MiningMainchainViewImpl implements MiningMainchainView { 34  private static final Logger logger = LoggerFactory.getLogger("miningmainchainview"); 35  36  private final Object internalBlockStoreReadWriteLock = new Object(); 37  38  private final int height; 39  40  private BlockStore blockStore; 41  42  @GuardedBy("internalBlockStoreReadWriteLock") 43  private Map<Keccak256, BlockHeader> blocksByHash; 44  45  @GuardedBy("internalBlockStoreReadWriteLock") 46  private Map<Long, List<Keccak256>> blockHashesByNumber; 47  48  @GuardedBy("internalBlockStoreReadWriteLock") 49  private List<BlockHeader> mainchain; 50  51  public MiningMainchainViewImpl(BlockStore blockStore, 52  int height) { 53  this.height = height; 54  this.blockStore = blockStore; 55  this.blocksByHash = new HashMap<>(); 56  this.blockHashesByNumber = new HashMap<>(); 57  58  BlockHeader currentBest = blockStore.getBestBlock().getHeader(); 59  addHeaderToMaps(currentBest); 60  buildMainchainFromList(Arrays.asList(currentBest)); 61  } 62  63  public void addBest(BlockHeader bestHeader) { 64  synchronized (internalBlockStoreReadWriteLock) { 65  addHeaderToMaps(bestHeader); 66  67  // try to avoid recalculating the whole chain if the new header's parent already exists in the chain 68  OptionalInt parentIndex = findParentIndex(bestHeader); 69  if (parentIndex.isPresent()) { 70  addBestAndRebuildFromParent(bestHeader, parentIndex.getAsInt()); 71  } else { 72  buildMainchainFromList(Arrays.asList(bestHeader)); 73  } 74  75  deleteEntriesOutOfBoundaries(bestHeader.getNumber()); 76  } 77  } 78  79  @Override 80  public List<BlockHeader> get() { 81  synchronized (internalBlockStoreReadWriteLock) { 82  return Collections.unmodifiableList(mainchain); 83  } 84  } 85  86  /** 87  * Given a new best header and the index of its parent rebuild the mainchain using the index as the pivot point 88  * by discarding all headers subsequent to the it, setting the new header as the tip and refilling with as many 89  * are needed to complete the desired height 90  * 91  * @param bestHeader The best header to be on top of the chain 92  * @param parentIndex List index of the best header's parent 93  */ 94  private void addBestAndRebuildFromParent(BlockHeader bestHeader, int parentIndex) { 95  List<BlockHeader> commonAncestorChain = mainchain.stream() 96  .skip(parentIndex) 97  .collect(Collectors.toList()); 98  99  List<BlockHeader> newMainchain = Stream.concat( 100  Arrays.asList(bestHeader).stream(), 101  commonAncestorChain.stream()) 102  .collect(Collectors.toList()); 103  104  buildMainchainFromList(newMainchain); 105  } 106  107  /** 108  * Given a source list take it as the new mainchain and refill it with as many block headers are needed or trim it 109  * to reach the desired depth/height 110  * 111  * @param sourceList 112  */ 113  private void buildMainchainFromList(List<BlockHeader> sourceList) { 114  int sourceSize = sourceList.size(); 115  116  if (sourceSize == 0) { 117  return; 118  } 119  120  if (height < sourceSize) { 121  mainchain = sourceList.stream() 122  .limit(height) 123  .collect(Collectors.toList()); 124  125  return; 126  } 127  128  BlockHeader lastHeader = sourceList.get(sourceSize - 1); 129  130  List<BlockHeader> missingHeaders = retrieveAncestorsForHeader(lastHeader, height - sourceSize); 131  132  for (BlockHeader header : missingHeaders) { 133  if(!blocksByHash.containsKey(header.getHash())) { 134  addHeaderToMaps(header); 135  } 136  } 137  138  mainchain = Stream.concat(sourceList.stream(), missingHeaders.stream()) 139  .collect(Collectors.toList()); 140  } 141  142  /** 143  * Given a start block header and a chain length, retrieve a List of said length consisting of 144  * the start header's ancestors 145  * 146  * The returned list DOES NOT include the start header 147  * 148  * @param header The block header to look the ancestors for 149  * @param chainLength The max length of the returned ancestor chain 150  */ 151  private List<BlockHeader> retrieveAncestorsForHeader(BlockHeader header, int chainLength) { 152  List<BlockHeader> missingHeaders = new ArrayList<>(chainLength); 153  154  BlockHeader currentHeader = header; 155  for(int i = 0; i < chainLength; i++) { 156  157  // genesis has no parent 158  if(currentHeader.isGenesis()) { 159  break; 160  } 161  162  Block nextBlock = blockStore.getBlockByHash(currentHeader.getParentHash().getBytes()); 163  if (nextBlock == null) { 164  logger.error("Missing parent for block %s, number %d", currentHeader.getPrintableHash(), currentHeader.getNumber()); 165  break; 166  } 167  currentHeader = nextBlock.getHeader(); 168  169  missingHeaders.add(currentHeader); 170  } 171  172  return missingHeaders; 173  } 174  175  private void addHeaderToMaps(BlockHeader header) { 176  blocksByHash.put(header.getHash(), header); 177  addToBlockHashesByNumberMap(header); 178  } 179  180  private void addToBlockHashesByNumberMap(BlockHeader headerToAdd) { 181  long blockNumber = headerToAdd.getNumber(); 182  if (blockHashesByNumber.containsKey(blockNumber)) { 183  blockHashesByNumber.get(blockNumber).add(headerToAdd.getHash()); 184  } else { 185  blockHashesByNumber.put(headerToAdd.getNumber(), new ArrayList<>(Collections.singletonList(headerToAdd.getHash()))); 186  } 187  } 188  189  private void deleteEntriesOutOfBoundaries(long bestBlockNumber) { 190  long blocksHeightToDelete = bestBlockNumber - height; 191  if(blocksHeightToDelete >= 0 && blockHashesByNumber.containsKey(blocksHeightToDelete)) { 192  blockHashesByNumber.get(blocksHeightToDelete).forEach(blockHashToDelete -> blocksByHash.remove(blockHashToDelete)); 193  blockHashesByNumber.remove(blocksHeightToDelete); 194  } 195  } 196  197  private OptionalInt findParentIndex(BlockHeader header) { 198  for (int i = 0; i < mainchain.size(); i++) { 199  BlockHeader chainHeader = mainchain.get(i); 200  if (chainHeader.isParentOf(header)) { 201  return OptionalInt.of(i); 202  } 203  } 204  return OptionalInt.empty(); 205  } 206 }