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