Skip to content

Commit

Permalink
Add support for more KAS forks (BGA, HTN, CAS, NTL, NXL, SDR)
Browse files Browse the repository at this point in the history
Fix block classification issue in the Ethereum family when the node is not connected to the network
Fix Nicehash support for ALPH
  • Loading branch information
ceedii committed Apr 26, 2024
1 parent 49378e9 commit 64e8d16
Show file tree
Hide file tree
Showing 12 changed files with 422 additions and 55 deletions.
68 changes: 59 additions & 9 deletions src/Miningcore/Blockchain/Alephium/AlephiumPool.cs
Original file line number Diff line number Diff line change
Expand Up @@ -64,10 +64,21 @@ protected virtual async Task OnHelloAsync(StratumConnection connection, Timestam
blockchainStats.NodeVersion
};

await connection.RespondAsync(data, request.Id);

context.UserAgent = requestParams.FirstOrDefault()?.Trim();

// Nicehash's stupid validator insists on "error" property present
// in successful responses which is a violation of the JSON-RPC spec
// [Respect the goddamn standards Nicehack :(]
var response = new JsonRpcResponse<object[]>(data, request.Id);

if(context.IsNicehash)
{
response.Extra = new Dictionary<string, object>();
response.Extra["error"] = null;
}

await connection.RespondAsync(response);

logger.Info(() => $"[{connection.ConnectionId}] Hello {context.UserAgent}");
}

Expand All @@ -81,15 +92,26 @@ protected virtual async Task OnSubscribeAsync(StratumConnection connection, Time
var context = connection.ContextAs<AlephiumWorkerContext>();
var requestParams = request.ParamsAs<string[]>();

await connection.RespondAsync(connection.ConnectionId, request.Id);

// setup worker context
context.IsSubscribed = true;
// If the user agent was set by a mining.hello, we don't want to overwrite that (to match actual protocol)
if (string.IsNullOrEmpty(context.UserAgent))
{
context.UserAgent = requestParams.FirstOrDefault()?.Trim();
}

// Nicehash's stupid validator insists on "error" property present
// in successful responses which is a violation of the JSON-RPC spec
// [Respect the goddamn standards Nicehack :(]
var response = new JsonRpcResponse<object>(connection.ConnectionId, request.Id);

if(context.IsNicehash)
{
response.Extra = new Dictionary<string, object>();
response.Extra["error"] = null;
}

await connection.RespondAsync(response);
}

protected virtual async Task OnAuthorizeAsync(StratumConnection connection, Timestamped<JsonRpcRequest> tsRequest, CancellationToken ct)
Expand Down Expand Up @@ -122,12 +144,27 @@ protected virtual async Task OnAuthorizeAsync(StratumConnection connection, Time
{
// setup worker context
context.IsSubscribed = true;
context.UserAgent = requestParams.FirstOrDefault()?.Trim();
// If the user agent was set by a mining.hello, we don't want to overwrite that (to match actual protocol)
if (string.IsNullOrEmpty(context.UserAgent))
{
context.UserAgent = requestParams?.Length > 2 ? requestParams[2] : requestParams.FirstOrDefault()?.Trim();
}
}


// Nicehash's stupid validator insists on "error" property present
// in successful responses which is a violation of the JSON-RPC spec
// [Respect the goddamn standards Nicehack :(]
var response = new JsonRpcResponse<object>(context.IsAuthorized, request.Id);

if(context.IsNicehash)
{
response.Extra = new Dictionary<string, object>();
response.Extra["error"] = null;
}

// respond
await connection.RespondAsync(context.IsAuthorized, request.Id);
await connection.RespondAsync(response);

// send extranonce
await connection.NotifyAsync(AlephiumStratumMethods.SetExtraNonce, manager.GetSubscriberData(connection));

Expand Down Expand Up @@ -216,7 +253,20 @@ protected virtual async Task OnSubmitAsync(StratumConnection connection, Timesta

// submit
var share = await manager.SubmitShareAsync(connection, requestParams, ct);
await connection.RespondAsync(true, request.Id);

// Nicehash's stupid validator insists on "error" property present
// in successful responses which is a violation of the JSON-RPC spec
// [Respect the goddamn standards Nicehack :(]
var response = new JsonRpcResponse<object>(true, request.Id);

if(context.IsNicehash)
{
response.Extra = new Dictionary<string, object>();
response.Extra["error"] = null;
}

// respond
await connection.RespondAsync(response);

// publish
messageBus.SendMessage(share);
Expand Down
66 changes: 44 additions & 22 deletions src/Miningcore/Blockchain/Ethereum/EthereumPayoutHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,11 @@ public async Task<Block[]> ClassifyBlocksAsync(IMiningPool pool, Block[] blocks,
Contract.RequiresNonNull(poolConfig);
Contract.RequiresNonNull(blocks);

// ensure we have enough peers
var enoughPeers = await EnsureDaemonsSynchedAsync(ct);
if(!enoughPeers)
return blocks;

var coin = poolConfig.Template.As<EthereumCoinTemplate>();
var pageSize = 100;
var pageCount = (int) Math.Ceiling(blocks.Length / (double) pageSize);
Expand Down Expand Up @@ -250,14 +255,14 @@ public async Task<Block[]> ClassifyBlocksAsync(IMiningPool pool, Block[] blocks,

block.Hash = uncle.Hash;
block.Reward = GetUncleReward(chainType, uncle.Height.Value, blockInfo2.Height.Value);
block.BlockHeight = uncle.Height.Value;

// There is a rare case-scenario where an Uncle has a block reward of zero
// We must handle it carefully otherwise payout will be stuck forever
if(block.Reward > 0)
{
block.Status = BlockStatus.Confirmed;
block.ConfirmationProgress = 1;
block.BlockHeight = uncle.Height.Value;
}
else
{
Expand Down Expand Up @@ -307,16 +312,10 @@ public override async Task<decimal> UpdateBlockRewardBalancesAsync(IDbConnection

public async Task PayoutAsync(IMiningPool pool, Balance[] balances, CancellationToken ct)
{
// ensure we have peers
var infoResponse = await rpcClient.ExecuteAsync<string>(logger, EC.GetPeerCount, ct);

if((networkType == EthereumNetworkType.Main || extraPoolConfig?.ChainTypeOverride == "Classic" || extraPoolConfig?.ChainTypeOverride == "Mordor" || networkType == EthereumNetworkType.MainPow || extraPoolConfig?.ChainTypeOverride == "Ubiq" || extraPoolConfig?.ChainTypeOverride == "EtherOne" || extraPoolConfig?.ChainTypeOverride == "Pink" || extraPoolConfig?.ChainTypeOverride == "OctaSpace" || extraPoolConfig?.ChainTypeOverride == "OctaSpaceTestnet" || extraPoolConfig?.ChainTypeOverride == "Hypra") &&
(infoResponse.Error != null || string.IsNullOrEmpty(infoResponse.Response) ||
infoResponse.Response.IntegralFromHex<int>() < EthereumConstants.MinPayoutPeerCount))
{
logger.Warn(() => $"[{LogCategory}] Payout aborted. Not enough peers (4 required)");
// ensure we have enough peers
var enoughPeers = await EnsureDaemonsSynchedAsync(ct);
if(!enoughPeers)
return;
}

var txHashes = new List<string>();

Expand Down Expand Up @@ -347,6 +346,22 @@ public double AdjustBlockEffort(double effort)

#endregion // IPayoutHandler

private async Task<bool> EnsureDaemonsSynchedAsync(CancellationToken ct)
{
// ensure we have enough peers
var infoResponse = await rpcClient.ExecuteAsync<string>(logger, EC.GetPeerCount, ct);

if((networkType == EthereumNetworkType.Main || extraPoolConfig?.ChainTypeOverride == "Classic" || extraPoolConfig?.ChainTypeOverride == "Mordor" || networkType == EthereumNetworkType.MainPow || extraPoolConfig?.ChainTypeOverride == "Ubiq" || extraPoolConfig?.ChainTypeOverride == "EtherOne" || extraPoolConfig?.ChainTypeOverride == "Pink" || extraPoolConfig?.ChainTypeOverride == "OctaSpace" || extraPoolConfig?.ChainTypeOverride == "OctaSpaceTestnet" || extraPoolConfig?.ChainTypeOverride == "Hypra") &&
(infoResponse.Error != null || string.IsNullOrEmpty(infoResponse.Response) ||
infoResponse.Response.IntegralFromHex<int>() < EthereumConstants.MinPayoutPeerCount))
{
logger.Warn(() => $"[{LogCategory}] Payout aborted. Not enough peer(s) ({EthereumConstants.MinPayoutPeerCount} required)");
return false;
}

return true;
}

private async Task<DaemonResponses.Block[]> FetchBlocks(Dictionary<long, DaemonResponses.Block> blockCache, CancellationToken ct, params long[] blockHeights)
{
var cacheMisses = blockHeights.Where(x => !blockCache.ContainsKey(x)).ToArray();
Expand Down Expand Up @@ -491,30 +506,37 @@ internal static decimal GetUncleReward(GethChainType chainType, ulong uheight, u
case GethChainType.Mordor:
double heightEraLength = height / EthereumClassicConstants.EraLength;
double era = Math.Floor(heightEraLength);
if (era == 0) {
reward *= uheight + 8 - height;

if(era == 0)
{
if(uheight != height)
reward *= uheight + 8 - height;

reward /= 8m;
}
else {
else
{
reward /= 32m;
}

break;
case GethChainType.Ubiq:
reward = (uheight == height) ? reward / 2 : (uheight + 2 - height) * reward / 2;
// blocks older than the previous block are not rewarded
if (reward < 0)
reward = 0;
if(uheight != height)
reward *= uheight + 2 - height;

reward /= 2m;

break;
case GethChainType.Hypra:
reward = 0.1m;

break;
default:
reward *= uheight + 8 - height;
if(uheight != height)
reward *= uheight + 8 - height;

reward /= 8m;

break;
}

Expand Down
56 changes: 55 additions & 1 deletion src/Miningcore/Blockchain/Kaspa/KaspaConstants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,24 @@

namespace Miningcore.Blockchain.Kaspa;

public static class BugnaConstants
{
// List of BGA prefixes: https://github.com/bugnanetwork/bugnad/blob/master/util/address.go
public const string ChainPrefixDevnet = "bugnadev";
public const string ChainPrefixSimnet = "bugnasim";
public const string ChainPrefixTestnet = "bugnatest";
public const string ChainPrefixMainnet = "bugna";
}

public static class HoosatConstants
{
// List of HTN prefixes: https://github.com/Hoosat-Oy/HTND/blob/master/util/address.go
public const string ChainPrefixDevnet = "htndev";
public const string ChainPrefixSimnet = "hoosatsim";
public const string ChainPrefixTestnet = "hoosattest";
public const string ChainPrefixMainnet = "hoosat";
}

public static class KaspaConstants
{
public const string WalletDaemonCategory = "wallet";
Expand Down Expand Up @@ -57,6 +75,15 @@ public static class KaspaConstants
public const int Blake2bSize256 = 32;
}

public static class KaspaClassicConstants
{
// List of CAS prefixes: https://github.com/kaspaclassic/caspad/blob/main/util/address.go
public const string ChainPrefixDevnet = "caspadev";
public const string ChainPrefixSimnet = "pyrinsim";
public const string ChainPrefixTestnet = "pyrintest";
public const string ChainPrefixMainnet = "cas";
}

public static class KarlsencoinConstants
{
// List of KLS prefixes: https://github.com/karlsen-network/karlsend/blob/master/util/address.go
Expand All @@ -66,7 +93,25 @@ public static class KarlsencoinConstants
public const string ChainPrefixMainnet = "karlsen";

public const long FishHashForkHeightTestnet = 0;
public const long FishHashForkHeightMainnet = 999999999999;
public const long FishHashPlusForkHeightTestnet = 6000000;
}

public static class NautilusConstants
{
// List of NTL prefixes: https://github.com/Nautilus-Network/nautiliad/blob/master/util/address.go
public const string ChainPrefixDevnet = "nautiliadev";
public const string ChainPrefixSimnet = "nautilussim";
public const string ChainPrefixTestnet = "nautilustest";
public const string ChainPrefixMainnet = "nautilus";
}

public static class NexelliaConstants
{
// List of NXL prefixes: https://github.com/Nexellia-Network/nexelliad/blob/master/util/address.go
public const string ChainPrefixDevnet = "nexelliadev";
public const string ChainPrefixSimnet = "nexelliasim";
public const string ChainPrefixTestnet = "nexelliatest";
public const string ChainPrefixMainnet = "nexellia";
}

public static class PyrinConstants
Expand All @@ -80,6 +125,15 @@ public static class PyrinConstants
public const long Blake3ForkHeight = 1484741;
}

public static class SedraCoinConstants
{
// List of HTN prefixes: https://github.com/sedracoin/sedrad/blob/main/util/address.go
public const string ChainPrefixDevnet = "sedradev";
public const string ChainPrefixSimnet = "sedrasim";
public const string ChainPrefixTestnet = "sedratest";
public const string ChainPrefixMainnet = "sedra";
}

public enum KaspaBech32Prefix
{
Unknown = 0,
Expand Down
44 changes: 43 additions & 1 deletion src/Miningcore/Blockchain/Kaspa/KaspaJobManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,22 @@ private KaspaJob CreateJob(long blockHeight)
{
switch(coin.Symbol)
{
case "CAS":
case "HTN":
if(customBlockHeaderHasher is not Blake3)
{
string coinbaseBlockHash = KaspaConstants.CoinbaseBlockHash;
byte[] hashBytes = Encoding.UTF8.GetBytes(coinbaseBlockHash.PadRight(32, '\0')).Take(32).ToArray();
customBlockHeaderHasher = new Blake3(hashBytes);
}

if(customCoinbaseHasher is not Blake3)
customCoinbaseHasher = new Blake3();

if(customShareHasher is not Blake3)
customShareHasher = new Blake3();

return new PyrinJob(customBlockHeaderHasher, customCoinbaseHasher, customShareHasher);
case "KLS":
var karlsenNetwork = network.ToLower();

Expand All @@ -224,7 +240,21 @@ private KaspaJob CreateJob(long blockHeight)
if(customCoinbaseHasher is not Blake3)
customCoinbaseHasher = new Blake3();

if ((karlsenNetwork == "testnet" && blockHeight >= KarlsencoinConstants.FishHashForkHeightTestnet))
if(karlsenNetwork == "testnet" && blockHeight >= KarlsencoinConstants.FishHashPlusForkHeightTestnet)
{
logger.Debug(() => $"fishHashPlusHardFork activated");

if(customShareHasher is not FishHashKarlsen)
{
var started = DateTime.Now;
logger.Debug(() => $"Generating light cache");

customShareHasher = new FishHashKarlsen(true);

logger.Debug(() => $"Done generating light cache after {DateTime.Now - started}");
}
}
else if(karlsenNetwork == "testnet" && blockHeight >= KarlsencoinConstants.FishHashForkHeightTestnet)
{
logger.Debug(() => $"fishHashHardFork activated");

Expand All @@ -242,6 +272,18 @@ private KaspaJob CreateJob(long blockHeight)
if(customShareHasher is not CShake256)
customShareHasher = new CShake256(null, Encoding.UTF8.GetBytes(KaspaConstants.CoinbaseHeavyHash));

return new KarlsencoinJob(customBlockHeaderHasher, customCoinbaseHasher, customShareHasher);
case "NTL":
case "NXL":
if(customBlockHeaderHasher is not Blake2b)
customBlockHeaderHasher = new Blake2b(Encoding.UTF8.GetBytes(KaspaConstants.CoinbaseBlockHash));

if(customCoinbaseHasher is not Blake3)
customCoinbaseHasher = new Blake3();

if(customShareHasher is not CShake256)
customShareHasher = new CShake256(null, Encoding.UTF8.GetBytes(KaspaConstants.CoinbaseHeavyHash));

return new KarlsencoinJob(customBlockHeaderHasher, customCoinbaseHasher, customShareHasher);
case "PYI":
if(blockHeight >= PyrinConstants.Blake3ForkHeight)
Expand Down
Loading

0 comments on commit 64e8d16

Please sign in to comment.