Permalink
Fetching contributors…
Cannot retrieve contributors at this time
380 lines (336 sloc) 12.3 KB
/*
* 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.CheckerResultEvent;
import burstcoin.jminer.core.network.Network;
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.reader.Reader;
import burstcoin.jminer.core.reader.data.Plots;
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.Date;
import java.util.HashSet;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;
/**
* The type Round.
*/
@Component
@Scope("singleton")
public class Round
{
private static final Logger LOG = LoggerFactory.getLogger(Round.class);
private final ApplicationContext context;
private final 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;
// cache for next lowest
private CheckerResultEvent queuedEvent;
private BigInteger lowestCommitted;
private Set<BigInteger> runningChunkPartStartNonces;
private Plots plots;
@Autowired
public Round(Reader reader, Checker checker, Network network, ThreadPoolTaskExecutor roundPool, ApplicationContext context)
{
this.reader = reader;
this.checker = checker;
this.network = network;
this.roundPool = roundPool;
this.context = context;
}
@PostConstruct
protected void postConstruct()
{
this.poolMining = CoreProperties.isPoolMining();
timer = new Timer();
}
private void initNewRound(Plots plots)
{
runningChunkPartStartNonces = new HashSet<>(plots.getChunkPartStartNonces().keySet());
roundStartDate = new Date();
lowest = new BigInteger("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 16);
lowestCommitted = new BigInteger("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 16);
queuedEvent = null;
bestCommittedDeadline = Long.MAX_VALUE;
}
@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, 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
}
}
@EventListener
public void handleMessage(CheckerResultEvent event)
{
if(blockNumber == event.getBlockNumber())
{
// check new lowest result
if(event.getResult() != null)
{
BigInteger deadline = event.getResult().divide(BigInteger.valueOf(baseTarget));
long calculatedDeadline = deadline.longValue();
if(event.getResult().compareTo(lowest) < 0)
{
lowest = event.getResult();
if(calculatedDeadline < targetDeadline)
{
network.commitResult(blockNumber, calculatedDeadline, event.getNonce(), event.getChunkPartStartNonce(), plots.getSize(), event.getResult());
// ui event
fireEvent(new RoundSingleResultEvent(event.getBlockNumber(), event.getNonce(), event.getChunkPartStartNonce(), calculatedDeadline,
poolMining));
}
else
{
// ui event
if(CoreProperties.isShowSkippedDeadlines())
{
fireEvent(new RoundSingleResultSkippedEvent(event.getBlockNumber(), event.getNonce(), event.getChunkPartStartNonce(), calculatedDeadline,
targetDeadline, poolMining));
}
// chunkPartStartNonce finished
runningChunkPartStartNonces.remove(event.getChunkPartStartNonce());
triggerFinishRoundEvent(event.getBlockNumber());
}
}
// remember next lowest in case that lowest fails to commit
else if(calculatedDeadline < targetDeadline
&& event.getResult().compareTo(lowestCommitted) < 0
&& (queuedEvent == null || event.getResult().compareTo(queuedEvent.getResult()) < 0))
{
if(queuedEvent != null)
{
// remove previous queued
runningChunkPartStartNonces.remove(queuedEvent.getChunkPartStartNonce());
}
LOG.info("dl '" + calculatedDeadline + "' queued");
queuedEvent = event;
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 ...");
}
}
@EventListener
public void handleMessage(NetworkResultConfirmedEvent event)
{
if(blockNumber == event.getBlockNumber())
{
lowestCommitted = event.getResult();
// if queuedLowest exist and is higher than lowestCommitted, remove queuedLowest
if(queuedEvent != null && lowestCommitted.compareTo(queuedEvent.getResult()) < 0)
{
BigInteger dl = queuedEvent.getResult().divide(BigInteger.valueOf(baseTarget));
LOG.debug("dl '" + dl + "' removed from queue");
runningChunkPartStartNonces.remove(queuedEvent.getChunkPartStartNonce());
queuedEvent = null;
}
runningChunkPartStartNonces.remove(event.getChunkPartStartNonce());
if(bestCommittedDeadline > event.getDeadline())
{
bestCommittedDeadline = event.getDeadline();
}
triggerFinishRoundEvent(event.getBlockNumber());
}
}
@EventListener
public void handleMessage(NetworkResultErrorEvent event)
{
if(blockNumber == event.getBlockNumber())
{
// reset lowest to lowestCommitted, as it does not commit successful.
lowest = lowestCommitted;
// in case that queued result is lower than committedLowest, commit queued again.
if(queuedEvent != null && lowestCommitted.compareTo(queuedEvent.getResult()) < 0)
{
LOG.info("commit queued dl ...");
handleMessage(queuedEvent);
queuedEvent = null;
}
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 queued if exists ... and it is the only remaining in runningChunkPartStartNonces
else if(queuedEvent != null && runningChunkPartStartNonces.size() == 1 && runningChunkPartStartNonces.contains(queuedEvent.getChunkPartStartNonce()))
{
handleMessage(queuedEvent);
queuedEvent = null;
}
}
}
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 ...");
}
}
// 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.trace("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;
}
}