Skip to content

Commit

Permalink
Add support for Conceal
Browse files Browse the repository at this point in the history
Fix typo with `Cryptonight` being mispelled `Crytonight`
  • Loading branch information
ceedii authored and Censseo committed Jan 29, 2023
1 parent 1fe87fe commit 1e330fb
Show file tree
Hide file tree
Showing 39 changed files with 2,576 additions and 39 deletions.
8 changes: 7 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -205,4 +205,10 @@ fabric.properties
# End of https://www.toptal.com/developers/gitignore/api/rider
.idea

.fake
.fake
/nbproject/
/*/*/*/*.a
/*/*/*/*/*/*/*/*.o
/*/*/*/*/*/*/*/*/*.o
/*/*/*/*/*/*/*/*/*/*.o
/*/*/*/*/*/*/*/*/*/*.o
6 changes: 6 additions & 0 deletions src/Miningcore/AutofacModule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using Miningcore.Api;
using Miningcore.Banning;
using Miningcore.Blockchain.Bitcoin;
using Miningcore.Blockchain.Conceal;
using Miningcore.Blockchain.Cryptonote;
using Miningcore.Blockchain.Equihash;
using Miningcore.Blockchain.Ethereum;
Expand Down Expand Up @@ -147,7 +148,12 @@ protected override void Load(ContainerBuilder builder)
// Bitcoin and family

builder.RegisterType<BitcoinJobManager>();

//////////////////////
// Conceal

builder.RegisterType<ConcealJobManager>();

//////////////////////
// Cryptonote

Expand Down
60 changes: 60 additions & 0 deletions src/Miningcore/Blockchain/Conceal/ConcealConstants.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
using System.Globalization;
using System.Text.RegularExpressions;
using Org.BouncyCastle.Math;

namespace Miningcore.Blockchain.Conceal;

public enum ConcealNetworkType
{
Main = 1,
Test
}

public static class ConcealConstants
{
public const string WalletDaemonCategory = "wallet";

public const string DaemonRpcLocation = "json_rpc";
public const string DaemonRpcGetInfoLocation = "getinfo";
public const int ConcealRpcMethodNotFound = -32601;
public const int PaymentIdHexLength = 64;
public const decimal SmallestUnit = 1000000;
public static readonly Regex RegexValidNonce = new("^[0-9a-f]{8}$", RegexOptions.Compiled);

public static readonly BigInteger Diff1 = new("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 16);
public static readonly System.Numerics.BigInteger Diff1b = System.Numerics.BigInteger.Parse("00FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", NumberStyles.HexNumber);

public const int PayoutMinBlockConfirmations = 10;

public const int InstanceIdSize = 4;
public const int ExtraNonceSize = 4;

// NOTE: for whatever strange reason only reserved_size -1 can be used,
// the LAST byte MUST be zero or nothing works
public const int ReserveSize = ExtraNonceSize + InstanceIdSize + 1;

// Offset to nonce in block blob
public const int BlobNonceOffset = 39;

public const decimal StaticTransactionFeeReserve = 0.001m; // in conceal
}

public static class ConcealCommands
{
public const string GetInfo = "getinfo";
public const string GetLastBlockHeader = "getlastblockheader";
public const string GetBlockTemplate = "getblocktemplate";
public const string SubmitBlock = "submitblock";
public const string GetBlockHeaderByHash = "getblockheaderbyhash";
public const string GetBlockHeaderByHeight = "getblockheaderbyheight";
}

public static class ConcealWalletCommands
{
public const string GetBalance = "getBalance";
public const string GetAddress = "getAddresses";
public const string SendTransaction = "sendTransaction";
public const string GetTransactions = "getTransactions";
public const string SplitIntegratedAddress = "splitIntegrated";
public const string Save = "save";
}
192 changes: 192 additions & 0 deletions src/Miningcore/Blockchain/Conceal/ConcealJob.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
using Miningcore.Blockchain.Conceal.DaemonResponses;
using Miningcore.Configuration;
using Miningcore.Extensions;
using Miningcore.Native;
using Miningcore.Stratum;
using Miningcore.Util;
using Org.BouncyCastle.Math;
using static Miningcore.Native.Cryptonight.Algorithm;
using Contract = Miningcore.Contracts.Contract;

namespace Miningcore.Blockchain.Conceal;

public class ConcealJob
{
public ConcealJob(GetBlockTemplateResponse blockTemplate, byte[] instanceId, string jobId,
ConcealCoinTemplate coin, PoolConfig poolConfig, ClusterConfig clusterConfig, string prevHash)
{
Contract.RequiresNonNull(blockTemplate);
Contract.RequiresNonNull(poolConfig);
Contract.RequiresNonNull(clusterConfig);
Contract.RequiresNonNull(instanceId);
Contract.Requires<ArgumentException>(!string.IsNullOrEmpty(jobId));

BlockTemplate = blockTemplate;
PrepareBlobTemplate(instanceId);
PrevHash = prevHash;

hashFunc = hashFuncs[coin.Hash];
}

protected delegate void HashFunc(ReadOnlySpan<byte> data, Span<byte> result, ulong height);

protected static readonly Dictionary<CryptonightHashType, HashFunc> hashFuncs = new()
{
{ CryptonightHashType.CryptonightCCX, (data, result, height) => Cryptonight.CryptonightHash(data, result, CN_CCX, height) },
{ CryptonightHashType.CryptonightGPU, (data, result, height) => Cryptonight.CryptonightHash(data, result, CN_GPU, height) },
};

private byte[] blobTemplate;
private int extraNonce;
private readonly HashFunc hashFunc;

private void PrepareBlobTemplate(byte[] instanceId)
{
blobTemplate = BlockTemplate.Blob.HexToByteArray();

// inject instanceId
instanceId.CopyTo(blobTemplate, BlockTemplate.ReservedOffset + ConcealConstants.ExtraNonceSize);
}

private string EncodeBlob(uint workerExtraNonce)
{
Span<byte> blob = stackalloc byte[blobTemplate.Length];
blobTemplate.CopyTo(blob);

// inject extranonce (big-endian) at the beginning of the reserved area
var bytes = BitConverter.GetBytes(workerExtraNonce.ToBigEndian());
bytes.CopyTo(blob[BlockTemplate.ReservedOffset..]);

return CryptonoteBindings.ConvertBlob(blob, blobTemplate.Length).ToHexString();
}

private string EncodeTarget(double difficulty, int size = 4)
{
var diff = BigInteger.ValueOf((long) (difficulty * 255d));
var quotient = ConcealConstants.Diff1.Divide(diff).Multiply(BigInteger.ValueOf(255));
var bytes = quotient.ToByteArray().AsSpan();
Span<byte> padded = stackalloc byte[32];

var padLength = padded.Length - bytes.Length;

if(padLength > 0)
bytes.CopyTo(padded.Slice(padLength, bytes.Length));

padded = padded[..size];
padded.Reverse();

return padded.ToHexString();
}

private void ComputeBlockHash(ReadOnlySpan<byte> blobConverted, Span<byte> result)
{
// blockhash is computed from the converted blob data prefixed with its length
Span<byte> block = stackalloc byte[blobConverted.Length + 1];
block[0] = (byte) blobConverted.Length;
blobConverted.CopyTo(block[1..]);

CryptonoteBindings.CryptonightHashFast(block, result);
}

#region API-Surface

public string PrevHash { get; }
public GetBlockTemplateResponse BlockTemplate { get; }

public void PrepareWorkerJob(ConcealWorkerJob workerJob, out string blob, out string target)
{
workerJob.Height = BlockTemplate.Height;
workerJob.ExtraNonce = (uint) Interlocked.Increment(ref extraNonce);

if(extraNonce < 0)
extraNonce = 0;

blob = EncodeBlob(workerJob.ExtraNonce);
target = EncodeTarget(workerJob.Difficulty);
}

public (Share Share, string BlobHex) ProcessShare(string nonce, uint workerExtraNonce, string workerHash, StratumConnection worker)
{
Contract.Requires<ArgumentException>(!string.IsNullOrEmpty(nonce));
Contract.Requires<ArgumentException>(!string.IsNullOrEmpty(workerHash));
Contract.Requires<ArgumentException>(workerExtraNonce != 0);

var context = worker.ContextAs<ConcealWorkerContext>();

// validate nonce
if(!ConcealConstants.RegexValidNonce.IsMatch(nonce))
throw new StratumException(StratumError.MinusOne, "malformed nonce");

// clone template
Span<byte> blob = stackalloc byte[blobTemplate.Length];
blobTemplate.CopyTo(blob);

// inject extranonce
var bytes = BitConverter.GetBytes(workerExtraNonce.ToBigEndian());
bytes.CopyTo(blob[BlockTemplate.ReservedOffset..]);

// inject nonce
bytes = nonce.HexToByteArray();
bytes.CopyTo(blob[ConcealConstants.BlobNonceOffset..]);

// convert
var blobConverted = CryptonoteBindings.ConvertBlob(blob, blobTemplate.Length);
if(blobConverted == null)
throw new StratumException(StratumError.MinusOne, "malformed blob");

// hash it
Span<byte> headerHash = stackalloc byte[32];
hashFunc(blobConverted, headerHash, BlockTemplate.Height);

var headerHashString = headerHash.ToHexString();
if(headerHashString != workerHash)
throw new StratumException(StratumError.MinusOne, "bad hash");

// check difficulty
var headerValue = headerHash.ToBigInteger();
var shareDiff = (double) new BigRational(ConcealConstants.Diff1b, headerValue);
var stratumDifficulty = context.Difficulty;
var ratio = shareDiff / stratumDifficulty;
var isBlockCandidate = shareDiff >= BlockTemplate.Difficulty;

// test if share meets at least workers current difficulty
if(!isBlockCandidate && ratio < 0.99)
{
// check if share matched the previous difficulty from before a vardiff retarget
if(context.VarDiff?.LastUpdate != null && context.PreviousDifficulty.HasValue)
{
ratio = shareDiff / context.PreviousDifficulty.Value;

if(ratio < 0.99)
throw new StratumException(StratumError.LowDifficultyShare, $"low difficulty share ({shareDiff})");

// use previous difficulty
stratumDifficulty = context.PreviousDifficulty.Value;
}

else
throw new StratumException(StratumError.LowDifficultyShare, $"low difficulty share ({shareDiff})");
}

var result = new Share
{
BlockHeight = BlockTemplate.Height,
Difficulty = stratumDifficulty,
};

if(isBlockCandidate)
{
// Compute block hash
Span<byte> blockHash = stackalloc byte[32];
ComputeBlockHash(blobConverted, blockHash);

// Fill in block-relevant fields
result.IsBlockCandidate = true;
result.BlockHash = blockHash.ToHexString();
}

return (result, blob.ToHexString());
}

#endregion // API-Surface
}
Loading

0 comments on commit 1e330fb

Please sign in to comment.