diff --git a/payjoin-ffi/Cargo.toml b/payjoin-ffi/Cargo.toml index c55bdd187..f186ec753 100644 --- a/payjoin-ffi/Cargo.toml +++ b/payjoin-ffi/Cargo.toml @@ -33,7 +33,7 @@ serde = { version = "1.0.219", features = ["derive"] } serde_json = "1.0.142" thiserror = "2.0.14" tokio = { version = "1.47.1", features = ["full"], optional = true } -uniffi = { version = "0.30.0", features = ["cli"] } +uniffi = { version = "0.30.0", features = ["cli", "tokio"] } uniffi-bindgen-cs = { git = "https://github.com/chavic/uniffi-bindgen-cs.git", rev = "878a3d269eacce64beadcd336ade0b7c8da09824", optional = true } uniffi-dart = { git = "https://github.com/Uniffi-Dart/uniffi-dart.git", rev = "f830323", optional = true } url = "2.5.4" diff --git a/payjoin-ffi/csharp/IntegrationTests.cs b/payjoin-ffi/csharp/IntegrationTests.cs index d17742a79..cc5150690 100644 --- a/payjoin-ffi/csharp/IntegrationTests.cs +++ b/payjoin-ffi/csharp/IntegrationTests.cs @@ -323,6 +323,32 @@ public ValueTask DisposeAsync() return ValueTask.CompletedTask; } + /// + /// Regression test: called from a plain .NET async + /// context should successfully return OHTTP keys. + /// + /// Without the fix this test fails with: + /// PanicException: "there is no reactor running, must be called from the context of a Tokio 1.x runtime" + /// + [Fact] + public async Task FetchOhttpKeys_ShouldWorkFromNonTokioContext() + { + // Arrange: use TestServices' URLs and certificate so connectivity is guaranteed and + // the failure (if any) is purely about missing Tokio runtime, not TLS trust setup. + _services!.WaitForServicesReady(); + var ohttpRelay = _services.OhttpRelayUrl(); + var directory = _services.DirectoryUrl(); + var cert = _services.Cert(); + + // Act: call the raw UniFFI async binding directly — NOT TestServices.FetchOhttpKeys(), + // which uses an internal block_on(RUNTIME) and therefore always has a Tokio context. + // PayjoinMethods.FetchOhttpKeysWithCert() has no such safety net. + var keys = await PayjoinMethods.FetchOhttpKeysWithCert(ohttpRelay, directory, cert); + + // Assert + Assert.NotNull(keys); + } + [Fact] public void TestFfiValidation() { diff --git a/payjoin-ffi/csharp/README.md b/payjoin-ffi/csharp/README.md index 9afccc5c3..a24fbb074 100644 --- a/payjoin-ffi/csharp/README.md +++ b/payjoin-ffi/csharp/README.md @@ -56,12 +56,12 @@ dotnet test Generation uses the Cargo-managed C# generator from `payjoin-ffi/Cargo.toml`. -By default, generation builds `payjoin-ffi` with `_test-utils` enabled to keep parity with other language test scripts. Override via `PAYJOIN_FFI_FEATURES`. +By default, generation builds `payjoin-ffi` with `_test-utils,_manual-tls` so C# integration tests can use local HTTPS services with generated self-signed certificates. Override via `PAYJOIN_FFI_FEATURES`. ### Unix shells ```shell -export PAYJOIN_FFI_FEATURES=_test-utils # default behavior +export PAYJOIN_FFI_FEATURES=_test-utils,_manual-tls # default behavior # export PAYJOIN_FFI_FEATURES="" # build without extra features bash ./scripts/generate_bindings.sh ``` @@ -69,7 +69,7 @@ bash ./scripts/generate_bindings.sh ### PowerShell ```powershell -$env:PAYJOIN_FFI_FEATURES = "_test-utils" # default behavior +$env:PAYJOIN_FFI_FEATURES = "_test-utils,_manual-tls" # default behavior # $env:PAYJOIN_FFI_FEATURES = "" # build without extra features powershell -ExecutionPolicy Bypass -File .\scripts\generate_bindings.ps1 dotnet build diff --git a/payjoin-ffi/csharp/scripts/generate_bindings.ps1 b/payjoin-ffi/csharp/scripts/generate_bindings.ps1 index 52e782508..ff195ef87 100644 --- a/payjoin-ffi/csharp/scripts/generate_bindings.ps1 +++ b/payjoin-ffi/csharp/scripts/generate_bindings.ps1 @@ -40,8 +40,9 @@ Write-Host "Generating payjoin C#..." if ($null -ne $env:PAYJOIN_FFI_FEATURES) { $payjoinFfiFeatures = $env:PAYJOIN_FFI_FEATURES } else { - # Keep parity with other language test scripts: include _test-utils by default. - $payjoinFfiFeatures = "_test-utils" + # Include test utilities and manual TLS by default so local test services + # can fetch OHTTP keys over HTTPS with their generated self-signed cert. + $payjoinFfiFeatures = "_test-utils,_manual-tls" } if ($payjoinFfiFeatures) { diff --git a/payjoin-ffi/csharp/scripts/generate_bindings.sh b/payjoin-ffi/csharp/scripts/generate_bindings.sh index 630ab2146..5de84d415 100755 --- a/payjoin-ffi/csharp/scripts/generate_bindings.sh +++ b/payjoin-ffi/csharp/scripts/generate_bindings.sh @@ -22,8 +22,9 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" cd "$SCRIPT_DIR/../.." echo "Generating payjoin C#..." -# Keep parity with other language test scripts: include _test-utils by default. -PAYJOIN_FFI_FEATURES=${PAYJOIN_FFI_FEATURES:-_test-utils} +# Include test utilities and manual TLS by default so local test services +# can fetch OHTTP keys over HTTPS with their generated self-signed cert. +PAYJOIN_FFI_FEATURES=${PAYJOIN_FFI_FEATURES:-_test-utils,_manual-tls} GENERATOR_FEATURES="csharp" if [[ -n $PAYJOIN_FFI_FEATURES ]]; then GENERATOR_FEATURES="$GENERATOR_FEATURES,$PAYJOIN_FFI_FEATURES" diff --git a/payjoin-ffi/src/io.rs b/payjoin-ffi/src/io.rs index 34a1f6d5c..6069c4e75 100644 --- a/payjoin-ffi/src/io.rs +++ b/payjoin-ffi/src/io.rs @@ -21,7 +21,7 @@ pub mod error { /// /// * `payjoin_directory`: The payjoin directory from which to fetch the ohttp keys. This /// directory stores and forwards payjoin client payloads. -#[uniffi::export] +#[uniffi::export(async_runtime = "tokio")] pub async fn fetch_ohttp_keys( ohttp_relay: &str, payjoin_directory: &str, @@ -43,6 +43,7 @@ pub async fn fetch_ohttp_keys( /// /// * `cert_der`: The DER-encoded certificate to use for local HTTPS connections. #[cfg(feature = "_manual-tls")] +#[uniffi::export(async_runtime = "tokio")] pub async fn fetch_ohttp_keys_with_cert( ohttp_relay: &str, payjoin_directory: &str,