Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions BTCPayServer.Plugins.Payjoin/Plugin.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ public override void Execute(IServiceCollection applicationBuilder)
provider.GetRequiredService<BTCPayNetworkProvider>(),
provider.GetRequiredService<PayjoinReceiverSessionStore>(),
provider.GetRequiredService<PayjoinMailroomManager>(),
provider.GetRequiredService<PayjoinOhttpKeysProvider>(),
provider.GetRequiredService<PayjoinAvailabilityService>(),
provider.GetRequiredService<PayjoinSessionBuildLock>(),
provider.GetRequiredService<IPayjoinAccountingBridgeService>(),
Expand Down
13 changes: 11 additions & 2 deletions BTCPayServer.Plugins.Payjoin/Services/PayjoinOhttpKeysProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,10 @@ namespace BTCPayServer.Plugins.Payjoin.Services;

public sealed class PayjoinOhttpKeysProvider
{
// TODO: Consider making this configurable if 12 hours is not a good duration for caching OHTTP keys.
private static readonly TimeSpan OhttpKeysCacheDuration = TimeSpan.FromHours(12);
// Directory OHTTP keys can rotate; a shorter lifetime bounds how long stale keys are served, and
// Invalidate lets a failed session build drop them immediately instead of waiting out the window.
// TODO: Consider making this configurable if one hour is not a good duration for caching OHTTP keys.
private static readonly TimeSpan OhttpKeysCacheDuration = TimeSpan.FromHours(1);

// TODO: Consider making this configurable if 10 seconds is not a good timeout for fetching OHTTP keys.
private static readonly TimeSpan OhttpKeysFetchTimeout = TimeSpan.FromSeconds(10);
Expand Down Expand Up @@ -121,6 +123,13 @@ internal async Task<PayjoinOhttpKeysFetchResult> FetchKeysAsync(
}
}

// Stale directory OHTTP keys are one way session construction fails; dropping the cached
// keys costs at most one refetch and lets the next attempt start from fresh material.
internal void Invalidate(string storeId, SystemUri ohttpRelayUrl, string directoryUrl)
{
_memoryCache.Remove(CreateCacheKey(storeId, ohttpRelayUrl, directoryUrl));
}

internal static string CreateCacheKey(string storeId, SystemUri ohttpRelayUrl, string directoryUrl)
{
return $"PayjoinOhttpKeys_{storeId}_{ohttpRelayUrl.AbsoluteUri}_{directoryUrl}";
Expand Down
21 changes: 18 additions & 3 deletions BTCPayServer.Plugins.Payjoin/Services/PayjoinUriSessionService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ public sealed class PayjoinUriSessionService
private readonly BTCPayNetworkProvider _networkProvider;
private readonly PayjoinReceiverSessionStore _receiverSessionStore;
private readonly PayjoinMailroomManager _mailroomManager;
private readonly PayjoinOhttpKeysProvider _ohttpKeysProvider;
private readonly PayjoinAvailabilityService _availabilityService;
private readonly PayjoinSessionBuildLock _sessionBuildLock;
private readonly IPayjoinAccountingBridgeService _accountingBridgeService;
Expand All @@ -44,6 +45,7 @@ internal PayjoinUriSessionService(
BTCPayNetworkProvider networkProvider,
PayjoinReceiverSessionStore receiverSessionStore,
PayjoinMailroomManager mailroomManager,
PayjoinOhttpKeysProvider ohttpKeysProvider,
PayjoinAvailabilityService availabilityService,
PayjoinSessionBuildLock sessionBuildLock,
IPayjoinAccountingBridgeService accountingBridgeService,
Expand All @@ -52,6 +54,7 @@ internal PayjoinUriSessionService(
_networkProvider = networkProvider;
_receiverSessionStore = receiverSessionStore;
_mailroomManager = mailroomManager;
_ohttpKeysProvider = ohttpKeysProvider;
_availabilityService = availabilityService;
_sessionBuildLock = sessionBuildLock;
_accountingBridgeService = accountingBridgeService;
Expand Down Expand Up @@ -129,19 +132,31 @@ public async Task<string> BuildAsync(

if (session is null)
{
var selectedRelay = await _mailroomManager.SelectBootstrapRouteAsync(
var bootstrapRoute = await _mailroomManager.SelectBootstrapRouteAsync(
storeSettings,
storeId,
invoiceId,
cancellationToken).ConfigureAwait(false);

if (selectedRelay is null)
if (bootstrapRoute is null)
{
return LogUnexpectedFallbackAndReturnBip21(bip21, invoiceId, "OHTTP keys are unavailable from all configured relays");
}

var bootstrapPersister = new CapturingReceiverSessionPersister();
InitializeSession(destination, due, selectedRelay.DirectoryUrl.AbsoluteUri, selectedRelay.OhttpKeys, monitoringExpiresAt, bootstrapPersister);
try
{
InitializeSession(destination, due, bootstrapRoute.DirectoryUrl.AbsoluteUri, bootstrapRoute.OhttpKeys, monitoringExpiresAt, bootstrapPersister);
}
catch (UniffiException)
{
// Stale directory OHTTP keys are one way session construction fails; dropping the
// cached keys costs at most one refetch and lets the next attempt start from fresh
// material. Replayed sessions never reach here, so their failures keep the cache.
_ohttpKeysProvider.Invalidate(storeId, bootstrapRoute.RelayUrl, bootstrapRoute.DirectoryUrl.AbsoluteUri);
throw;
}

session = _receiverSessionStore.CreateSession(
invoiceId,
destination,
Expand Down
Loading