Coverage Summary for Class: RepositoryBtcBlockStoreWithCache (co.rsk.peg)

Class Method, % Line, %
RepositoryBtcBlockStoreWithCache 0% (0/18) 0% (0/137)
RepositoryBtcBlockStoreWithCache$Factory 66.7% (2/3) 78.6% (11/14)
Total 9.5% (2/21) 7.3% (11/151)


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.peg; 20  21 import co.rsk.bitcoinj.core.BtcBlock; 22 import co.rsk.bitcoinj.core.NetworkParameters; 23 import co.rsk.bitcoinj.core.Sha256Hash; 24 import co.rsk.bitcoinj.core.StoredBlock; 25 import co.rsk.bitcoinj.store.BlockStoreException; 26 import co.rsk.config.BridgeConstants; 27 import co.rsk.core.RskAddress; 28 import co.rsk.util.MaxSizeHashMap; 29 import java.util.Optional; 30 import com.google.common.annotations.VisibleForTesting; 31 import org.ethereum.config.blockchain.upgrades.ActivationConfig; 32 import org.ethereum.config.blockchain.upgrades.ConsensusRule; 33 import org.ethereum.config.blockchain.upgrades.ActivationConfig.ForBlock; 34 import org.ethereum.core.Repository; 35 import org.ethereum.vm.DataWord; 36 import org.ethereum.vm.PrecompiledContracts; 37 import org.slf4j.Logger; 38 import org.slf4j.LoggerFactory; 39  40 import java.nio.ByteBuffer; 41 import java.util.Map; 42  43 /** 44  * Implementation of a bitcoinj blockstore that persists to RSK's Repository 45  * 46  * @author Oscar Guindzberg 47  */ 48 public class RepositoryBtcBlockStoreWithCache implements BtcBlockStoreWithCache { 49  50  private static final Logger logger = LoggerFactory.getLogger(RepositoryBtcBlockStoreWithCache.class); 51  52  private static final String BLOCK_STORE_CHAIN_HEAD_KEY = "blockStoreChainHead"; 53  private static final int DEFAULT_MAX_DEPTH_BLOCK_CACHE = 5_000; 54  private static final int DEFAULT_MAX_SIZE_BLOCK_CACHE = 10_000; 55  56  private final Repository repository; 57  private final RskAddress contractAddress; 58  private final NetworkParameters btcNetworkParams; 59  private final BridgeConstants bridgeConstants; 60  private final BridgeStorageProvider bridgeStorageProvider; 61  private final ActivationConfig.ForBlock activations; 62  private final int maxDepthBlockCache; 63  private final Map<Sha256Hash, StoredBlock> cacheBlocks; 64  65  public RepositoryBtcBlockStoreWithCache( 66  NetworkParameters btcNetworkParams, 67  Repository repository, 68  Map<Sha256Hash, StoredBlock> cacheBlocks, 69  RskAddress contractAddress, 70  BridgeConstants bridgeConstants, 71  BridgeStorageProvider bridgeStorageProvider, 72  ForBlock activations) { 73  74  this( 75  btcNetworkParams, 76  repository, 77  cacheBlocks, 78  contractAddress, 79  bridgeConstants, 80  bridgeStorageProvider, 81  activations, 82  DEFAULT_MAX_DEPTH_BLOCK_CACHE 83  ); 84  } 85  86  public RepositoryBtcBlockStoreWithCache( 87  NetworkParameters btcNetworkParams, 88  Repository repository, 89  Map<Sha256Hash, StoredBlock> cacheBlocks, 90  RskAddress contractAddress, 91  BridgeConstants bridgeConstants, 92  BridgeStorageProvider bridgeStorageProvider, 93  ForBlock activations, 94  int maxDepthBlockCache) { 95  96  this.cacheBlocks = cacheBlocks; 97  this.repository = repository; 98  this.contractAddress = contractAddress; 99  this.btcNetworkParams = btcNetworkParams; 100  this.bridgeConstants = bridgeConstants; 101  this.bridgeStorageProvider = bridgeStorageProvider; 102  this.activations = activations; 103  this.maxDepthBlockCache = maxDepthBlockCache; 104  105  checkIfInitialized(); 106  } 107  108  @Override 109  public synchronized void put(StoredBlock storedBlock) { 110  Sha256Hash hash = storedBlock.getHeader().getHash(); 111  byte[] ba = storedBlockToByteArray(storedBlock); 112  repository.addStorageBytes(contractAddress, DataWord.valueFromHex(hash.toString()), ba); 113  if (cacheBlocks != null) { 114  StoredBlock chainHead = getChainHead(); 115  if (chainHead == null || chainHead.getHeight() - storedBlock.getHeight() < this.maxDepthBlockCache) { 116  cacheBlocks.put(storedBlock.getHeader().getHash(), storedBlock); 117  } 118  } 119  } 120  121  @Override 122  public synchronized StoredBlock get(Sha256Hash hash) { 123  logger.trace("[get] Looking in storage for block with hash {}", hash); 124  byte[] ba = repository.getStorageBytes(contractAddress, DataWord.valueFromHex(hash.toString())); 125  if (ba == null) { 126  logger.trace("[get] Block with hash {} not found in storage", hash); 127  return null; 128  } 129  StoredBlock storedBlock = byteArrayToStoredBlock(ba); 130  return storedBlock; 131  } 132  133  @Override 134  public synchronized StoredBlock getChainHead() { 135  byte[] ba = repository.getStorageBytes(contractAddress, DataWord.fromString(BLOCK_STORE_CHAIN_HEAD_KEY)); 136  if (ba == null) { 137  return null; 138  } 139  return byteArrayToStoredBlock(ba); 140  } 141  142  @Override 143  public synchronized void setChainHead(StoredBlock newChainHead) { 144  logger.trace("Set new chain head with height: {}.", newChainHead.getHeight()); 145  byte[] ba = storedBlockToByteArray(newChainHead); 146  repository.addStorageBytes(contractAddress, DataWord.fromString(BLOCK_STORE_CHAIN_HEAD_KEY), ba); 147  if (cacheBlocks != null) { 148  populateCache(newChainHead); 149  } 150  setMainChainBlock(newChainHead.getHeight(), newChainHead.getHeader().getHash()); 151  } 152  153  @Override 154  public Optional<StoredBlock> getInMainchain(int height) { 155  Optional<Sha256Hash> bestBlockHash = bridgeStorageProvider.getBtcBestBlockHashByHeight(height); 156  if (!bestBlockHash.isPresent()) { 157  logger.trace("[getInMainchain] Block at height {} not present in storage", height); 158  return Optional.empty(); 159  } 160  161  StoredBlock block = get(bestBlockHash.get()); 162  if (block == null) { 163  logger.trace("[getInMainchain] Block with hash {} not found in storage", bestBlockHash.get()); 164  return Optional.empty(); 165  } 166  167  logger.trace("[getInMainchain] Found block with hash {} at height {}", bestBlockHash.get(), height); 168  return Optional.of(block); 169  } 170  171  @Override 172  public void setMainChainBlock(int height, Sha256Hash blockHash) { 173  logger.trace("[setMainChainBlock] Set block with hash {} at height {}", blockHash, height); 174  bridgeStorageProvider.setBtcBestBlockHashByHeight(height, blockHash); 175  } 176  177  @Override 178  public void close() { 179  } 180  181  @Override 182  public NetworkParameters getParams() { 183  return btcNetworkParams; 184  } 185  186  @Override 187  public StoredBlock getFromCache(Sha256Hash branchBlockHash) { 188  if (cacheBlocks == null) { 189  logger.trace("[getFromCache] Block with hash {} not found in cache", branchBlockHash); 190  return null; 191  } 192  return cacheBlocks.get(branchBlockHash); 193  } 194  195  @Override 196  public StoredBlock getStoredBlockAtMainChainHeight(int height) throws BlockStoreException { 197  StoredBlock chainHead = getChainHead(); 198  int depth = chainHead.getHeight() - height; 199  logger.trace("Getting btc block at depth: {}", depth); 200  201  if (depth < 0) { 202  String message = String.format( 203  "Height provided is higher than chain head. provided: %n. chain head: %n", 204  height, 205  chainHead.getHeight() 206  ); 207  logger.trace("[getStoredBlockAtMainChainHeight] {}", message); 208  throw new BlockStoreException(message); 209  } 210  211  if (activations.isActive(ConsensusRule.RSKIP199)) { 212  int btcHeightWhenBlockIndexActivates = this.bridgeConstants.getBtcHeightWhenBlockIndexActivates(); 213  int maxDepthToSearch = this.bridgeConstants.getMaxDepthToSearchBlocksBelowIndexActivation(); 214  int limit; 215  if (chainHead.getHeight() - btcHeightWhenBlockIndexActivates > maxDepthToSearch) { 216  limit = btcHeightWhenBlockIndexActivates; 217  } else { 218  limit = chainHead.getHeight() - maxDepthToSearch; 219  } 220  logger.trace("[getStoredBlockAtMainChainHeight] Chain head height is {} and the depth limit {}", chainHead.getHeight(), limit); 221  222  if (height < limit) { 223  String message = String.format( 224  "Height provided is lower than the depth limit defined to search for blocks. Provided: %n, limit: %n", 225  height, 226  limit 227  ); 228  logger.trace("[getStoredBlockAtMainChainHeight] {}", message); 229  throw new BlockStoreException(message); 230  } 231  } 232  233  StoredBlock block; 234  Optional<StoredBlock> blockOptional = getInMainchain(height); 235  if (blockOptional.isPresent()) { 236  block = blockOptional.get(); 237  } else { 238  block = getStoredBlockAtMainChainDepth(depth); 239  } 240  241  return block; 242  } 243  244  private synchronized void populateCache(StoredBlock chainHead) { 245  logger.trace("Populating BTC Block Store Cache."); 246  if (this.btcNetworkParams.getGenesisBlock().equals(chainHead.getHeader())) { 247  return; 248  } 249  cacheBlocks.put(chainHead.getHeader().getHash(), chainHead); 250  Sha256Hash blockHash = chainHead.getHeader().getPrevBlockHash(); 251  int depth = this.maxDepthBlockCache - 1; 252  while (blockHash != null && depth > 0) { 253  if (cacheBlocks.get(blockHash) != null) { 254  break; 255  } 256  StoredBlock currentBlock = get(blockHash); 257  if (currentBlock == null) { 258  break; 259  } 260  cacheBlocks.put(currentBlock.getHeader().getHash(), currentBlock); 261  depth--; 262  blockHash = currentBlock.getHeader().getPrevBlockHash(); 263  } 264  logger.trace("END Populating BTC Block Store Cache."); 265  } 266  267  @Override 268  @Deprecated 269  public StoredBlock getStoredBlockAtMainChainDepth(int depth) throws BlockStoreException { 270  logger.trace("[getStoredBlockAtMainChainDepth] Looking for block at depth {}", depth); 271  StoredBlock chainHead = getChainHead(); 272  Sha256Hash blockHash = chainHead.getHeader().getHash(); 273  274  for (int i = 0; i < depth && blockHash != null; i++) { 275  //If its older than cache go to disk 276  StoredBlock currentBlock = getFromCache(blockHash); 277  if (currentBlock == null) { 278  logger.trace("[getStoredBlockAtMainChainDepth] Block with hash {} not in cache, getting from disk", blockHash); 279  currentBlock = get(blockHash); 280  if (currentBlock == null) { 281  return null; 282  } 283  } 284  blockHash = currentBlock.getHeader().getPrevBlockHash(); 285  } 286  287  if (blockHash == null) { 288  logger.trace("[getStoredBlockAtMainChainDepth] Block not found"); 289  return null; 290  } 291  StoredBlock block = getFromCache(blockHash); 292  if (block == null) { 293  block = get(blockHash); 294  } 295  int expectedHeight = chainHead.getHeight() - depth; 296  if (block != null && block.getHeight() != expectedHeight) { 297  String message = String.format("Block %s at depth %d Height is %d but should be %d", 298  block.getHeader().getHash(), 299  depth, 300  block.getHeight(), 301  expectedHeight 302  ); 303  logger.trace("[getStoredBlockAtMainChainDepth] {}", message); 304  throw new BlockStoreException(message); 305  } 306  307  return block; 308  } 309  310  private byte[] storedBlockToByteArray(StoredBlock block) { 311  ByteBuffer byteBuffer = ByteBuffer.allocate(128); 312  block.serializeCompact(byteBuffer); 313  byte[] ba = new byte[byteBuffer.position()]; 314  byteBuffer.flip(); 315  byteBuffer.get(ba); 316  return ba; 317  } 318  319  private StoredBlock byteArrayToStoredBlock(byte[] ba) { 320  ByteBuffer byteBuffer = ByteBuffer.wrap(ba); 321  return StoredBlock.deserializeCompact(btcNetworkParams, byteBuffer); 322  } 323  324  private void checkIfInitialized() { 325  if (getChainHead() == null) { 326  BtcBlock genesisHeader = this.btcNetworkParams.getGenesisBlock().cloneAsHeader(); 327  StoredBlock storedGenesis = new StoredBlock(genesisHeader, genesisHeader.getWork(), 0); 328  put(storedGenesis); 329  setChainHead(storedGenesis); 330  } 331  } 332  333  public static class Factory implements BtcBlockStoreWithCache.Factory { 334  335  private final int maxSizeBlockCache; 336  //This is ok as we don't have parallel execution, in the feature we should move to a concurrentHashMap 337  private final Map<Sha256Hash, StoredBlock> cacheBlocks; 338  private final RskAddress contractAddress; 339  private final NetworkParameters btcNetworkParams; 340  private final int maxDepthBlockCache; 341  342  @VisibleForTesting 343  public Factory(NetworkParameters btcNetworkParams) { 344  this(btcNetworkParams, DEFAULT_MAX_DEPTH_BLOCK_CACHE, DEFAULT_MAX_SIZE_BLOCK_CACHE); 345  } 346  347  public Factory(NetworkParameters btcNetworkParams, int maxDepthBlockCache, int maxSizeBlockCache) { 348  this.contractAddress = PrecompiledContracts.BRIDGE_ADDR; 349  this.btcNetworkParams = btcNetworkParams; 350  this.maxDepthBlockCache = maxDepthBlockCache; 351  this.maxSizeBlockCache = maxSizeBlockCache; 352  this.cacheBlocks = new MaxSizeHashMap<>(this.maxSizeBlockCache, true); 353  354  if (this.maxDepthBlockCache > this.maxSizeBlockCache) { 355  logger.warn("Max depth ({}) is greater than Max Size ({}). This could lead to a misbehaviour.", this.maxDepthBlockCache, this.maxSizeBlockCache); 356  } 357  358  if (this.maxDepthBlockCache < DEFAULT_MAX_DEPTH_BLOCK_CACHE) { 359  logger.warn("Max depth ({}) is lower than the default ({}). This could lead to a misbehaviour.", this.maxDepthBlockCache, DEFAULT_MAX_DEPTH_BLOCK_CACHE); 360  } 361  } 362  363  @Override 364  public BtcBlockStoreWithCache newInstance( 365  Repository track, 366  BridgeConstants bridgeConstants, 367  BridgeStorageProvider bridgeStorageProvider, 368  ActivationConfig.ForBlock activations) { 369  370  return new RepositoryBtcBlockStoreWithCache( 371  btcNetworkParams, 372  track, 373  cacheBlocks, 374  contractAddress, 375  bridgeConstants, 376  bridgeStorageProvider, 377  activations, 378  this.maxDepthBlockCache 379  ); 380  } 381  } 382 }