co.rsk.remasc.Remasc.java Source code

Java tutorial

Introduction

Here is the source code for co.rsk.remasc.Remasc.java

Source

/*
 * This file is part of RskJ
 * Copyright (C) 2017 RSK Labs Ltd.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */

package co.rsk.remasc;

import co.rsk.config.RemascConfig;
import org.apache.commons.collections4.CollectionUtils;
import org.ethereum.core.Block;
import org.ethereum.core.BlockHeader;
import org.ethereum.core.Repository;
import org.ethereum.core.Transaction;
import org.ethereum.db.BlockStore;
import org.ethereum.util.BIUtil;
import org.ethereum.util.FastByteComparisons;
import org.ethereum.vm.PrecompiledContracts;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.spongycastle.util.encoders.Hex;

import java.math.BigInteger;
import java.util.ArrayList;
import java.util.List;

/**
 * Implements the actual Remasc distribution logic
 */
public class Remasc {
    private static final Logger logger = LoggerFactory.getLogger(Remasc.class);

    private RemascConfig remascConstants;
    private RemascStorageProvider provider;

    private final Transaction executionTx;
    private Repository repository;

    private Block executionBlock;
    private BlockStore blockStore;

    Remasc(Transaction executionTx, Repository repository, String contractAddress, Block executionBlock,
            BlockStore blockStore, RemascConfig remascConstants) {
        this.executionTx = executionTx;
        this.repository = repository;
        this.executionBlock = executionBlock;
        this.blockStore = blockStore;
        this.remascConstants = remascConstants;
        this.provider = new RemascStorageProvider(repository, contractAddress);
    }

    public void save() {
        provider.save();
    }

    /**
     * Returns the internal contract state.
     * @return the internal contract state.
     */
    public RemascState getStateForDebugging() {
        return new RemascState(this.provider.getRewardBalance(), this.provider.getBurnedBalance(),
                this.provider.getSiblings(), this.provider.getBrokenSelectionRule());
    }

    /**
     * Implements the actual Remasc distribution logic
     */
    void processMinersFees() {
        if (!(executionTx instanceof RemascTransaction)) {
            //Detect
            // 1) tx to remasc that is not the latest tx in a block
            // 2) invocation to remasc from another contract (ie call opcode)
            throw new RemascInvalidInvocationException("Invoked Remasc outside last tx of the block");
        }
        this.addNewSiblings();

        long blockNbr = executionBlock.getNumber();

        long processingBlockNumber = blockNbr - remascConstants.getMaturity();
        if (processingBlockNumber < 1) {
            logger.debug("First block has not reached maturity yet, current block is {}", blockNbr);
            return;
        }
        BlockHeader processingBlockHeader = blockStore
                .getBlockByHashAndDepth(executionBlock.getParentHash(), remascConstants.getMaturity() - 1)
                .getHeader();
        // Adds current block fees to accumulated rewardBalance
        BigInteger processingBlockReward = BigInteger.valueOf(processingBlockHeader.getPaidFees());
        BigInteger rewardBalance = provider.getRewardBalance();
        rewardBalance = rewardBalance.add(processingBlockReward);
        provider.setRewardBalance(rewardBalance);

        if (processingBlockNumber - remascConstants.getSyntheticSpan() < 0) {
            logger.debug("First block has not reached maturity+syntheticSpan yet, current block is {}",
                    executionBlock.getNumber());
            return;
        }

        // Takes from rewardBalance this block's height reward.
        BigInteger fullBlockReward = rewardBalance.divide(BigInteger.valueOf(remascConstants.getSyntheticSpan()));
        rewardBalance = rewardBalance.subtract(fullBlockReward);
        provider.setRewardBalance(rewardBalance);

        // Pay RSK labs cut
        BigInteger payToRskLabs = fullBlockReward.divide(BigInteger.valueOf(remascConstants.getRskLabsDivisor()));
        transfer(remascConstants.getRskLabsAddress(), payToRskLabs);
        fullBlockReward = fullBlockReward.subtract(payToRskLabs);

        List<Sibling> siblings = provider.getSiblings().get(processingBlockNumber);

        if (CollectionUtils.isNotEmpty(siblings)) {
            // Block has siblings, reward distribution is more complex
            boolean previousBrokenSelectionRule = provider.getBrokenSelectionRule();
            this.payWithSiblings(processingBlockHeader, fullBlockReward, siblings, previousBrokenSelectionRule);
            boolean brokenSelectionRule = this.isBrokenSelectionRule(processingBlockHeader, siblings);
            provider.setBrokenSelectionRule(brokenSelectionRule);
        } else {
            if (provider.getBrokenSelectionRule()) {
                // broken selection rule, apply punishment, ie burn part of the reward.
                BigInteger punishment = fullBlockReward
                        .divide(BigInteger.valueOf(remascConstants.getPunishmentDivisor()));
                fullBlockReward = fullBlockReward.subtract(punishment);
                provider.setBurnedBalance(provider.getBurnedBalance().add(punishment));
            }
            transfer(processingBlockHeader.getCoinbase(), fullBlockReward);
            provider.setBrokenSelectionRule(Boolean.FALSE);
        }

        this.removeUsedSiblings(processingBlockHeader);
    }

    /**
     * Remove siblings just processed if any
     */
    private void removeUsedSiblings(BlockHeader processingBlockHeader) {
        provider.getSiblings().remove(processingBlockHeader.getNumber());
    }

    /**
     * Saves uncles of the current block into the siblings map to use in the future for fee distribution
     */
    private void addNewSiblings() {
        // Add uncles of the execution block to the siblings map
        List<BlockHeader> uncles = executionBlock.getUncleList();
        if (CollectionUtils.isNotEmpty(uncles)) {
            for (BlockHeader uncleHeader : uncles) {
                List<Sibling> siblings = provider.getSiblings().get(uncleHeader.getNumber());
                if (siblings == null)
                    siblings = new ArrayList<>();
                siblings.add(new Sibling(uncleHeader, executionBlock.getHeader().getCoinbase(),
                        executionBlock.getNumber()));
                provider.getSiblings().put(uncleHeader.getNumber(), siblings);
            }
        }
    }

    /**
     * Pay the mainchain block miner, its siblings miners and the publisher miners
     */
    private void payWithSiblings(BlockHeader processingBlockHeader, BigInteger fullBlockReward,
            List<Sibling> siblings, boolean previousBrokenSelectionRule) {
        SiblingPaymentCalculator paymentCalculator = new SiblingPaymentCalculator(fullBlockReward,
                previousBrokenSelectionRule, siblings.size(), this.remascConstants);

        this.payPublishersWhoIncludedSiblings(siblings, paymentCalculator.getIndividualPublisherReward());
        provider.addToBurnBalance(paymentCalculator.getPublishersSurplus());

        provider.addToBurnBalance(paymentCalculator.getMinersSurplus());

        this.payIncludedSiblings(siblings, paymentCalculator.getIndividualMinerReward());
        if (previousBrokenSelectionRule) {
            provider.addToBurnBalance(
                    paymentCalculator.getPunishment().multiply(BigInteger.valueOf(siblings.size() + 1L)));
        }

        // Pay to main chain block miner
        transfer(processingBlockHeader.getCoinbase(), paymentCalculator.getIndividualMinerReward());
    }

    private void payPublishersWhoIncludedSiblings(List<Sibling> siblings, BigInteger minerReward) {
        for (Sibling sibling : siblings) {
            transfer(sibling.getIncludedBlockCoinbase(), minerReward);
        }
    }

    private void payIncludedSiblings(List<Sibling> siblings, BigInteger topReward) {
        long perLateBlockPunishmentDivisor = remascConstants.getLateUncleInclusionPunishmentDivisor();
        for (Sibling sibling : siblings) {
            long processingBlockNumber = executionBlock.getNumber() - remascConstants.getMaturity();
            long numberOfBlocksLate = sibling.getIncludedHeight() - processingBlockNumber - 1L;
            BigInteger lateInclusionPunishment = topReward.multiply(BigInteger.valueOf(numberOfBlocksLate))
                    .divide(BigInteger.valueOf(perLateBlockPunishmentDivisor));
            transfer(sibling.getCoinbase(), topReward.subtract(lateInclusionPunishment));
            provider.addToBurnBalance(lateInclusionPunishment);
        }
    }

    private boolean isBrokenSelectionRule(BlockHeader processingBlockHeader, List<Sibling> siblings) {
        // Find out if main chain block selection rule was broken
        for (Sibling sibling : siblings) {
            // Sibling pays significant more fees than block in the main chain OR Sibling has lower hash than block in the main chain
            if (sibling.getPaidFees() > 2 * processingBlockHeader.getPaidFees() || FastByteComparisons
                    .compareTo(sibling.getHash(), 0, 32, processingBlockHeader.getHash(), 0, 32) < 0) {
                return true;
            }
        }
        return false;
    }

    private void transfer(byte[] toAddr, BigInteger value) {
        BIUtil.transfer(repository, Hex.decode(PrecompiledContracts.REMASC_ADDR), toAddr, value);
    }
}