/// <summary> /// Log a message of the specified level.. /// </summary> /// <param name="message"> /// The message to log. /// </param> /// <param name="ex"> /// An optional exception related to the message. /// </param> /// <param name="logLevel"> /// The level of the message to log. /// </param> private static void Log(string message, Exception ex, LogLevel logLevel) { ConsoleColor foreground; switch (logLevel) { case LogLevel.Warn: foreground = ConsoleColor.Yellow; break; case LogLevel.Error: foreground = ConsoleColor.Red; break; case LogLevel.Debug: foreground = ConsoleColor.Green; break; default: foreground = ConsoleColor.White; break; } string formattedMessage = string.Format("[{0:HH}:{0:mm}:{0:ss}] {1}", DateTime.Now, message); ConsoleUi.WriteLine(formattedMessage, foreground); if (ex != null) { ConsoleUi.WriteLine(ex.ToString(), foreground); } }
/// <summary> /// Notify the submitter that a new deadline has been found and add it to the queue. /// </summary> /// <param name="deadline"> /// The deadline to add. /// </param> public void NewDeadline(Deadline deadline) { // Let's log this and notify the UI Logger.Info("New deadline found " + deadline.DeadlineDuration + " for block " + deadline.MiningInfo.BlockHeight); ConsoleUi.DisplayDeadline(deadline); // Add this to our queue lock (_deadlineQueueLocker) _deadlineQueue.Add(deadline); }
/// <summary> /// Free up our used resources. /// </summary> public void Dispose() { _isAlive = false; if (_thread != null) { _thread.Join(); _thread = null; } if (_applicationInstance == this) { _applicationInstance = null; } }
/// <summary> /// The main entry point to the application. /// </summary> /// <param name="args"> /// Any command line arguments. /// </param> public static void Main(string[] args) { try { using (Miner miner = new Miner()) { miner.Run(); } } catch (Exception ex) { ConsoleUi.WriteLine(ex.ToString()); } }
/// <summary> /// Handle new mining info being received. /// </summary> /// <param name="sender"> /// The event sender. /// </param> /// <param name="e"> /// The event arguments. /// </param> private void OnMiningInfoUpdate(object sender, System.ComponentModel.PropertyChangedEventArgs e) { // Update the UI if (_miningInfoUpdater.MiningInfo != null) { Logger.Info("Starting new block: " + _miningInfoUpdater.MiningInfo.BlockHeight); } else { Logger.Error("Empty mining info received"); } ConsoleUi.SetTextAreaHeader(_miningInfoUpdater?.MiningInfo != null ? "Block " + _miningInfoUpdater.MiningInfo.BlockHeight : null); // Start all objects on the new block _plotReaderManager.NotifyNewRound(_miningInfoUpdater.MiningInfo); _deadlineSubmitter.NotifyNewRound(_miningInfoUpdater.MiningInfo); _deadlineCalculator.NotifyNewRound(_miningInfoUpdater.MiningInfo); }
/// <summary> /// Start the miner. /// </summary> public void Run() { // Ensure we can only run once if (_started) { throw new Exception("Already started"); } _started = true; // Start the UI _consoleUi = new ConsoleUi(); Logger.Info("Miner starting up"); // Create our plot reader manager to manage reading from disk Logger.Debug("Plot reader manager: starting"); _plotReaderManager = new PlotReaderManager(); _plotReaderManager.ScoopsDiscovered += PlotReaderManagerOnScoopsDiscovered; _plotReaderManager.UtilisedStorageUpdated += PlotReaderManagerOnUtilisedStorageUpdated; Logger.Debug("Plot reader manager: started"); // Create our deadline calculator and hook up to its events Logger.Debug("Deadline calculator: starting"); _deadlineCalculator = new DeadlineCalculator(Configuration.MemoryLimitPlotChecker / Plot.SCOOP_SIZE, Configuration.ThreadCountPlotChecker); _deadlineCalculator.DeadlineFound += DeadlineCalculatorOnDeadlineFound; Logger.Debug("Deadline calculator: started"); // Setup our deadline submitter Logger.Debug("Deadline submitter: starting"); _deadlineSubmitter = new DeadlineSubmitter(); _deadlineSubmitter.UpdateUtilisedStorage(_plotReaderManager.UtilisedStorage); Logger.Debug("Deadline submitter: started"); // Create our mining info updater to listen for new rounds Logger.Debug("Mining info updater: starting"); _miningInfoUpdater = new MiningInfoUpdater(); _miningInfoUpdater.PropertyChanged += OnMiningInfoUpdate; Logger.Debug("Mining info updater: started"); // Finally hand over to the console UI which blocks us Logger.Info("Miner has started up successfully"); _consoleUi.Run(); }
/// <summary> /// Create a new instance of this class. /// </summary> public ConsoleUi() { // Store ourselves as app instance if not pre-existing if (_applicationInstance == null) { _applicationInstance = this; } // Setup action _exitAction = new Action(() => { Logger.Info("Exiting burst-sharp miner"); _isAlive = false; }); _functionKeyActions[9] = _exitAction; // Create our size monitoring thread _isAlive = true; _thread = new Thread(ConsoleDrawingThread) { IsBackground = true, Name = "Console Drawing", Priority = ThreadPriority.Highest }; _thread.Start(); }
/// <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> /// This method provides the main logic that manages state transitions and deals with our queue. /// </summary> private void ThreadEntry() { while (_isAlive) { // First let's get a handle to our deadlines Deadline[] deadlines; lock (_deadlineQueueLocker) { // Remove any old deadlines List <Deadline> toRemove = new List <Deadline>(); toRemove.AddRange(_deadlineQueue.Where(dl => dl.Status == DeadlineStatus.Accepted || dl.Status == DeadlineStatus.Rejected)); foreach (Deadline dl in toRemove) { _deadlineQueue.Remove(dl); } // Copy the list locally deadlines = _deadlineQueue.Where(dl => dl.NextSubmissionDate <= DateTime.UtcNow).ToArray(); } foreach (Deadline deadline in deadlines) { HttpResponseMessage response = null; string stringResponse = null; try { _client.DefaultRequestHeaders.Clear(); _client.DefaultRequestHeaders.Add("X-Miner", _version); _client.DefaultRequestHeaders.Add("X-Capacity", _gbStorage.ToString()); deadline.Submit(); ConsoleUi.DisplayDeadline(deadline); response = _client.PostAsync("/burst?requestType=submitNonce&nonce=" + deadline.Scoop.Nonce + "&accountId=" + deadline.Scoop.AccountId, null).Result; response.EnsureSuccessStatusCode(); stringResponse = response.Content.ReadAsStringAsync().Result; JObject obj = JObject.Parse(stringResponse); JToken tok; if (obj.TryGetValue("deadline", out tok)) { // We have deadline so it was accepted deadline.Accept(); ConsoleUi.DisplayDeadline(deadline); } else if (obj.TryGetValue("errorCode", out tok)) { // An error and it was rejected deadline.Reject(); ConsoleUi.DisplayDeadline(deadline); } else { // Some other state so we want to retry deadline.SubmissionFailed(); ConsoleUi.DisplayDeadline(deadline); } } catch (Exception ex) { deadline.SubmissionFailed(); ConsoleUi.DisplayDeadline(deadline); Logger.Error("Failed to submit deadline", ex); if (response != null) { if (!string.IsNullOrEmpty(stringResponse)) { try { stringResponse = response.Content.ReadAsStringAsync().Result; } catch { } } if (!string.IsNullOrEmpty(stringResponse)) { Logger.Debug(stringResponse); } } } } // Wait to retry Thread.Sleep(100); } }