Skip to content

Commit 6f44905

Browse files
committed
Address validation: treat RPC errors as Unknown
Introduce AddressValidationResult (Valid/Invalid/Unknown) and replace the simple bool address validation with ValidateAddressDetailedAsync in BitcoinJobManagerBase. Add retry logic (default 3 attempts, 500ms delay) so RPC timeouts/connection errors return Unknown rather than being treated as Invalid. Update BitcoinPool to authorize only on Valid, reject Unknown without banning (logs a warning), and retain ban behavior for explicit Invalid responses. This prevents mis-banning miners due to daemon unresponsiveness.
1 parent e196632 commit 6f44905

3 files changed

Lines changed: 69 additions & 6 deletions

File tree

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
namespace Miningcore.Blockchain.Bitcoin;
2+
3+
// Result of address validation against the coin daemon.
4+
// Three states instead of bool: RPC timeout/error is not proof of an
5+
// invalid address, so it must not collapse into the same "false" as
6+
// an explicit invalid response from the daemon.
7+
public enum AddressValidationResult
8+
{
9+
// Daemon confirmed the address is valid.
10+
Valid,
11+
12+
// Daemon confirmed the address is invalid.
13+
Invalid,
14+
15+
// No definitive answer after retries (timeout/connection error).
16+
// Must not be treated as Invalid.
17+
Unknown
18+
}

src/Miningcore/Blockchain/Bitcoin/BitcoinJobManagerBase.cs

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,7 @@ protected virtual async Task UpdateNetworkStatsAsync(CancellationToken ct)
230230
logger.Warn(() => $"Error(s) refreshing network stats: {string.Join(", ", errors.Select(y => y.Error.Message))}");
231231

232232
// results[0]=GetMiningInfo and results[1]=GetNetworkInfo are mandatory.
233-
// results[2]=GetNetworkHashPS is optional already guarded by the null-check below.
233+
// results[2]=GetNetworkHashPS is optional - already guarded by the null-check below.
234234
if(results[0].Error != null || results[1].Error != null)
235235
return;
236236

@@ -593,14 +593,46 @@ public override void Configure(PoolConfig pc, ClusterConfig cc)
593593
base.Configure(pc, cc);
594594
}
595595

596-
public virtual async Task<bool> ValidateAddressAsync(string address, CancellationToken ct)
596+
// Retry attempts for validateaddress RPC before giving up as Unknown.
597+
protected virtual int AddressValidationMaxAttempts => 3;
598+
599+
// Delay between retry attempts.
600+
protected virtual TimeSpan AddressValidationRetryDelay => TimeSpan.FromMilliseconds(500);
601+
602+
public virtual async Task<AddressValidationResult> ValidateAddressDetailedAsync(string address, CancellationToken ct)
597603
{
598604
if(string.IsNullOrEmpty(address))
599-
return false;
605+
return AddressValidationResult.Invalid;
606+
607+
for(var attempt = 1; attempt <= AddressValidationMaxAttempts; attempt++)
608+
{
609+
var result = await rpc.ExecuteAsync<ValidateAddressResponse>(logger, BitcoinCommands.ValidateAddress, ct, new[] { address });
610+
611+
// RPC succeeded, daemon gave a definitive answer, no retry needed.
612+
if(result.Error == null && result.Response != null)
613+
return result.Response.IsValid ? AddressValidationResult.Valid : AddressValidationResult.Invalid;
614+
615+
// RPC failed (timeout/connection error). Not proof address is invalid.
616+
logger.Warn(() => $"validateaddress RPC failed for '{address}' (attempt {attempt}/{AddressValidationMaxAttempts}): {result.Error?.Message ?? "no response"}");
617+
618+
if(attempt < AddressValidationMaxAttempts)
619+
{
620+
try
621+
{
622+
await Task.Delay(AddressValidationRetryDelay, ct);
623+
}
624+
625+
catch(TaskCanceledException)
626+
{
627+
break;
628+
}
629+
}
630+
}
600631

601-
var result = await rpc.ExecuteAsync<ValidateAddressResponse>(logger, BitcoinCommands.ValidateAddress, ct, new[] { address });
632+
// All retries failed, no definitive answer from daemon.
633+
logger.Warn(() => $"Unable to validate address '{address}' after {AddressValidationMaxAttempts} attempts, daemon unresponsive");
602634

603-
return result.Response is {IsValid: true};
635+
return AddressValidationResult.Unknown;
604636
}
605637

606638
#endregion // API-Surface

src/Miningcore/Blockchain/Bitcoin/BitcoinPool.cs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,8 @@ protected virtual async Task OnAuthorizeAsync(StratumConnection connection, Time
105105
var workerName = split?.Skip(1).FirstOrDefault()?.Trim() ?? string.Empty;
106106

107107
// assumes that minerName is an address
108-
context.IsAuthorized = await manager.ValidateAddressAsync(minerName, ct);
108+
var addressValidationResult = await manager.ValidateAddressDetailedAsync(minerName, ct);
109+
context.IsAuthorized = addressValidationResult == AddressValidationResult.Valid;
109110
context.Miner = minerName;
110111
context.Worker = workerName;
111112

@@ -160,8 +161,20 @@ protected virtual async Task OnAuthorizeAsync(StratumConnection connection, Time
160161
await connection.NotifyAsync(BitcoinStratumMethods.SetDifficulty, new object[] { context.Difficulty });
161162
}
162163

164+
else if(addressValidationResult == AddressValidationResult.Unknown)
165+
{
166+
// Daemon did not respond after retries, not proof address is bad.
167+
// Reject without ban so a valid miner can just reconnect.
168+
logger.Warn(() => $"[{connection.ConnectionId}] Could not validate address '{minerName}' (daemon unresponsive), rejecting without ban");
169+
170+
await connection.RespondErrorAsync(StratumError.UnauthorizedWorker, "Address validation temporarily unavailable, please retry", request.Id, context.IsAuthorized);
171+
172+
Disconnect(connection);
173+
}
174+
163175
else
164176
{
177+
// Daemon explicitly confirmed the address is invalid, safe to ban.
165178
await connection.RespondErrorAsync(StratumError.UnauthorizedWorker, "Authorization failed", request.Id, context.IsAuthorized);
166179

167180
if(clusterConfig?.Banning?.BanOnLoginFailure is null or true)

0 commit comments

Comments
 (0)