Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make the miner work in parallel and add mining output #3

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Text;
using System;
using System.Text;
using Microsoft.Extensions.Logging;
using NBitcoin;
using UnnamedCoin.Bitcoin.Configuration;
Expand Down Expand Up @@ -33,6 +34,7 @@ public MinerSettings(NodeSettings nodeSettings)
this.Mine = config.GetOrDefault("mine", false, this.logger);
if (this.Mine)
this.MineAddress = config.GetOrDefault<string>("mineaddress", null, this.logger);
this.MineThreadCount = config.GetOrDefault<int>("minethreads", 1, this.logger);

this.Stake = config.GetOrDefault("stake", false, this.logger);
if (this.Stake)
Expand Down Expand Up @@ -88,6 +90,8 @@ public MinerSettings(NodeSettings nodeSettings)
/// </summary>
public bool Mine { get; }

public int MineThreadCount { get; }

/// <summary>
/// If true this will only allow staking coins that have been flaged.
/// </summary>
Expand Down Expand Up @@ -124,8 +128,11 @@ public static void PrintHelp(Network network)

builder.AppendLine("-mine=<0 or 1> Enable POW mining.");
builder.AppendLine("-stake=<0 or 1> Enable POS.");
builder.AppendLine(
"-mineaddress=<string> The address to use for mining (empty string to select an address from the wallet).");
builder.AppendLine("-mineaddress=<string> The address to use for mining (empty string to select an address from the wallet).");
builder.AppendLine("-minethreads=1 Total threads to mine on (default 1).");

builder.AppendLine("-mine=<0 or 1> Enable POW mining.");

builder.AppendLine("-walletname=<string> The wallet name to use when staking.");
builder.AppendLine("-walletpassword=<string> Password to unlock the wallet.");
builder.AppendLine(
Expand Down Expand Up @@ -159,6 +166,8 @@ public static void BuildDefaultConfigurationFile(StringBuilder builder, Network
builder.AppendLine("#stake=0");
builder.AppendLine("#The address to use for mining (empty string to select an address from the wallet).");
builder.AppendLine("#mineaddress=<string>");
builder.AppendLine("#Total threads to mine on (default 1)..");
builder.AppendLine("#minethreads=1");
builder.AppendLine("#The wallet name to use when staking.");
builder.AppendLine("#walletname=<string>");
builder.AppendLine("#Password to unlock the wallet.");
Expand Down
170 changes: 127 additions & 43 deletions src/components/Fullnode/UnnamedCoin.Bitcoin.Features.Miner/PowMining.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using NBitcoin;
using NBitcoin.BouncyCastle.math;
using NBitcoin.Crypto;
using UnnamedCoin.Bitcoin.AsyncWork;
using UnnamedCoin.Bitcoin.Consensus;
using UnnamedCoin.Bitcoin.Features.MemoryPool;
Expand Down Expand Up @@ -38,7 +42,7 @@ public class PowMining : IPowMining
/// </summary>
public const int DefaultBlockMinTxFee = 1;

const int InnerLoopCount = 0x10000;
//const int InnerLoopCount = 10_000_000;

/// <summary>Factory for creating background async loop tasks.</summary>
readonly IAsyncProvider asyncProvider;
Expand All @@ -56,6 +60,7 @@ public class PowMining : IPowMining
readonly IDateTimeProvider dateTimeProvider;

readonly IInitialBlockDownloadState initialBlockDownloadState;
private readonly MinerSettings minerSettings;

/// <summary>Instance logger.</summary>
readonly ILogger logger;
Expand Down Expand Up @@ -97,7 +102,8 @@ public PowMining(
Network network,
INodeLifetime nodeLifetime,
ILoggerFactory loggerFactory,
IInitialBlockDownloadState initialBlockDownloadState)
IInitialBlockDownloadState initialBlockDownloadState,
MinerSettings minerSettings)
{
this.asyncProvider = asyncProvider;
this.blockProvider = blockProvider;
Expand All @@ -106,6 +112,7 @@ public PowMining(
this.dateTimeProvider = dateTimeProvider;
this.loggerFactory = loggerFactory;
this.initialBlockDownloadState = initialBlockDownloadState;
this.minerSettings = minerSettings;
this.logger = loggerFactory.CreateLogger(GetType().FullName);
this.mempool = mempool;
this.mempoolLock = mempoolLock;
Expand All @@ -125,34 +132,34 @@ public void Mine(Script reserveScript)
CancellationTokenSource.CreateLinkedTokenSource(this.nodeLifetime.ApplicationStopping);

this.miningLoop = this.asyncProvider.CreateAndRunAsyncLoop("PowMining.Mine", token =>
{
try
{
try
{
GenerateBlocks(new ReserveScript {ReserveFullNodeScript = reserveScript}, int.MaxValue,
int.MaxValue);
}
catch (OperationCanceledException)
{
// Application stopping, nothing to do as the loop will be stopped.
}
catch (MinerException me)
{
// Block not accepted by peers or invalid. Should not halt mining.
this.logger.LogDebug("Miner exception occurred in miner loop: {0}", me.ToString());
}
catch (ConsensusErrorException cee)
{
// Issues constructing block or verifying it. Should not halt mining.
this.logger.LogDebug("Consensus error exception occurred in miner loop: {0}", cee.ToString());
}
catch
{
this.logger.LogTrace("(-)[UNHANDLED_EXCEPTION]");
throw;
}
GenerateBlocks(new ReserveScript { ReserveFullNodeScript = reserveScript }, int.MaxValue,
int.MaxValue);
}
catch (OperationCanceledException)
{
// Application stopping, nothing to do as the loop will be stopped.
}
catch (MinerException me)
{
// Block not accepted by peers or invalid. Should not halt mining.
this.logger.LogDebug("Miner exception occurred in miner loop: {0}", me.ToString());
}
catch (ConsensusErrorException cee)
{
// Issues constructing block or verifying it. Should not halt mining.
this.logger.LogDebug("Consensus error exception occurred in miner loop: {0}", cee.ToString());
}
catch
{
this.logger.LogTrace("(-)[UNHANDLED_EXCEPTION]");
throw;
}

return Task.CompletedTask;
},
return Task.CompletedTask;
},
this.miningCancellationTokenSource.Token,
TimeSpans.Second,
TimeSpans.TenSeconds);
Expand All @@ -171,7 +178,7 @@ public void StopMining()
/// <inheritdoc />
public List<uint256> GenerateBlocks(ReserveScript reserveScript, ulong amountOfBlocksToMine, ulong maxTries)
{
var context = new MineBlockContext(amountOfBlocksToMine, (ulong) this.chainIndexer.Height, maxTries,
var context = new MineBlockContext(amountOfBlocksToMine, (ulong)this.chainIndexer.Height, maxTries,
reserveScript);

while (context.MiningCanContinue)
Expand All @@ -183,7 +190,7 @@ public List<uint256> GenerateBlocks(ReserveScript reserveScript, ulong amountOfB
continue;

if (!MineBlock(context))
break;
continue;

if (!ValidateMinedBlock(context))
continue;
Expand Down Expand Up @@ -226,7 +233,11 @@ bool ConsensusIsAtTip(MineBlockContext context)
{
this.miningCancellationTokenSource.Token.ThrowIfCancellationRequested();

context.ChainTip = this.consensusManager.Tip;
if (context.ChainTip != this.consensusManager.Tip)
{
context.ChainTip = this.consensusManager.Tip;
context.BlockTemplate = null;
}

// Genesis on a regtest network is a special case. We need to regard ourselves as outside of IBD to
// bootstrap the mining.
Expand All @@ -251,8 +262,8 @@ bool ConsensusIsAtTip(MineBlockContext context)
/// </summary>
bool BuildBlock(MineBlockContext context)
{
context.BlockTemplate =
this.blockProvider.BuildPowBlock(context.ChainTip, context.ReserveScript.ReserveFullNodeScript);
if (context.BlockTemplate == null)
context.BlockTemplate = this.blockProvider.BuildPowBlock(context.ChainTip, context.ReserveScript.ReserveFullNodeScript);

if (this.network.Consensus.IsProofOfStake)
if (context.BlockTemplate.Block.Header.Time <= context.ChainTip.Header.Time)
Expand All @@ -269,33 +280,101 @@ bool MineBlock(MineBlockContext context)
context.ExtraNonce = IncrementExtraNonce(context.BlockTemplate.Block, context.ChainTip, context.ExtraNonce);

var block = context.BlockTemplate.Block;
while (context.MaxTries > 0 && block.Header.Nonce < InnerLoopCount && !block.CheckProofOfWork())
block.Header.Nonce = 0;

uint looplength = 2_000_000;
int threads = this.minerSettings.MineThreadCount; // Environment.ProcessorCount;
int batch = threads;
var totalNonce = batch * looplength;
uint winnernonce = 0;
bool found = false;

ParallelOptions options = new ParallelOptions() { MaxDegreeOfParallelism = threads, CancellationToken = this.miningCancellationTokenSource.Token };

Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();

int fromInclusive = context.ExtraNonce * batch;
int toExclusive = fromInclusive + batch;

Parallel.For(fromInclusive, toExclusive, options, (index, state) =>
{
this.miningCancellationTokenSource.Token.ThrowIfCancellationRequested();
if (this.miningCancellationTokenSource.Token.IsCancellationRequested)
return;

uint256 bits = block.Header.Bits.ToUInt256();

var headerbytes = block.Header.ToBytes(this.network.Consensus.ConsensusFactory);
uint nonce = (uint)index * looplength;

var end = nonce + looplength;

++block.Header.Nonce;
--context.MaxTries;
//this.logger.LogDebug($"nonce={nonce}, end={end}, index={index}, context.ExtraNonce={context.ExtraNonce}, looplength={looplength}");

while (nonce < end)
{
if (CheckProofOfWork(headerbytes, nonce, bits))
{
winnernonce = nonce;
found = true;
state.Stop();

return;
}

if (state.IsStopped)
return;

++nonce;
}
});

stopwatch.Stop();

if (found)
{
block.Header.Nonce = winnernonce;
if (block.Header.CheckProofOfWork())
return true;
}

if (context.MaxTries == 0)
return false;
var MHashedPerSec = Math.Round((totalNonce / stopwatch.Elapsed.TotalSeconds) / 1_000_000, 4);

return true;
var currentDifficulty = BigInteger.ValueOf((long)block.Header.Bits.Difficulty);
var MHashedPerSecTotal = (double)currentDifficulty.Multiply(Target.Pow256).Divide(Target.Difficulty1.ToBigInteger()).Divide(BigInteger.ValueOf(10 * 60)).LongValue / 1_000_000.0;

this.logger.LogInformation($"Difficulty={block.Header.Bits.Difficulty}, extraNonce={context.ExtraNonce}, hashes={totalNonce}, execution={stopwatch.Elapsed.TotalSeconds} sec, hash-rate={MHashedPerSec} MHash/sec ({threads} threads), network hash-rate ~{MHashedPerSecTotal} MHash/sec");

return false;
}

private static bool CheckProofOfWork(byte[] header, uint nonce, uint256 bits)
{
var bytes = BitConverter.GetBytes(nonce);
header[76] = bytes[0];
header[77] = bytes[1];
header[78] = bytes[2];
header[79] = bytes[3];

var headerHash = Sha512T.GetHash(header);

var res = headerHash <= bits;

return res;
}

/// <summary>
/// Ensures that the block was properly mined by checking the block's work against the next difficulty target.
/// </summary>
bool ValidateMinedBlock(MineBlockContext context)
{
if (context.BlockTemplate.Block.Header.Nonce == InnerLoopCount)
return false;

var chainedHeader = new ChainedHeader(context.BlockTemplate.Block.Header,
context.BlockTemplate.Block.GetHash(), context.ChainTip);
if (chainedHeader.ChainWork <= context.ChainTip.ChainWork)
return false;

var block = context.BlockTemplate.Block;

return true;
}

Expand Down Expand Up @@ -323,9 +402,12 @@ bool ValidateAndConnectBlock(MineBlockContext context)

void OnBlockMined(MineBlockContext context)
{
this.logger.LogInformation("==================================================================");

this.logger.LogInformation("Mined new {0} block: '{1}'.",
BlockStake.IsProofOfStake(context.ChainedHeaderBlock.Block) ? "POS" : "POW",
context.ChainedHeaderBlock.ChainedHeader);
this.logger.LogInformation("==================================================================");

context.CurrentHeight++;

Expand Down Expand Up @@ -358,6 +440,8 @@ public MineBlockContext(ulong amountOfBlocksToMine, ulong chainHeight, ulong max
public ulong CurrentHeight { get; set; }
public ChainedHeader ChainTip { get; set; }
public int ExtraNonce { get; set; }
public int ExtraNonceStart { get; set; }

public ulong MaxTries { get; set; }
public bool MiningCanContinue => this.CurrentHeight < this.ChainHeight + this.amountOfBlocksToMine;
}
Expand Down
8 changes: 7 additions & 1 deletion src/components/NBitcoin/Target.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,13 @@ public Target(uint256 target)
this._Target = new Target(ToCompact())._Target;
}

public static Target Difficulty1 { get; } = new Target(new byte[] {0x1d, 0x00, 0xff, 0xff});
public static BigInteger Pow256 = BigInteger.ValueOf(2).Pow(256);

// XDS: 0x1e0fffff, 0x1e = 30, 0xfffff * 2**(8*(0x1e - 3)) == 0x00000fffff000000000000000000000000000000000000000000000000000000
// Bitcoin: 0x1d00ffff, 0x1d = 29, 0xffff * 2**(8*(0x1d - 3)) == 0x00000000FFFF0000000000000000000000000000000000000000000000000000

public static Target Difficulty1 { get; } = new Target(new byte[] { 0x1e, 0x0f, 0xff, 0xff });
// public static Target Difficulty1 { get; } = new Target(new byte[] { 0x1d, 0x00, 0xff, 0xff });

public double Difficulty
{
Expand Down