Skip to content

C++, GO, dotnet bindings via uniffi - #406

Open
dkackman wants to merge 97 commits into
xch-dev:mainfrom
dkackman:more-uniffi
Open

C++, GO, dotnet bindings via uniffi#406
dkackman wants to merge 97 commits into
xch-dev:mainfrom
dkackman:more-uniffi

Conversation

@dkackman

@dkackman dkackman commented Jun 2, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds UniFFI as a fourth bindings backend alongside the existing pyo3, napi, and wasm targets, enabling generation of C++, Go, and .NET (C#) bindings for the Chia Wallet SDK from a single Rust source.

What's included

  • bindy crate: New uniffi feature-gated backend mirroring the existing pyo3/napi/wasm conversions (BigInt, u64/u128 as String, bytes as Vec, signed 64/128-bit types).
  • chia-sdk-bindings: UniFFI feature wiring, type mappings in bindings.json, shared tokio runtime management, and timeout options on peer/RPC clients.
  • UniFFI crate entry point (uniffi/): Library scaffolding with uniffi.toml config and documentation of the async + remote interface limitation.
  • .NET / NuGet (dotnet/): ChiaWalletSdk library project targeting net8 for broad compatibility, xUnit test project with 12 tests covering the core binding API, MSBuild post-processing to promote generated types to public, an MSBuild target that patches a String.Format ambiguity in the generated Clvm class, and a multi-arch GitHub Actions workflow (win-x64, linux-x64, osx-arm64, osx-x64) that builds native libraries and publishes the NuGet package.
  • Go (go/): Generated bindings plus integration tests and a dedicated GitHub Actions job.
  • C++ (cpp/): Generated bindings with matching integration tests; async Rust is exposed as synchronous to match C++ ergonomics (using tokio block_on)
  • Configurable timeouts for full node, coinset and peer clients. Defaults to no timeouts to preserve current behavior
  • Python: Regenerated type stubs reflecting the new timeout options.

Notes

Timeouts

Because the C++ converts async operations to synchronous ones, I introduced timeout options that get passed down to reqwest and websockets. These are opt-in so current behavior doesn't change. This could also be done as a separate PR but without them those synchronous calls have no cancellation mechanism for C++.

uniffi plugin gaps

There are a couple of places where uniffi-* is not generating valid code. I've used post processing string replacement to get around those gaps. Updates to uniffi plugin versions (which do not seem to happen often) need to be handled carefully.

dkackman and others added 30 commits April 8, 2026 06:26
Adds a new `uniffi` feature-gated backend to the bindy type conversion
framework, mirroring the existing pyo3/napi/wasm backends, with
`Uniffi` / `UniffiContext` marker types and conversions for BigInt,
u64, u128 (as String), and bytes types (as Vec<u8>).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Move uniffi dependency to workspace (consistent with all other deps)
- Add impl_self!(i64) and impl_self!(i128) to uniffi_impls.rs so signed
  64/128-bit types work with the uniffi feature
- Fix redundant Ok(...)? wrapping in IntoRust impls for u64 and u128
- Clarify the usize comment to reference the lib.rs blanket impl

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Create the uniffi/ crate as the C# bindings entry point. This required
several supporting changes:

- Add uniffi workspace member and create Cargo.toml, build.rs, src/lib.rs
- Hand-write the Clvm::alloc method dispatching ClvmType enum variants
- Add {usize} -> u32 mapping in bindings.json and FromRust/IntoRust impls
- Fix orphan rules: use concrete UniffiContext instead of generic T, add
  Clone derive to wrapper structs, add blanket Arc<U> FromRust/IntoRust
- Emit static methods as free functions (UniFFI 0.28 limitation)
- Gate peer/rpc types behind uniffi feature alongside napi/pyo3
- Add chia-sdk-client and chia-ssl deps to bindy uniffi feature

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Removes the num-bigint dependency from the uniffi crate (BigInt conversions
live entirely in bindy/src/uniffi_impls.rs). Adds a comment above Clvm::alloc
explaining that nil/int/bool/string/bytes must go through typed helper methods
in the UniFFI backend rather than the dynamic alloc path.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Also fix String.Format ambiguity in generated bindings where Clvm.String(string)
method shadowed System.String within the Clvm class body.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The Clvm class defines a String(string) method which shadows System.String
in the class body, causing CS0119 on the ObjectDisposedException /
OverflowException guard lines. Patch the two affected call sites in the
generated file to use System.String.Format explicitly.

Also suppress a nullability warning in CurryRoundtrip by asserting non-null
inline with the null-forgiving operator.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace the manual sed step with a PatchClvmStringAmbiguity MSBuild
target that runs BeforeTargets="CoreCompile" on every build. The patch
is idempotent, so regenerating the bindings and running dotnet test is
enough — no separate script needed.

Update README to remove the manual patch step and reference the build
target instead.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Use RoslynCodeTaskFactory to apply the String.Format fix in pure .NET,
eliminating the sed dependency. Works identically on Windows, macOS,
and Linux with no external tools.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Also adds InternalsVisibleTo in ChiaWalletSdk.csproj so the test
assembly can access the internal types in the generated bindings.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Allows the chia.wallet.dotnet test project to access internal SDK types.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@dkackman
dkackman marked this pull request as draft June 2, 2026 00:39
@dkackman dkackman mentioned this pull request Jun 2, 2026
@dkackman dkackman changed the title More uniffi C++, GO, dotnet bindings via uniffi Jun 2, 2026
dkackman and others added 9 commits June 1, 2026 21:30
Adds set_request_timeout, RpcClientOptions, the *_with_options RPC
factories, and the new PeerOptions timeout fields to the committed
.pyi, matching the generator output checked by CI.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@dkackman
dkackman marked this pull request as ready for review June 3, 2026 17:04
dkackman and others added 10 commits June 3, 2026 15:40
When a request_timeout fires, the oneshot receiver was dropped but the
sender entry stayed in the RequestMap (purged only lazily on the next
insert). A later request could reuse that id before the peer's delayed
response arrived, causing the stale response to be delivered to the
unrelated newer request. Remove the id explicitly on timeout.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The ReplaceInFile/ReplaceInFileRegex tasks silently no-op'd when their
pattern matched zero times. For the internal->public visibility
promotions that means a uniffi-bindgen-cs output change would ship a
NuGet whose entire public API is internal (unusable) while the build
stays green. Add an opt-in FailIfMissing flag and set it on the two
always-present promotions so a generator format change fails loudly.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The dotnet workflow packed and published to NuGet.org without ever
running the .NET test suite (unlike the cpp and go workflows). Add a
test job that builds the native lib, generates the bindings, and runs
dotnet test, and make pack depend on it so publish is gated on tests
passing.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The workflow triggered on every tag ("**") and fed GITHUB_REF_NAME
verbatim into dotnet pack, so a non-semver tag (e.g. "latest") or a
v-prefixed tag would fail the pack or publish a malformed version to
NuGet.org. Restrict the tag trigger to semver-shaped tags, strip an
optional leading 'v', and validate the version (via an env var rather
than direct interpolation) so a bad tag fails fast.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add a regression test that drives a request to timeout against a silent
in-process websocket server and asserts the RequestMap is emptied, so a
future change can't reintroduce the stale-entry / id-reuse misrouting bug.

Adds a test-only RequestMap::len() helper and a tokio dev-dependency
(net/macros) for the in-process server. Also restructures the timeout
arm in request_raw as an if/else (no behavior change) to satisfy the
clippy single_match_else lint introduced by the earlier fix.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… check

The previous FailIfMissing flag asserted that the `internal `->`public `
replacement *fired*, which broke idempotency: the generated bindings file
persists across builds, so the target re-runs over an already-promoted
file (e.g. dotnet pack then dotnet test) where the `internal ` patterns
are legitimately gone, causing a false build failure.

Replace it with an AssertFileContains check on the desired end state
(public ChiaWalletSdkMethods + a core public domain type). This is
idempotent — it passes whether the file was just promoted or already
public — yet still fails loudly if a uniffi-bindgen-cs format change
leaves the public API internal.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant