Coverage Summary for Class: Remasc (co.rsk.remasc)

Class Class, % Method, % Line, %
Remasc 0% (0/1) 0% (0/13) 0% (0/111)


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.remasc; 20  21 import co.rsk.config.RemascConfig; 22 import co.rsk.core.Coin; 23 import co.rsk.core.RskAddress; 24 import co.rsk.core.bc.SelectionRule; 25 import co.rsk.rpc.modules.trace.ProgramSubtrace; 26 import org.ethereum.config.Constants; 27 import org.ethereum.config.blockchain.upgrades.ActivationConfig; 28 import org.ethereum.config.blockchain.upgrades.ConsensusRule; 29 import org.ethereum.core.Block; 30 import org.ethereum.core.BlockHeader; 31 import org.ethereum.core.Repository; 32 import org.ethereum.core.Transaction; 33 import org.ethereum.db.BlockStore; 34 import org.ethereum.vm.LogInfo; 35 import org.slf4j.Logger; 36 import org.slf4j.LoggerFactory; 37  38 import java.math.BigInteger; 39 import java.util.*; 40 import java.util.stream.Collectors; 41  42 /** 43  * Implements the actual Remasc distribution logic 44  * @author Oscar Guindzberg 45  */ 46 public class Remasc { 47  private static final Logger logger = LoggerFactory.getLogger(Remasc.class); 48  49  private final Constants constants; 50  private final ActivationConfig activationConfig; 51  private final Repository repository; 52  private final BlockStore blockStore; 53  private final RemascConfig remascConstants; 54  private final Transaction executionTx; 55  private final Block executionBlock; 56  private final List<LogInfo> logs; 57  58  private final RemascStorageProvider provider; 59  private final RemascFeesPayer feesPayer; 60  61  public Remasc( 62  Constants constants, 63  ActivationConfig activationConfig, 64  Repository repository, 65  BlockStore blockStore, 66  RemascConfig remascConstants, 67  Transaction executionTx, 68  RskAddress contractAddress, 69  Block executionBlock, 70  List<LogInfo> logs) { 71  this.repository = repository; 72  this.blockStore = blockStore; 73  this.remascConstants = remascConstants; 74  this.executionTx = executionTx; 75  this.executionBlock = executionBlock; 76  this.logs = logs; 77  78  this.provider = new RemascStorageProvider(repository, contractAddress); 79  this.feesPayer = new RemascFeesPayer(repository, contractAddress); 80  this.constants = constants; 81  this.activationConfig = activationConfig; 82  } 83  84  public void save() { 85  provider.save(); 86  } 87  88  public List<ProgramSubtrace> getSubtraces() { 89  return this.feesPayer.getSubtraces(); 90  } 91  92  /** 93  * Returns the internal contract state. 94  * @return the internal contract state. 95  */ 96  public RemascState getStateForDebugging() { 97  return new RemascState(this.provider.getRewardBalance(), this.provider.getBurnedBalance(), this.provider.getBrokenSelectionRule()); 98  } 99  100  101  /** 102  * Implements the actual Remasc distribution logic 103  */ 104  void processMinersFees() { 105  if (!(executionTx instanceof RemascTransaction)) { 106  //Detect 107  // 1) tx to remasc that is not the latest tx in a block 108  // 2) invocation to remasc from another contract (ie call opcode) 109  throw new RemascInvalidInvocationException("Invoked Remasc outside last tx of the block"); 110  } 111  112  long blockNbr = executionBlock.getNumber(); 113  114  long processingBlockNumber = blockNbr - remascConstants.getMaturity(); 115  if (processingBlockNumber < 1 ) { 116  logger.debug("First block has not reached maturity yet, current block is {}", blockNbr); 117  return; 118  } 119  120  int uncleGenerationLimit = constants.getUncleGenerationLimit(); 121  Deque<Map<Long, List<Sibling>>> descendantsBlocks = new LinkedList<>(); 122  123  // this search is now optimized if have certainty that the execution block is not in a fork 124  // larger than depth. The optimized algorithm already covers this case 125  Block currentBlock = blockStore.getBlockAtDepthStartingAt( 126  remascConstants.getMaturity() - 1 - uncleGenerationLimit, 127  executionBlock.getParentHash().getBytes() 128  ); 129  130  descendantsBlocks.push(blockStore.getSiblingsFromBlockByHash(currentBlock.getHash())); 131  132  // descendants are stored in reverse order because the original order to pay siblings is defined in the way 133  // blocks are ordered in the blockchain (the same as were stored in remasc contract) 134  for (int i = 0; i < uncleGenerationLimit - 1; i++) { 135  currentBlock = blockStore.getBlockByHash(currentBlock.getParentHash().getBytes()); 136  descendantsBlocks.push(blockStore.getSiblingsFromBlockByHash(currentBlock.getHash())); 137  } 138  139  Block processingBlock = blockStore.getBlockByHash(currentBlock.getParentHash().getBytes()); 140  BlockHeader processingBlockHeader = processingBlock.getHeader(); 141  142  // Adds current block fees to accumulated rewardBalance 143  Coin processingBlockReward = processingBlockHeader.getPaidFees(); 144  Coin rewardBalance = provider.getRewardBalance(); 145  rewardBalance = rewardBalance.add(processingBlockReward); 146  provider.setRewardBalance(rewardBalance); 147  148  if (processingBlockNumber - remascConstants.getSyntheticSpan() < 0 ) { 149  logger.debug("First block has not reached maturity+syntheticSpan yet, current block is {}", executionBlock.getNumber()); 150  return; 151  } 152  153  List<Sibling> siblings = getSiblingsToReward(descendantsBlocks, processingBlockNumber); 154  boolean previousBrokenSelectionRule = provider.getBrokenSelectionRule(); 155  boolean brokenSelectionRule = SelectionRule.isBrokenSelectionRule(processingBlockHeader, siblings); 156  provider.setBrokenSelectionRule(!siblings.isEmpty() && brokenSelectionRule); 157  158  // Takes from rewardBalance this block's height reward. 159  Coin syntheticReward = rewardBalance.divide(BigInteger.valueOf(remascConstants.getSyntheticSpan())); 160  boolean isRskip85Enabled = activationConfig.isActive(ConsensusRule.RSKIP85, blockNbr); 161  if (isRskip85Enabled) { 162  BigInteger minimumPayableGas = constants.getMinimumPayableGas(); 163  Coin minPayableFees = executionBlock.getMinimumGasPrice().multiply(minimumPayableGas); 164  if (syntheticReward.compareTo(minPayableFees) < 0) { 165  logger.debug("Synthetic Reward: {} is lower than minPayableFees: {} at block: {}", 166  syntheticReward, minPayableFees, executionBlock.getPrintableHash()); 167  return; 168  } 169  } 170  171  rewardBalance = rewardBalance.subtract(syntheticReward); 172  provider.setRewardBalance(rewardBalance); 173  174  // Pay RSK labs cut 175  RskAddress rskLabsAddress = getRskLabsAddress(); 176  Coin payToRskLabs = syntheticReward.divide(BigInteger.valueOf(remascConstants.getRskLabsDivisor())); 177  feesPayer.payMiningFees(processingBlockHeader.getHash().getBytes(), payToRskLabs, rskLabsAddress, logs); 178  syntheticReward = syntheticReward.subtract(payToRskLabs); 179  Coin payToFederation = payToFederation(constants, isRskip85Enabled, processingBlock, processingBlockHeader, syntheticReward); 180  syntheticReward = syntheticReward.subtract(payToFederation); 181  182  if (!siblings.isEmpty()) { 183  // Block has siblings, reward distribution is more complex 184  this.payWithSiblings(processingBlockHeader, syntheticReward, siblings, previousBrokenSelectionRule); 185  } else { 186  if (previousBrokenSelectionRule) { 187  // broken selection rule, apply punishment, ie burn part of the reward. 188  Coin punishment = syntheticReward.divide(BigInteger.valueOf(remascConstants.getPunishmentDivisor())); 189  syntheticReward = syntheticReward.subtract(punishment); 190  provider.setBurnedBalance(provider.getBurnedBalance().add(punishment)); 191  } 192  feesPayer.payMiningFees(processingBlockHeader.getHash().getBytes(), syntheticReward, processingBlockHeader.getCoinbase(), logs); 193  } 194  } 195  196  RskAddress getRskLabsAddress() { 197  boolean isRskip218Enabled = activationConfig.isActive(ConsensusRule.RSKIP218, executionBlock.getNumber()); 198  return isRskip218Enabled ? remascConstants.getRskLabsAddressRskip218() : remascConstants.getRskLabsAddress(); 199  } 200  201  private Coin payToFederation(Constants constants, boolean isRskip85Enabled, Block processingBlock, BlockHeader processingBlockHeader, Coin syntheticReward) { 202  RemascFederationProvider federationProvider = new RemascFederationProvider(activationConfig, constants.getBridgeConstants(), repository, processingBlock); 203  Coin federationReward = syntheticReward.divide(BigInteger.valueOf(remascConstants.getFederationDivisor())); 204  205  Coin payToFederation = provider.getFederationBalance().add(federationReward); 206  byte[] processingBlockHash = processingBlockHeader.getHash().getBytes(); 207  int nfederators = federationProvider.getFederationSize(); 208  Coin[] payAndRemainderToFederator = payToFederation.divideAndRemainder(BigInteger.valueOf(nfederators)); 209  Coin payToFederator = payAndRemainderToFederator[0]; 210  Coin restToLastFederator = payAndRemainderToFederator[1]; 211  212  if (isRskip85Enabled) { 213  BigInteger minimumFederatorPayableGas = constants.getFederatorMinimumPayableGas(); 214  Coin minPayableFederatorFees = executionBlock.getMinimumGasPrice().multiply(minimumFederatorPayableGas); 215  if (payToFederator.compareTo(minPayableFederatorFees) < 0) { 216  provider.setFederationBalance(payToFederation); 217  return federationReward; 218  } else { // balance goes to zero because all federation balance will be distributed 219  provider.setFederationBalance(Coin.ZERO); 220  } 221  } 222  223  for (int k = 0; k < nfederators; k++) { 224  RskAddress federatorAddress = federationProvider.getFederatorAddress(k); 225  226  if (k == nfederators - 1 && restToLastFederator.compareTo(Coin.ZERO) > 0) { 227  feesPayer.payMiningFees(processingBlockHash, payToFederator.add(restToLastFederator), federatorAddress, logs); 228  } else { 229  feesPayer.payMiningFees(processingBlockHash, payToFederator, federatorAddress, logs); 230  } 231  232  } 233  234  return federationReward; 235  } 236  237  /** 238  * Descendants included on the same chain as the processing block could include siblings 239  * that should be rewarded when fees on this block are paid 240  * @param descendants blocks in the same blockchain that may include rewarded siblings 241  * @param blockNumber number of the block is looked for siblings 242  * @return 243  */ 244  private List<Sibling> getSiblingsToReward(Deque<Map<Long, List<Sibling>>> descendants, long blockNumber) { 245  return descendants.stream() 246  .flatMap(map -> map.getOrDefault(blockNumber, Collections.emptyList()).stream()) 247  .collect(Collectors.toList()); 248  } 249  250  /** 251  * Pay the mainchain block miner, its siblings miners and the publisher miners 252  */ 253  private void payWithSiblings(BlockHeader processingBlockHeader, Coin fullBlockReward, List<Sibling> siblings, boolean previousBrokenSelectionRule) { 254  SiblingPaymentCalculator paymentCalculator = new SiblingPaymentCalculator(fullBlockReward, previousBrokenSelectionRule, siblings.size(), this.remascConstants); 255  256  byte[] processingBlockHeaderHash = processingBlockHeader.getHash().getBytes(); 257  this.payPublishersWhoIncludedSiblings(processingBlockHeaderHash, siblings, paymentCalculator.getIndividualPublisherReward()); 258  provider.addToBurnBalance(paymentCalculator.getPublishersSurplus()); 259  260  provider.addToBurnBalance(paymentCalculator.getMinersSurplus()); 261  262  this.payIncludedSiblings(processingBlockHeaderHash, siblings, paymentCalculator.getIndividualMinerReward()); 263  if (previousBrokenSelectionRule) { 264  provider.addToBurnBalance(paymentCalculator.getPunishment().multiply(BigInteger.valueOf(siblings.size() + 1L))); 265  } 266  267  // Pay to main chain block miner 268  feesPayer.payMiningFees(processingBlockHeaderHash, paymentCalculator.getIndividualMinerReward(), processingBlockHeader.getCoinbase(), logs); 269  } 270  271  private void payPublishersWhoIncludedSiblings(byte[] blockHash, List<Sibling> siblings, Coin minerReward) { 272  for (Sibling sibling : siblings) { 273  feesPayer.payMiningFees(blockHash, minerReward, sibling.getIncludedBlockCoinbase(), logs); 274  } 275  } 276  277  private void payIncludedSiblings(byte[] blockHash, List<Sibling> siblings, Coin topReward) { 278  long perLateBlockPunishmentDivisor = remascConstants.getLateUncleInclusionPunishmentDivisor(); 279  for (Sibling sibling : siblings) { 280  long processingBlockNumber = executionBlock.getNumber() - remascConstants.getMaturity(); 281  long numberOfBlocksLate = sibling.getIncludedHeight() - processingBlockNumber - 1L; 282  Coin lateInclusionPunishment = topReward.multiply(BigInteger.valueOf(numberOfBlocksLate)).divide(BigInteger.valueOf(perLateBlockPunishmentDivisor)); 283  feesPayer.payMiningFees(blockHash, topReward.subtract(lateInclusionPunishment), sibling.getCoinbase(), logs); 284  provider.addToBurnBalance(lateInclusionPunishment); 285  } 286  } 287  288 } 289