burstcoin.jminer.core.round.Round.java Source code

Java tutorial

Introduction

Here is the source code for burstcoin.jminer.core.round.Round.java

Source

/*
 * The MIT License (MIT)
 *
 * Copyright (c) 2016 by luxe - https://github.com/de-luxe - BURST-LUXE-RED2-G6JW-H4HG5
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software
 * and associated documentation files (the "Software"), to deal in the Software without restriction,
 * including without limitation the rights to use, copy, modify, merge, publish, distribute,
 * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software
 * is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all copies
 * or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
 * BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 *
 */

package burstcoin.jminer.core.round;

import burstcoin.jminer.core.CoreProperties;
import burstcoin.jminer.core.checker.Checker;
import burstcoin.jminer.core.checker.event.CheckerDevResultEvent;
import burstcoin.jminer.core.checker.event.CheckerResultEvent;
import burstcoin.jminer.core.network.Network;
import burstcoin.jminer.core.network.event.NetworkDevResultConfirmedEvent;
import burstcoin.jminer.core.network.event.NetworkResultConfirmedEvent;
import burstcoin.jminer.core.network.event.NetworkResultErrorEvent;
import burstcoin.jminer.core.network.event.NetworkStateChangeEvent;
import burstcoin.jminer.core.network.model.DevPoolResult;
import burstcoin.jminer.core.reader.Reader;
import burstcoin.jminer.core.reader.data.Plots;
import burstcoin.jminer.core.reader.event.ReaderProgressChangedEvent;
import burstcoin.jminer.core.reader.event.ReaderStoppedEvent;
import burstcoin.jminer.core.round.event.RoundFinishedEvent;
import burstcoin.jminer.core.round.event.RoundSingleResultEvent;
import burstcoin.jminer.core.round.event.RoundSingleResultSkippedEvent;
import burstcoin.jminer.core.round.event.RoundStartedEvent;
import burstcoin.jminer.core.round.event.RoundStoppedEvent;
import burstcoin.jminer.core.round.task.RoundFireEventTask;
import fr.cryptohash.Shabal256;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.annotation.Scope;
import org.springframework.context.event.EventListener;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;
import pocminer.generate.MiningPlot;

import javax.annotation.PostConstruct;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;

/**
 * The type Round.
 */
@Component
@Scope("singleton")
public class Round implements NetworkStateChangeEvent.Handler, NetworkResultErrorEvent.Handler {
    private static final Logger LOG = LoggerFactory.getLogger(Round.class);

    @Autowired
    private ApplicationContext context;

    @Autowired
    private ThreadPoolTaskExecutor roundPool;

    private final Reader reader;
    private final Checker checker;
    private final Network network;

    private boolean poolMining;
    private long targetDeadline;

    private Timer timer;
    private long blockNumber;
    private long finishedBlockNumber;
    private long baseTarget;
    private Date roundStartDate;

    private BigInteger lowest;
    private long bestCommittedDeadline;

    // dev pool
    private boolean devPool;
    private List<DevPoolResult> devPoolResults;
    private int devPoolCommitsThisRound;
    private int devPoolCommitsPerRound;// one additional on finish round

    private Set<Long> runningChunkPartStartNonces;
    private Plots plots;

    /**
     * Instantiates a new Round.
     *
     * @param reader  the reader
     * @param checker the checker
     * @param network the network
     */
    @Autowired
    public Round(Reader reader, Checker checker, Network network) {
        this.reader = reader;
        this.checker = checker;
        this.network = network;
    }

    /**
     * Post construct.
     */
    @PostConstruct
    protected void postConstruct() {
        this.poolMining = CoreProperties.isPoolMining();
        this.devPool = CoreProperties.isDevPool();
        this.devPoolCommitsPerRound = CoreProperties.getDevPoolCommitsPerRound();

        timer = new Timer();
    }

    private void initNewRound(Plots plots) {
        runningChunkPartStartNonces = new HashSet<>(plots.getChunkPartStartNonces().keySet());
        roundStartDate = new Date();
        lowest = new BigInteger("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 16);
        devPoolResults = new ArrayList<>();
        bestCommittedDeadline = Long.MAX_VALUE;
        devPoolCommitsThisRound = devPoolCommitsPerRound;
    }

    @EventListener
    public void handleMessage(NetworkStateChangeEvent event) {
        if (blockNumber < event.getBlockNumber()) {
            long previousBlockNumber = blockNumber;
            this.blockNumber = event.getBlockNumber();
            this.baseTarget = event.getBaseTarget();
            this.targetDeadline = event.getTargetDeadline();

            long lastBestCommittedDeadline = bestCommittedDeadline;

            plots = reader.getPlots();
            initNewRound(plots);

            // reconfigure checker
            checker.reconfigure(blockNumber, baseTarget, targetDeadline, event.getGenerationSignature());

            // start reader
            int scoopNumber = calcScoopNumber(event.getBlockNumber(), event.getGenerationSignature());
            reader.read(previousBlockNumber, blockNumber, scoopNumber, lastBestCommittedDeadline);

            // ui event
            fireEvent(new RoundStartedEvent(blockNumber, scoopNumber, plots.getSize(), targetDeadline, baseTarget));

            timer.schedule(new TimerTask() {
                @Override
                public void run() {
                    network.checkLastWinner(blockNumber);
                }
            }, 0); // deferred
        }
    }

    /**
     * Handle message.
     *
     * @param event the event
     */
    @EventListener
    public void handleMessage(CheckerResultEvent event) {
        if (blockNumber == event.getBlockNumber()) {
            // check new lowest result
            if (event.getResult() != null) {
                long nonce = event.getNonce();
                BigInteger deadline = event.getResult().divide(BigInteger.valueOf(baseTarget));
                long calculatedDeadline = deadline.longValue();

                if (devPool) {
                    if (calculatedDeadline < targetDeadline) {
                        // remember for next triggered commit
                        devPoolResults.add(new DevPoolResult(event.getBlockNumber(), calculatedDeadline,
                                event.getNonce(), event.getChunkPartStartNonce()));
                    } else {
                        // todo there will be a lot of skipped, makes no sense, cause lowest not used here, remove or make adjustable by setting.
                        //            publisher.publishEvent(new RoundSingleResultSkippedEvent(this, event.getBlockNumber(), nonce, event.getChunkPartStartNonce(), calculatedDeadline,
                        //                                                                     targetDeadline, poolMining));
                        runningChunkPartStartNonces.remove(event.getChunkPartStartNonce());
                        triggerFinishRoundEvent(event.getBlockNumber());
                    }
                } else {
                    if (event.getResult().compareTo(lowest) < 0) {
                        lowest = event.getResult();
                        if (calculatedDeadline < targetDeadline) {
                            network.commitResult(blockNumber, calculatedDeadline, nonce,
                                    event.getChunkPartStartNonce(), plots.getSize());

                            // ui event
                            fireEvent(new RoundSingleResultEvent(this, event.getBlockNumber(), nonce,
                                    event.getChunkPartStartNonce(), calculatedDeadline, poolMining));
                        } else {
                            // ui event
                            fireEvent(new RoundSingleResultSkippedEvent(this, event.getBlockNumber(), nonce,
                                    event.getChunkPartStartNonce(), calculatedDeadline, targetDeadline,
                                    poolMining));
                            // chunkPartStartNonce finished
                            runningChunkPartStartNonces.remove(event.getChunkPartStartNonce());
                            triggerFinishRoundEvent(event.getBlockNumber());
                        }
                    } else {
                        // chunkPartStartNonce finished
                        runningChunkPartStartNonces.remove(event.getChunkPartStartNonce());
                        triggerFinishRoundEvent(event.getBlockNumber());
                    }
                }
            } else {
                LOG.error("CheckerResultEvent result == null");
            }
        } else {
            LOG.trace("event for previous block ...");
        }
    }

    /**
     * Handle message.
     *
     * @param event the event
     */
    @EventListener
    public void handleMessage(CheckerDevResultEvent event) {
        if (blockNumber == event.getBlockNumber()) {
            if (event.hasResults()) {
                // remember for next triggered commit
                devPoolResults.addAll(event.getDevPoolResults());
            } else {
                runningChunkPartStartNonces.remove(event.getChunkPartStartNonce());
                triggerFinishRoundEvent(event.getBlockNumber());
            }
        } else {
            LOG.trace("event for previous block ...");
        }
    }

    /**
     * triggers commit devPool nonces if needed, there will be 'numberOfDevPoolCommitsPerRound'
     *
     * @param event read progress
     */
    @EventListener
    public void handleMessage(ReaderProgressChangedEvent event) {
        if (devPool && blockNumber == event.getBlockNumber()) {
            if (devPoolCommitsPerRound == devPoolCommitsThisRound) {
                devPoolCommitsThisRound--;
            } else if (event.getRemainingCapacity() < (event.getCapacity() / devPoolCommitsPerRound)
                    * devPoolCommitsThisRound) {
                devPoolCommitsThisRound--;
                LOG.debug("trigger dev commit by progress #" + (devPoolCommitsPerRound - devPoolCommitsThisRound)
                        + " this round.");
                if (!devPoolResults.isEmpty()) {
                    commitDevPoolNonces(event.getBlockNumber());
                } else {
                    LOG.info("no shares to commit to dev pool ...");
                }
            }
        }
    }

    /**
     * Handle message.
     *
     * @param event the event
     */
    @EventListener
    public void handleMessage(NetworkResultConfirmedEvent event) {
        if (blockNumber == event.getBlockNumber()) {
            runningChunkPartStartNonces.remove(event.getChunkPartStartNonce());
            bestCommittedDeadline = event.getDeadline();
            triggerFinishRoundEvent(event.getBlockNumber());
        }
    }

    /**
     * Handle message.
     *
     * @param event the event
     */
    @EventListener
    public void handleMessage(NetworkDevResultConfirmedEvent event) {
        if (blockNumber == event.getBlockNumber()) {
            for (DevPoolResult devPoolResult : event.getDevPoolResults()) {
                if (!runningChunkPartStartNonces.remove(devPoolResult.getChunkPartStartNonce())) {
                    LOG.error("unknown chunkPartStartNonce in devResult." + devPoolResult.getChunkPartStartNonce());
                }
                if (devPoolResult.getCalculatedDeadline() < bestCommittedDeadline) {
                    // this is not really a committed just the calculated, cause devPool does not provide deadlines
                    // therefore no chance to validate a commit.
                    bestCommittedDeadline = devPoolResult.getCalculatedDeadline();
                }
            }
            triggerFinishRoundEvent(event.getBlockNumber());
        }
    }

    @Override
    @EventListener
    public void handleMessage(NetworkResultErrorEvent event) {
        if (blockNumber == event.getBlockNumber()) {
            runningChunkPartStartNonces.remove(event.getChunkPartStartNonce());
            triggerFinishRoundEvent(event.getBlockNumber());
        }
    }

    @EventListener
    public void handleMessage(ReaderStoppedEvent event) {
        System.gc();
        fireEvent(new RoundStoppedEvent(event.getBlockNumber(), event.getLastBestCommittedDeadline(),
                event.getCapacity(), event.getRemainingCapacity(), event.getElapsedTime()));
    }

    private void triggerFinishRoundEvent(long blockNumber) {
        if (finishedBlockNumber < blockNumber) {
            if (runningChunkPartStartNonces.isEmpty()) {
                onRoundFinish(blockNumber);
            }
            // commit last devPool results
            else if (devPool && runningChunkPartStartNonces.size() == devPoolResults.size()) {
                commitDevPoolNonces(blockNumber);
            }
        }
    }

    private void onRoundFinish(long blockNumber) {
        finishedBlockNumber = blockNumber;
        long elapsedRoundTime = new Date().getTime() - roundStartDate.getTime();
        triggerGarbageCollection();
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                fireEvent(new RoundFinishedEvent(blockNumber, bestCommittedDeadline, elapsedRoundTime));
            }
        }, 250); // fire deferred

        triggerCleanup();
    }

    private void triggerCleanup() {
        TimerTask cleanupTask = new TimerTask() {
            @Override
            public void run() {
                if (!reader.cleanupReaderPool()) {
                    triggerCleanup();
                }
            }
        };

        try {
            timer.schedule(cleanupTask, 1000);
        } catch (IllegalStateException e) {
            LOG.error("cleanup task already scheduled ...");
        }
    }

    private void commitDevPoolNonces(long blockNumber) {
        network.commitDevResult(blockNumber, new ArrayList<>(devPoolResults));
        devPoolResults.clear();

        // todo ui event
        LOG.info("shares, committed to devPool ...");
    }

    // not needed, just to force java to free memory (depending on gc used)
    private void triggerGarbageCollection() {
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                LOG.debug("trigger garbage collection ... ");
                System.gc();
            }
        }, 1500);
    }

    private <EVENT extends ApplicationEvent> void fireEvent(EVENT event) {
        RoundFireEventTask roundFireEventTask = context.getBean(RoundFireEventTask.class);
        roundFireEventTask.init(event);
        roundPool.execute(roundFireEventTask);
    }

    private static int calcScoopNumber(long blockNumber, byte[] generationSignature) {
        if (blockNumber > 0 && generationSignature != null) {
            ByteBuffer buf = ByteBuffer.allocate(32 + 8);
            buf.put(generationSignature);
            buf.putLong(blockNumber);

            // generate new scoop number
            Shabal256 md = new Shabal256();
            md.update(buf.array());

            BigInteger hashnum = new BigInteger(1, md.digest());
            return hashnum.mod(BigInteger.valueOf(MiningPlot.SCOOPS_PER_PLOT)).intValue();
        }
        return 0;
    }
}