All checks were successful
Publish NuGets / build (release) Successful in 39s
Adds an abstraction over the CreateChallengeRequest.complexity field
(already present in the proto since the original altcha module landed),
letting applications scale PoW difficulty per request based on actor
signals — repeat-offender counters, threat-intel headers, reputation
scores — without leaking those concerns into the gRPC provider.
- new IAltchaDifficultyAdvisor in Svrnty.CQRS.Altcha.Abstractions:
Task<uint?> GetComplexityAsync(...). null means "use the upstream
service's configured default."
- NullAltchaDifficultyAdvisor in Svrnty.CQRS.Altcha is the no-op
fallback registered by AddSvrntyAltcha() via TryAddSingleton, so
applications can replace it without ordering constraints.
- AltchaGrpcChallengeProvider now resolves the advisor and sets
CreateChallengeRequest.Complexity when the advisor returns a value.
The Altcha server clamps to its configured min/max, so callers
don't need to enforce bounds here.
No breaking changes to existing consumers — the no-op default keeps
behaviour identical when no advisor is registered.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
70 lines
2.4 KiB
C#
70 lines
2.4 KiB
C#
using Grpc.Core;
|
|
using Microsoft.Extensions.Logging;
|
|
using Microsoft.Extensions.Options;
|
|
using Svrnty.CQRS.Altcha.Abstractions;
|
|
|
|
namespace Svrnty.CQRS.Altcha.Grpc;
|
|
|
|
/// <summary>
|
|
/// Default <see cref="IAltchaChallengeProvider"/> backed by gRPC. Calls
|
|
/// <c>AltchaService.CreateChallenge</c> on the configured endpoint and
|
|
/// projects the response onto <see cref="AltchaChallenge"/>.
|
|
/// </summary>
|
|
public sealed class AltchaGrpcChallengeProvider : IAltchaChallengeProvider
|
|
{
|
|
private readonly AltchaService.AltchaServiceClient _client;
|
|
private readonly IOptions<AltchaGrpcOptions> _options;
|
|
private readonly IAltchaDifficultyAdvisor _advisor;
|
|
private readonly ILogger<AltchaGrpcChallengeProvider> _logger;
|
|
|
|
public AltchaGrpcChallengeProvider(
|
|
AltchaService.AltchaServiceClient client,
|
|
IOptions<AltchaGrpcOptions> options,
|
|
IAltchaDifficultyAdvisor advisor,
|
|
ILogger<AltchaGrpcChallengeProvider> logger)
|
|
{
|
|
_client = client;
|
|
_options = options;
|
|
_advisor = advisor;
|
|
_logger = logger;
|
|
}
|
|
|
|
public async Task<AltchaChallenge> CreateAsync(CancellationToken cancellationToken = default)
|
|
{
|
|
var opts = _options.Value;
|
|
var metadata = await AltchaCallCredentials.BuildMetadataAsync(opts, cancellationToken);
|
|
var deadline = DateTime.UtcNow.Add(opts.CallTimeout);
|
|
|
|
var request = new CreateChallengeRequest();
|
|
var advisedComplexity = await _advisor.GetComplexityAsync(cancellationToken);
|
|
if (advisedComplexity is uint complexity)
|
|
{
|
|
request.Complexity = complexity;
|
|
_logger.LogDebug("Altcha advisor requested complexity {Complexity}", complexity);
|
|
}
|
|
|
|
try
|
|
{
|
|
var response = await _client.CreateChallengeAsync(
|
|
request,
|
|
headers: metadata,
|
|
deadline: deadline,
|
|
cancellationToken: cancellationToken);
|
|
|
|
return new AltchaChallenge
|
|
{
|
|
Algorithm = response.Algorithm,
|
|
Challenge = response.ChallengeHash,
|
|
Salt = response.Salt,
|
|
Signature = response.Signature,
|
|
MaxNumber = response.Maxnumber
|
|
};
|
|
}
|
|
catch (RpcException ex)
|
|
{
|
|
_logger.LogError(ex, "Altcha create-challenge failed against {Endpoint}: {Status}", opts.Endpoint, ex.StatusCode);
|
|
throw;
|
|
}
|
|
}
|
|
}
|