/// <summary> /// Create a new instance of this class. /// </summary> /// <param name="deadline"> /// The duration of the deadline. /// </param> /// <param name="scoop"> /// The scoop the dealdine came from. /// The</param> /// <param name="miningInfo"> /// The information about the network that this deadline was found matching. /// </param> public Deadline(TimeSpan deadline, Scoop scoop, MiningInfo miningInfo) { Status = DeadlineStatus.Found; DeadlineDuration = deadline; Scoop = scoop; MiningInfo = miningInfo; NextSubmissionDate = DateTime.UtcNow; }
/// <summary> /// Resets the queue and specifies the current block height. Any scoops received for different block heights will be ignored. /// </summary> /// <param name="miningInfo"> /// The details of which block is currently being processed. /// </param> public void NotifyNewRound(MiningInfo miningInfo) { // Store the information and then clear the queue lock (_scoopQueueLocker) { _miningInfo = miningInfo; _scoopQueue.Clear(); } }
/// <summary> /// Starts reading plots for the specifed information and terminates and current plot mining. /// </summary> /// <param name="miningInfo"> /// The information to use to commence mining. /// </param> public void NotifyNewRound(MiningInfo miningInfo) { // Store this value _lastRoundStart = DateTime.UtcNow; _miningInfo = miningInfo; // First let's kill any existing plot reading foreach (PlotReader reader in _plotReaders) { reader.Terminate(); } // If no mining information stop now if (miningInfo == null) { return; } // Throw error if we have no readers if (_plotReaders == null) { Logger.Warn("Unable to process new block " + miningInfo.BlockHeight + ", no plot readers available"); return; } // Now we perform our basics to determine scoop number byte[] gensigHashable = new byte[40]; Array.Copy(miningInfo.PreviousGenerationSignatureBytes, gensigHashable, 32); Array.Copy(miningInfo.BlockHeightBytes, 0, gensigHashable, 32, 8); byte[] gensig = _shabel.ComputeBytes(gensigHashable).GetBytes(); uint scoop = (uint)((gensig[gensig.Length - 2] & 0x0F) << 8) | (gensig[gensig.Length - 1]); Logger.Debug("Calculated scoop for block as " + scoop); // Update our capacity values decimal utilisedStorage = 0; foreach (PlotReader reader in _plotReaders) { utilisedStorage += reader.UtilisedStorage; } if (UtilisedStorage != utilisedStorage) { UtilisedStorage = utilisedStorage; UtilisedStorageUpdated?.Invoke(this, new UtilisedStorageEventHandler(utilisedStorage)); } // With the config that we have we can now execute all of our readers foreach (PlotReader reader in _plotReaders) { reader.StartMining(miningInfo, scoop); } }
/// <summary> /// Commence mining for the current block. /// </summary> /// <param name="miningInfo"> /// Information used during the mining session including current network parameters. /// </param> /// <param name="scoop"> /// The scoop that this session will use. /// </param> internal void StartMining(MiningInfo miningInfo, uint scoop) { if (_miningThread != null || _isAlive) { throw new Exception("Thread is already mining"); } _miningInfo = miningInfo; _scoop = scoop; Logger.Info("Mining " + _directory + " for block " + miningInfo.BlockHeight + " with scoop " + scoop); _isAlive = true; _miningThread = new Thread(ThreadEntry) { Name = "Plot Reader: " + _directory, IsBackground = true }; _miningThread.Start(); }
/// <summary> /// The main thread entry point that continually checks for changes to mining state in the background. /// </summary> private void MiningInfoThread() { while (_isAlive) { try { string stringResponse; HttpResponseMessage response = _client.GetAsync("/burst?requestType=getMiningInfo").Result; response.EnsureSuccessStatusCode(); stringResponse = response.Content.ReadAsStringAsync().Result; if (stringResponse != _lastJson) { MiningInfo = JsonConvert.DeserializeObject <MiningInfo>(stringResponse); _lastJson = stringResponse; } } catch (AggregateException ex) { if (ex.InnerExceptions.Count == 1 && ex.InnerException is TaskCanceledException) { Logger.Warn("Timeout requesting current block status"); } else if (ex.InnerException.GetType().Name == "WinHttpException") { Logger.Warn("Error reading mining info:" + ex.InnerException.Message); } else if (ex.ToString().Contains("System.Net.Http.CurlException: Couldn't connect to server")) { Logger.Warn("Could not connect to server to get new blocks"); } else { Logger.Error("Unexpected error reading blocks: " + ex.InnerException.Message); } } catch (Exception ex) { Logger.Error("Failed to update mining info", ex); } Thread.Sleep(1000); } }
/// <summary> /// Free up our resources. /// </summary> public void Dispose() { // Log the dispose Logger.Debug("Stopping PlotChecker"); // Instruct threads to termintae and wait for them to all die _isAlive = false; if (_threads != null) { foreach (Thread thread in _threads) { thread.Join(); } _threads = null; } Logger.Debug("PlotChecker threads all terminated"); // Set values that should free up anything wanting to queue _maximumScoops = uint.MaxValue; _miningInfo = null; }
/// <summary> /// Create a new instance of this class. /// </summary> /// <param name="deadline"> /// The deadline that has been found. /// </param> /// <param name="scoop"> /// The scoop that this deadline was generated from. /// </param> /// <param name="miningInfo"> /// The mining information that was used to generate this deadline. /// </param> public DeadlineFoundEventArgs(ulong deadline, Scoop scoop, MiningInfo miningInfo) { Scoop = scoop; MiningInfo = miningInfo; Deadline = deadline; }
/// <summary> /// The main entry point for the threads that individually check for deadlines. /// </summary> private void ThreadEntry() { Shabal256 shabal = new Shabal256(); byte[] hashBuffer = new byte[32 + Plot.SCOOP_SIZE]; ulong lastBlockHeightPrevGenCopied = 0; while (_isAlive) { // Get a few items from the queue to process Scoop[] scoops; lock (_scoopQueueLocker) { scoops = new Scoop[_scoopQueue.Count > 100 ? 100 : _scoopQueue.Count]; if (scoops.Length > 0) { for (int i = 0; i < scoops.Length; i++) { scoops[i] = _scoopQueue[_scoopQueue.Count - i - 1]; } _scoopQueue.RemoveRange(_scoopQueue.Count - scoops.Length, scoops.Length); } } // If we didn't get an item or if for some reason the mining info has been wiped wait and try again if (scoops == null || scoops.Length < 1) { Thread.Sleep(200); continue; } foreach (Scoop scoop in scoops) { // Breakout if we're no longer running MiningInfo miningInfo = _miningInfo; if (!_isAlive || miningInfo == null) { break; } // Ensure we're on right block if (scoop.BlockHeight != miningInfo.BlockHeight) { continue; } // Calculate the deadline for this scoop if (lastBlockHeightPrevGenCopied != miningInfo.BlockHeight) { Array.Copy(_miningInfo.PreviousGenerationSignatureBytes, hashBuffer, 32); lastBlockHeightPrevGenCopied = miningInfo.BlockHeight; } Array.Copy(scoop.Data, 0, hashBuffer, 32, Plot.SCOOP_SIZE); byte[] target = shabal.ComputeBytes(hashBuffer).GetBytes(); ulong targetResult = BitConverter.ToUInt64(target, 0); // And with our target compute a deadline ulong deadline = targetResult / miningInfo.BaseTarget; if (deadline < miningInfo.Deadline) { DeadlineFound?.Invoke(this, new DeadlineFoundEventArgs(deadline, scoop, miningInfo)); } } } }
/// <summary> /// This method is the main progress loop which is used to check how we're currently doing and update the UI accordingly. /// </summary> private void ProgressMonitoringThread() { bool isFirstLoop = true; byte lastAnimationIndex = 0; MiningInfo miningInfo = null; bool lastVisible = false; decimal lastValue = 0; while (_isAlive) { // Determine what state we should display - first if block has changed we need to reset everything back to begining if (miningInfo != _miningInfo) { lastAnimationIndex = 0; miningInfo = _miningInfo; } bool visible = miningInfo != null; if (!visible) { // Update UI if we've got a change if (isFirstLoop || lastVisible != visible) { lastVisible = visible; isFirstLoop = false; lastValue = 0; ConsoleUi.ProgressBarHide(); } // Wait to try again Thread.Sleep(50); continue; } // Determine how far through our readers are of this block (if we're in a block) decimal value = 0m; string text = null; ulong totalScoops = (ulong)(UtilisedStorage * 1000000000 / Plot.SCOOPS_PER_PLOT / Plot.SCOOP_SIZE); ulong readScoops = 0; ulong totalBytesRead = 0; foreach (PlotReader reader in _plotReaders) { readScoops += reader.ScoopsRead; totalBytesRead += reader.BytesRead; } if (totalScoops > 0) { value = (decimal)readScoops / (decimal)totalScoops; } // Now determine speed of reading double seconds = DateTime.UtcNow.Subtract(_lastRoundStart).TotalSeconds; double bps = totalBytesRead / seconds; double mbps = Math.Round(bps / 1000 / 1000); string mbpsString = mbps.ToString(); if (!mbpsString.Contains(".")) { mbpsString += ".0"; } text = mbpsString + " MBps"; // Based on this determine how much longer we have to go or how long it took depending on the data if (value < 1) { double scoopsPerSecond = (double)readScoops / seconds; if (scoopsPerSecond > 0) { ulong remainingScoops = totalScoops - readScoops; double remainingSeconds = (double)remainingScoops / scoopsPerSecond; if (remainingSeconds < 86400) { TimeSpan remaining = TimeSpan.FromSeconds(remainingSeconds); string remainingText = remaining.Minutes.ToString(); if (remainingText.Length < 2) { remainingText = "0" + remainingText; } remainingText += ":"; if (remaining.Seconds < 10) { remainingText += "0"; } remainingText += remaining.Seconds.ToString(); text += " ETA: " + remainingText; } } } else if (seconds > 0) { text += " Dur: " + TimeSpan.FromSeconds(seconds).ToString(); } // Display this on first loop or a change in visibility if (lastVisible != visible || lastValue != value) { ConsoleUi.ProgressBarSetup(lastAnimationIndex++, value, text); if (lastAnimationIndex > 7) { lastAnimationIndex = 0; } } lastValue = value; lastVisible = true; isFirstLoop = false; // Wait to redraw Thread.Sleep(10); } }
/// <summary> /// Reset the deadline submitter and remvoe any queued deadlines that are for old blocks. /// </summary> /// <param name="miningInfo"> /// The current blocks mining information. /// </param> public void NotifyNewRound(MiningInfo miningInfo) { // Reset our deadlines list lock (_deadlineQueueLocker) _deadlineQueue.Clear(); }