Skip to content
This repository was archived by the owner on Sep 10, 2025. It is now read-only.

Commit da79dfc

Browse files
author
ceedii
committed
- Fix over payment issue for the KAS family, due to the node accepting the submission of multiple identical blocks
1 parent 1669d3f commit da79dfc

File tree

3 files changed

+57
-3
lines changed

3 files changed

+57
-3
lines changed

src/Miningcore/Blockchain/Kaspa/KaspaPayoutHandler.cs

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -154,15 +154,27 @@ public virtual async Task<Block[]> ClassifyBlocksAsync(IMiningPool pool, Block[]
154154
.Skip(i * pageSize)
155155
.Take(pageSize)
156156
.ToArray();
157-
157+
158158
for(var j = 0; j < page.Length; j++)
159159
{
160160
var block = page[j];
161-
161+
162+
// There is a case scenario:
163+
// https://github.com/blackmennewstyle/miningcore/issues/191
164+
// Sadly miners can submit different solutions which will produce the exact same blockHash for the same block
165+
// We must handle that case carefully here, otherwise we will overpay our miners.
166+
// Only one of these blocks must will be confirmed, the others will all become Orphans
167+
uint totalDuplicateBlockBefore = await cf.Run(con => blockRepo.GetPoolDuplicateBlockBeforeCountByPoolHeightAndHashNoTypeAndStatusAsync(con, poolConfig.Id, Convert.ToInt64(block.BlockHeight), block.Hash, new[]
168+
{
169+
BlockStatus.Confirmed,
170+
BlockStatus.Orphaned,
171+
BlockStatus.Pending
172+
}, block.Created));
173+
162174
var request = new kaspad.KaspadMessage();
163175
request.GetBlockRequest = new kaspad.GetBlockRequestMessage
164176
{
165-
Hash = (string) block.Hash,
177+
Hash = block.Hash,
166178
IncludeTransactions = true,
167179
};
168180
await Guard(() => stream.RequestStream.WriteAsync(request),
@@ -181,6 +193,18 @@ await Guard(() => stream.RequestStream.WriteAsync(request),
181193

182194
messageBus.NotifyBlockUnlocked(poolConfig.Id, block, coin);
183195
}
196+
// multiple blocks with the exact same height & hash recorded in the database
197+
else if(totalDuplicateBlockBefore > 0)
198+
{
199+
result.Add(block);
200+
201+
block.Status = BlockStatus.Orphaned;
202+
block.Reward = 0;
203+
204+
logger.Info(() => $"[{LogCategory}] Block {block.BlockHeight} [{block.Hash}] classified as orphaned because we already have in the database {totalDuplicateBlockBefore} block(s) with the same height and hash");
205+
206+
messageBus.NotifyBlockUnlocked(poolConfig.Id, block, coin);
207+
}
184208
else
185209
{
186210
logger.Info(() => $"[{LogCategory}] Block {block.BlockHeight} uses a custom minimum confirmations calculation [{minConfirmations}]");

src/Miningcore/Persistence/Postgres/Repositories/BlockRepository.cs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,4 +244,32 @@ public async Task<uint> GetPoolDuplicateBlockAfterCountByPoolHeightNoTypeAndStat
244244
after
245245
}));
246246
}
247+
248+
public async Task<uint> GetPoolDuplicateBlockBeforeCountByPoolHeightAndHashNoTypeAndStatusAsync(IDbConnection con, string poolId, long height, string hash, BlockStatus[] status, DateTime before)
249+
{
250+
const string query = @"SELECT COUNT(id) FROM blocks WHERE poolid = @poolId AND blockheight = @height AND hash = @hash AND status = ANY(@status) AND created < @before";
251+
252+
return await con.ExecuteScalarAsync<uint>(new CommandDefinition(query, new
253+
{
254+
poolId,
255+
height,
256+
hash,
257+
status = status.Select(x => x.ToString().ToLower()).ToArray(),
258+
before
259+
}));
260+
}
261+
262+
public async Task<uint> GetPoolDuplicateBlockAfterCountByPoolHeightAndHashNoTypeAndStatusAsync(IDbConnection con, string poolId, long height, string hash, BlockStatus[] status, DateTime after)
263+
{
264+
const string query = @"SELECT COUNT(id) FROM blocks WHERE poolid = @poolId AND blockheight = @height AND hash = @hash AND status = ANY(@status) AND created > @after";
265+
266+
return await con.ExecuteScalarAsync<uint>(new CommandDefinition(query, new
267+
{
268+
poolId,
269+
height,
270+
hash,
271+
status = status.Select(x => x.ToString().ToLower()).ToArray(),
272+
after
273+
}));
274+
}
247275
}

src/Miningcore/Persistence/Repositories/IBlockRepository.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,4 +27,6 @@ public interface IBlockRepository
2727
Task<uint> GetPoolDuplicateBlockCountByPoolHeightNoTypeAndStatusAsync(IDbConnection con, string poolId, long height, BlockStatus[] status);
2828
Task<uint> GetPoolDuplicateBlockBeforeCountByPoolHeightNoTypeAndStatusAsync(IDbConnection con, string poolId, long height, BlockStatus[] status, DateTime before);
2929
Task<uint> GetPoolDuplicateBlockAfterCountByPoolHeightNoTypeAndStatusAsync(IDbConnection con, string poolId, long height, BlockStatus[] status, DateTime after);
30+
Task<uint> GetPoolDuplicateBlockBeforeCountByPoolHeightAndHashNoTypeAndStatusAsync(IDbConnection con, string poolId, long height, string hash, BlockStatus[] status, DateTime before);
31+
Task<uint> GetPoolDuplicateBlockAfterCountByPoolHeightAndHashNoTypeAndStatusAsync(IDbConnection con, string poolId, long height, string hash, BlockStatus[] status, DateTime after);
3032
}

0 commit comments

Comments
 (0)