Skip to content

Commit

Permalink
Add support for ALPH (Rhône upgrade)
Browse files Browse the repository at this point in the history
Add new API Admin features:
- Change Log level while pool is running
- Activate/deactivate PayoutHandler while pool is running
Fix typo in Dockerfile
  • Loading branch information
ceedii committed Jun 6, 2024
1 parent 64e8d16 commit df5c3c5
Show file tree
Hide file tree
Showing 5 changed files with 283 additions and 81 deletions.
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
FROM mcr.microsoft.com/dotnet/sdk:6.0-jammy as BUILDER
WORKDIR /app
RUN apt-get update && \
apt-get -y install cmake ninja-build build-essential libssl-dev pkg-config libboost-all-dev libsodium-dev libzmq5n libzmq3-dev golang-go libgmp-dev
apt-get -y install cmake ninja-build build-essential libssl-dev pkg-config libboost-all-dev libsodium-dev libzmq5 libzmq3-dev golang-go libgmp-dev
COPY . .
WORKDIR /app/src/Miningcore
RUN dotnet publish -c Release --framework net6.0 -o ../../build
Expand Down
3 changes: 3 additions & 0 deletions src/Miningcore/Blockchain/Alephium/AlephiumConstants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ public static class AlephiumConstants
public const uint ShareMultiplier = 1;
// ALPH smallest unit is called PHI: https://wiki.alephium.org/glossary#gas-price
public const decimal SmallestUnit = 1000000000000000000;

public const string BlockTypeUncle = "uncle";
public const string BlockTypeBlock = "block";

// Socket miner API
public const int MessageHeaderSize = 4; // 4 bytes body length
Expand Down
46 changes: 25 additions & 21 deletions src/Miningcore/Blockchain/Alephium/AlephiumJobManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -331,33 +331,30 @@ private async Task<bool> UpdateJob(CancellationToken ct, string via = null, Alep
validJobs.RemoveAt(validJobs.Count - 1);
}
if(isNew)
{
if(via != null)
logger.Info(() => $"Detected new block {job.BlockTemplate.Height} on chain[{job.BlockTemplate.ChainIndex}] [{via}]");
else
logger.Info(() => $"Detected new block {job.BlockTemplate.Height} on chain[{job.BlockTemplate.ChainIndex}]");
// update stats
if ((job.BlockTemplate.Height - 1) > BlockchainStats.BlockHeight)
{
// update stats
BlockchainStats.LastNetworkBlockTime = clock.Now;
BlockchainStats.BlockHeight = job.BlockTemplate.Height - 1;
}
}
if(via != null)
logger.Info(() => $"Detected new block {job.BlockTemplate.Height} on chain[{job.BlockTemplate.ChainIndex}] [{via}]");
else
logger.Info(() => $"Detected new block {job.BlockTemplate.Height} on chain[{job.BlockTemplate.ChainIndex}]");
// update stats
if ((job.BlockTemplate.Height - 1) > BlockchainStats.BlockHeight)
{
if(via != null)
logger.Debug(() => $"Template update {job.BlockTemplate.Height} on chain[{job.BlockTemplate.ChainIndex}] [{via}]");
else
logger.Debug(() => $"Template update {job.BlockTemplate.Height} on chain[{job.BlockTemplate.ChainIndex}]");
// update stats
BlockchainStats.LastNetworkBlockTime = clock.Now;
BlockchainStats.BlockHeight = job.BlockTemplate.Height - 1;
}
currentJob = job;
}
else
{
if(via != null)
logger.Debug(() => $"Template update {job.BlockTemplate.Height} on chain[{job.BlockTemplate.ChainIndex}] [{via}]");
else
logger.Debug(() => $"Template update {job.BlockTemplate.Height} on chain[{job.BlockTemplate.ChainIndex}]");
}
return isNew;
}
Expand Down Expand Up @@ -632,6 +629,7 @@ protected override async Task PostStartInitAsync(CancellationToken ct)
network = "mainnet";
break;
case 1:
case 7:
network = "testnet";
break;
case 4:
Expand Down Expand Up @@ -694,6 +692,9 @@ protected override async Task<bool> AreDaemonsHealthyAsync(CancellationToken ct)

protected override async Task<bool> AreDaemonsConnectedAsync(CancellationToken ct)
{
var infosChainParams = await Guard(() => rpc.GetInfosChainParamsAsync(ct),
ex=> logger.Debug(ex));

var info = await Guard(() => rpc.GetInfosInterCliquePeerInfoAsync(ct),
ex=> logger.Debug(ex));

Expand All @@ -704,7 +705,10 @@ protected override async Task<bool> AreDaemonsConnectedAsync(CancellationToken c
if(!string.IsNullOrEmpty(nodeInfo?.BuildInfo.ReleaseVersion))
BlockchainStats.NodeVersion = nodeInfo?.BuildInfo.ReleaseVersion;

return info?.Count > 0;
if(infosChainParams?.NetworkId != 7)
return info?.Count > 0;

return true;
}

protected override async Task EnsureDaemonsSynchedAsync(CancellationToken ct)
Expand Down
181 changes: 122 additions & 59 deletions src/Miningcore/Blockchain/Alephium/AlephiumPayoutHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ public virtual async Task ConfigureAsync(ClusterConfig cc, PoolConfig pc, Cancel
network = "mainnet";
break;
case 1:
case 7:
network = "testnet";
break;
case 4:
Expand Down Expand Up @@ -105,107 +106,156 @@ public virtual async Task<Block[]> ClassifyBlocksAsync(IMiningPool pool, Block[]
for(var j = 0; j < page.Length; j++)
{
var block = page[j];


List<Settlement> blockRewardTransactions;
BlockEntry blockInfo;

var isBlockInMainChain = await Guard(() => alephiumClient.GetBlockflowIsBlockInMainChainAsync((string) block.Hash, ct),
ex=> logger.Debug(ex));

// We lost that battle
// Starting with Rhone-Upgrade - https://docs.alephium.org/integration/mining/#rhone-upgrade - "Ghost" uncles are now a thing on ALPH
// When a Block is not found in main chain, we must check now if it could be a "ghost" uncle
if(!isBlockInMainChain)
{
result.Add(block);

block.Status = BlockStatus.Orphaned;
block.Reward = 0;

logger.Info(() => $"[{LogCategory}] Block {block.BlockHeight} classified as orphaned because it's not the chain");
block.Type = AlephiumConstants.BlockTypeUncle;

// get uncle block info
blockInfo = await Guard(() => alephiumClient.UncleHashAsync((string) block.Hash, ct),
ex=> logger.Debug(ex));

// Dang, not even a "ghost" uncle, we definitely lost that battle :'(
if(blockInfo == null)
{
result.Add(block);

block.Status = BlockStatus.Orphaned;
block.Reward = 0;

logger.Info(() => $"[{LogCategory}] Block {block.BlockHeight} [{block.Hash}] classified as orphaned because it's not the chain and not even a 'ghost' uncle");

messageBus.NotifyBlockUnlocked(poolConfig.Id, block, coin);

continue;
}
else
{
logger.Debug(() => $"[{LogCategory}] Block {block.BlockHeight} [{block.Hash}] is a possible ghost uncle. It contains {blockInfo.Transactions.Count} transaction(s)");

messageBus.NotifyBlockUnlocked(poolConfig.Id, block, coin);
continue;
// we only need the transaction(s) related to the block reward
blockRewardTransactions = blockInfo.Transactions
.Where(x => x.Unsigned.Inputs.Count < 1)
.ToList();
}
}
else
{
block.Type = AlephiumConstants.BlockTypeBlock;

// get block info
var blockInfo = await Guard(() => alephiumClient.HashAsync((string) block.Hash, ct),
blockInfo = await Guard(() => alephiumClient.HashAsync((string) block.Hash, ct),
ex=> logger.Debug(ex));

logger.Debug(() => $"[{LogCategory}] Block {block.BlockHeight} contains {blockInfo.Transactions.Count} transaction(s)");
logger.Debug(() => $"[{LogCategory}] Block {block.BlockHeight} [{block.Hash}] contains {blockInfo.Transactions.Count} transaction(s)");

// we only need the transaction(s) related to the block reward
blockRewardTransactions = blockInfo.Transactions
.Where(x => x.Unsigned.Inputs.Count < 1)
.ToList();
}

logger.Debug(() => $"[{LogCategory}] Block {block.BlockHeight} [{block.Hash}] contains {blockRewardTransactions.Count} transaction(s) related to the block reward");

// Money time
if(blockRewardTransactions.Count > 0)
{
// get wallet miner's addresses
var walletMinersAddresses = await Guard(() => alephiumClient.GetMinersAddressesAsync(ct),
ex=> logger.Debug(ex));

// we only need the transaction(s) related to the block reward
var blockRewardTransactions = blockInfo.Transactions
.Where(x => x.Unsigned.Inputs.Count < 1)
.ToList();
// We only need the transaction(s) for our wallet miner's addresses
blockRewardTransactions = blockRewardTransactions
.Where(x =>
{
var fixedOutputs = x.Unsigned.FixedOutputs
.Where(y => walletMinersAddresses.Addresses.Contains(y.Address))
.ToList();
logger.Debug(() => $"[{LogCategory}] Block {block.BlockHeight} contains {blockRewardTransactions.Count} transaction(s) related to the block reward");
return fixedOutputs.Count > 0;
})
.ToList();

// update real blockHeight from chain if necessary
//if (block.BlockHeight != blockInfo.Height)
//block.BlockHeight = blockInfo.Height;
logger.Debug(() => $"[{LogCategory}] Block {block.BlockHeight} [{block.Hash}] contains {blockRewardTransactions.Count} transaction(s) related to our wallet miner's addresses");

// update progress
// Two block confirmations methods are available:
// 1) ALPH default lock mechanism: All of the mined coins are locked for N minutes (up to +8 hours on mainnet, very short on testnet)
// 2) Mining pool operator provides a custom block rewards lock time, this method must be ONLY USE ON TESTNET in order to mimic MAINNET
if(extraPoolPaymentProcessingConfig?.BlockRewardsLockTime == null)
if(blockRewardTransactions.Count > 0)
{
logger.Info(() => $"[{LogCategory}] Block {block.BlockHeight} uses the default block reward lock mechanism for minimum confirmations calculation");

decimal transactionsLockTime = 0;
int totalTransactionsLockTime = 0;
foreach (var blockTransactionLockTime in blockRewardTransactions)
// update progress
// Two block confirmations methods are available:
// 1) ALPH default lock mechanism: All of the mined coins are locked for N minutes (up to +8 hours on mainnet, very short on testnet)
// 2) Mining pool operator provides a custom block rewards lock time, this method must be ONLY USE ON TESTNET in order to mimic MAINNET
if(extraPoolPaymentProcessingConfig?.BlockRewardsLockTime == null)
{
foreach (var unsignedLockTimeFixedOutputs in blockTransactionLockTime.Unsigned.FixedOutputs)
logger.Info(() => $"[{LogCategory}] Block {block.BlockHeight} [{block.Hash}] uses the default block reward lock mechanism for minimum confirmations calculation");

decimal transactionsLockTime = 0;
int totalTransactionsLockTime = 0;
foreach (var blockTransactionLockTime in blockRewardTransactions)
{
// We only need the transaction(s) for our wallet miner's addresses
if(walletMinersAddresses.Addresses.Contains(unsignedLockTimeFixedOutputs.Address))
foreach (var unsignedLockTimeFixedOutputs in blockTransactionLockTime.Unsigned.FixedOutputs)
{
transactionsLockTime += (decimal) unsignedLockTimeFixedOutputs.LockTime;
totalTransactionsLockTime += 1;
}
}
}
if(totalTransactionsLockTime > 0)
transactionsLockTime /= totalTransactionsLockTime;
if(totalTransactionsLockTime > 0)
transactionsLockTime /= totalTransactionsLockTime;

block.ConfirmationProgress = Math.Min(1.0d, (double) ((AlephiumUtils.UnixTimeStampForApi(clock.Now) - blockInfo.Timestamp) / (transactionsLockTime - blockInfo.Timestamp)));
}
else
{
logger.Info(() => $"[{LogCategory}] Block {block.BlockHeight} uses a custom [{network}] block rewards lock time: [{extraPoolPaymentProcessingConfig?.BlockRewardsLockTime}] minute(s)");
block.ConfirmationProgress = Math.Min(1.0d, (double) ((AlephiumUtils.UnixTimeStampForApi(clock.Now) - blockInfo.Timestamp) / (transactionsLockTime - blockInfo.Timestamp)));
}
else
{
logger.Info(() => $"[{LogCategory}] Block {block.BlockHeight} [{block.Hash}] uses a custom [{network}] block rewards lock time: [{extraPoolPaymentProcessingConfig?.BlockRewardsLockTime}] minute(s)");

block.ConfirmationProgress = Math.Min(1.0d, (double) ((AlephiumUtils.UnixTimeStampForApi(clock.Now) - blockInfo.Timestamp) / ((decimal) extraPoolPaymentProcessingConfig?.BlockRewardsLockTime * 60 * 1000)));
}
block.ConfirmationProgress = Math.Min(1.0d, (double) ((AlephiumUtils.UnixTimeStampForApi(clock.Now) - blockInfo.Timestamp) / ((decimal) extraPoolPaymentProcessingConfig?.BlockRewardsLockTime * 60 * 1000)));
}

result.Add(block);
result.Add(block);

messageBus.NotifyBlockConfirmationProgress(poolConfig.Id, block, coin);
messageBus.NotifyBlockConfirmationProgress(poolConfig.Id, block, coin);

// matured and spendable?
if(block.ConfirmationProgress >= 1)
{
block.Status = BlockStatus.Confirmed;
block.ConfirmationProgress = 1;
// matured and spendable?
if(block.ConfirmationProgress >= 1)
{
block.Status = BlockStatus.Confirmed;
block.ConfirmationProgress = 1;

// reset block reward
block.Reward = 0;
// reset block reward
block.Reward = 0;

foreach (var blockTransaction in blockRewardTransactions)
{
foreach (var unsignedFixedOutputs in blockTransaction.Unsigned.FixedOutputs)
foreach (var blockTransaction in blockRewardTransactions)
{
// We only need the transaction(s) for our wallet miner's addresses
if(walletMinersAddresses.Addresses.Contains(unsignedFixedOutputs.Address))
foreach (var unsignedFixedOutputs in blockTransaction.Unsigned.FixedOutputs)
{
block.Reward += AlephiumUtils.ConvertNumberFromApi(unsignedFixedOutputs.AttoAlphAmount) / AlephiumConstants.SmallestUnit;
}
}

logger.Info(() => $"[{LogCategory}] Unlocked block {block.BlockHeight} [{block.Hash}] worth {FormatAmount(block.Reward)}");
messageBus.NotifyBlockUnlocked(poolConfig.Id, block, coin);
}

logger.Info(() => $"[{LogCategory}] Unlocked block {block.BlockHeight} worth {FormatAmount(block.Reward)}");
messageBus.NotifyBlockUnlocked(poolConfig.Id, block, coin);
continue;
}
}

// If we end here that only means that we definitely lost that battle :'(
result.Add(block);

block.Status = BlockStatus.Orphaned;
block.Reward = 0;

logger.Info(() => $"[{LogCategory}] Block {block.BlockHeight} [{block.Hash}] classified as orphaned because it's not the chain");

messageBus.NotifyBlockUnlocked(poolConfig.Id, block, coin);
}
}

Expand All @@ -215,7 +265,20 @@ public virtual async Task<Block[]> ClassifyBlocksAsync(IMiningPool pool, Block[]
public virtual async Task PayoutAsync(IMiningPool pool, Balance[] balances, CancellationToken ct)
{
Contract.RequiresNonNull(balances);

var infosChainParams = await Guard(() => alephiumClient.GetInfosChainParamsAsync(ct));

var info = await Guard(() => alephiumClient.GetInfosInterCliquePeerInfoAsync(ct));

if(infosChainParams?.NetworkId != 7)
{
if(info?.Count < 1)
{
logger.Warn(() => $"[{LogCategory}] Payout aborted. Not enough peer(s)");
return;
}
}

// build args
var amounts = balances
.Where(x => x.Amount > 0)
Expand Down
Loading

0 comments on commit df5c3c5

Please sign in to comment.