Skip to content

feat: js-libp2p compatibility — zero-copy JSI data path + Node net.So…#232

Open
tswindell wants to merge 1 commit into
Rapsssito:masterfrom
tswindell:venho/libp2p-jsi-209
Open

feat: js-libp2p compatibility — zero-copy JSI data path + Node net.So…#232
tswindell wants to merge 1 commit into
Rapsssito:masterfrom
tswindell:venho/libp2p-jsi-209

Conversation

@tswindell
Copy link
Copy Markdown

…cket half-open parity (#209)

Fixes #209 (Task 2: RN clients failing to connect to libp2p bootstrap/relay/peer nodes with UnexpectedEOFError). Task 1 (#183, net.createServer(options, cb)) is already resolved as of v6.4.1 — no code change needed.

Root-caused four independent defects that made js-libp2p v3 unusable on Android RN 0.83 (New Arch / bridgeless / Hermes), each device-proven on a physical device (Nokia 8.3 5G, arm64-v8a) against the public Amino DHT swarm:

  1. connect() option-graph OOM. @libp2p/tcp spreads its entire cyclic dial-options object (signal/upgrader/components) into the net.connect() arg; RN's jsi::dynamicFromValue recursively explodes it into millions of folly::dynamic nodes → Scudo OOM in seconds. Fixed with an explicit scalar allow-list (the native side only ever reads those keys).

  2. Per-chunk legacy-bridge data path OOM. Any per-chunk crossing of the RCTDeviceEventEmitter / invokeJavaMethod bridge backlogs unbounded folly::dynamic under libp2p volume. Replaced inbound + outbound byte movement AND the readable/written signals with a zero-copy JSI HostObject (cpp/TcpDataBridge) driven by the C++ CallInvoker — bytes and signals never transit folly::dynamic.

  3. JNI registration lifetime. In the New-Arch single-merged-.so model the data-bridge JNI_OnLoad ran pre-bundle under the bootstrap classloader; native methods failed to bind. Fixed with an implicitly-bound, Java-driven install entry + a link-time keep anchor (defeats --gc-sections stripping of the runtime-only TU).

  4. JSI install guard on a HostObject. The "callback installed" guard was stashed as a property on the JSI HostObject; Hermes rejects arbitrary-property writes on a HostObject with a default setter (TypeError), which was swallowed → bridge null → every noise write failed → every dial silently abandoned (conns=0). Moved the guards to module-level state.

Also implements the Node net.Socket half-open / teardown contract @libp2p/tcp depends on (and which the bounty's connect failure surfaces):

  • connect() now reads options.allowHalfOpen (kept off the native bridge — JS lifecycle flag only).
  • Adds socket.destroySoon() (libp2p sendClose calls it on every graceful close — its absence threw is not a function on every connection teardown).
  • Adds socket.resetAndDestroy() (libp2p sendReset).
  • 'close' now emits a boolean hadError per the Node spec (was the raw error object, working only by truthiness).
  • destroy(error) accepts an optional error per the Node signature.

iOS JSI parity is a scoped follow-up: the shared JS changes are iOS-correct, but the C++ data path is not yet wired into the iOS pod. The legacy iOS path is unaffected; only the new zero-copy JSI path is Android-only for now.

Adds tests/halfOpen209.test.js (6 tests). Full suite green (16/16). No version bump (release is the maintainer's call). Server.js / index.js / TLS*.js intentionally untouched.

…cket half-open parity (Rapsssito#209)

Fixes Rapsssito#209 (Task 2: RN clients failing to connect to libp2p
bootstrap/relay/peer nodes with `UnexpectedEOFError`). Task 1 (Rapsssito#183,
`net.createServer(options, cb)`) is already resolved as of v6.4.1 — no
code change needed.

Root-caused four independent defects that made js-libp2p v3 unusable on
Android RN 0.83 (New Arch / bridgeless / Hermes), each device-proven on
a physical device (Nokia 8.3 5G, arm64-v8a) against the public Amino
DHT swarm:

1. connect() option-graph OOM. @libp2p/tcp spreads its entire cyclic
   dial-options object (signal/upgrader/components) into the
   net.connect() arg; RN's jsi::dynamicFromValue recursively explodes it
   into millions of folly::dynamic nodes → Scudo OOM in seconds. Fixed
   with an explicit scalar allow-list (the native side only ever reads
   those keys).

2. Per-chunk legacy-bridge data path OOM. Any per-chunk crossing of the
   RCTDeviceEventEmitter / invokeJavaMethod bridge backlogs unbounded
   folly::dynamic under libp2p volume. Replaced inbound + outbound byte
   movement AND the readable/written signals with a zero-copy JSI
   HostObject (cpp/TcpDataBridge) driven by the C++ CallInvoker — bytes
   and signals never transit folly::dynamic.

3. JNI registration lifetime. In the New-Arch single-merged-.so model
   the data-bridge JNI_OnLoad ran pre-bundle under the bootstrap
   classloader; native methods failed to bind. Fixed with an
   implicitly-bound, Java-driven install entry + a link-time keep
   anchor (defeats --gc-sections stripping of the runtime-only TU).

4. JSI install guard on a HostObject. The "callback installed" guard
   was stashed as a property on the JSI HostObject; Hermes rejects
   arbitrary-property writes on a HostObject with a default setter
   (TypeError), which was swallowed → bridge null → every noise write
   failed → every dial silently abandoned (conns=0). Moved the guards
   to module-level state.

Also implements the Node net.Socket half-open / teardown contract
@libp2p/tcp depends on (and which the bounty's connect failure surfaces):

- connect() now reads `options.allowHalfOpen` (kept off the native
  bridge — JS lifecycle flag only).
- Adds socket.destroySoon() (libp2p sendClose calls it on every
  graceful close — its absence threw `is not a function` on every
  connection teardown).
- Adds socket.resetAndDestroy() (libp2p sendReset).
- 'close' now emits a boolean `hadError` per the Node spec (was the
  raw error object, working only by truthiness).
- destroy(error) accepts an optional error per the Node signature.

iOS JSI parity is a scoped follow-up: the shared JS changes are
iOS-correct, but the C++ data path is not yet wired into the iOS pod.
The legacy iOS path is unaffected; only the new zero-copy JSI path is
Android-only for now.

Adds __tests__/halfOpen209.test.js (6 tests). Full suite green
(16/16). No version bump (release is the maintainer's call). Server.js
/ index.js / TLS*.js intentionally untouched.
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.

Bounty: $300 for Fixing js-libp2p Compatibility Issues in react-native-tcp-socket

1 participant