New RPCConnectionManager: Single unified manager, no ringbuffer, OpenSSL-owned sockets - #8117
New RPCConnectionManager: Single unified manager, no ringbuffer, OpenSSL-owned sockets#8117Eddy Ashton (eddyashton) wants to merge 63 commits into
RPCConnectionManager: Single unified manager, no ringbuffer, OpenSSL-owned sockets#8117Conversation
…ransport cert-deferred listening + ALPN + outbound client, RPCConnectionManager (AbstractRPCSessions). Not yet wired into enclave/run.cpp.
…wire RPCConnectionManager into enclave.h/run.cpp, delete RPCSessions/rpc_connections/tls_session. Full build green, 53/53 unit tests pass.
…:Cert::use) and request client cert on inbound for caller auth; add peer-cert capture test. Full build green, unit tests pass.
…xing localhost/[::1] interfaces (cpp, cpp_cose_only, common_ipv6 e2e). Add localhost/IPv6 binding tests.
… large response queued just before close_socket() is not truncated (fixes cpp/cpp_cose_only receipt 'server disconnected'). Add truncation test.
…e (ERR_clear_error) before each SSL op so a stale error from one connection cannot poison SSL_get_error for another (root cause of cpp/cpp_cose_only 'server disconnected'). Add persistent-connection + peer-cert tests.
… handler; branch udp interfaces to listen_udp. Clearly marked QUIC extension points (substrate for OpenSSL >=3.5 native QUIC). e2e_logging udp echo passes; full suite green.
…ned quic_session.h/src/quic, udp.h + udp/msg_types.h + UDPImpl vestiges in run.cpp, dead RPC ringbuffer message enums (keep tcp::ConnID). Drop old-implementation comments. Fix build after cert.h use->configure_ssl rename + commit-callback include.
…Config (so the enclave-side RPC manager receives it); track per-connection last_active in OpenSSLServer and sweep idle connections off the epoll timeout. Plumbed manager->bridge->server. idletimeout e2e passes.
There was a problem hiding this comment.
Pull request overview
This PR replaces the legacy split host/enclave RPC networking stack (ringbuffer-driven sockets + memory-BIO TLS) with a single-process, OpenSSL-native connection layer, and updates the session boundary so HTTP/HTTP2/custom protocols operate on plaintext via a transport-provided SessionWriter.
Changes:
- Introduces
RPCConnectionManager+OpenSSLSessionManager+OpenSSLServer-backed transports, removing the old ringbuffer RPC message types,TLSSession, and host libuv RPC containers. - Refactors protocol sessions (HTTP/1 + HTTP/2) to run as
PlaintextSessioninstances which write responses throughSessionWriterrather than owning TLS state. - Adds a minimal epoll-based UDP
DatagramServerwith a temporary echo session for QUIC/UDP behavior, and updates docs/tests accordingly.
Custom instructions used:
.github/copilot-instructions.md.github/instructions/reviewing.instructions.md
Reviewed changes
Copilot reviewed 56 out of 57 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| tests/tls_groups.py | Updates test comment to reference new OpenSSL-native TLS group source header. |
| tests/infra/clients.py | Makes client context managers exception-safe via try/finally close. |
| tests/e2e_operations.py | Fixes ledger chunk skipping logic when chunk end seqno is None (open chunk). |
| tests/connections.py | Makes socket context manager exception-safe via try/finally close. |
| src/udp/msg_types.h | Removes UDP ringbuffer message types from old split RPC transport. |
| src/tls/tls.h | Removes legacy TLS error-code shim header tied to the old BIO/ringbuffer model. |
| src/tls/test/main.cpp | Removes legacy TLS unit tests for the old in-enclave TLS path. |
| src/tls/server.h | Removes legacy TLS server wrapper around ccf::tls::Context. |
| src/tls/README.md | Removes docs describing the deprecated TLS emulation layer. |
| src/tls/plaintext_server.h | Removes legacy plaintext server adapter that mimicked TLS Context. |
| src/tls/context.h | Removes legacy TLS Context implementation (memory BIO + negated error codes). |
| src/tls/client.h | Removes legacy TLS client wrapper around ccf::tls::Context. |
| src/tcp/msg_types.h | Drops TCP ringbuffer message definitions, keeping only ConnID. |
| src/quic/test/main.cpp | Removes legacy QUIC OpenSSL call test tied to removed TLS headers. |
| src/quic/quic_session.h | Removes old QUIC session implementation built on ringbuffer UDP messages. |
| src/node/rpc/custom_protocol_subsystem.h | Updates custom protocol session creation to use (ConnID, SessionWriter&). |
| src/node/node_state.h | Switches node RPC session ownership to AbstractRPCSessions. |
| src/node/http_node_client.h | Adds missing include needed by refactored HTTP node client code. |
| src/js/extensions/ccf/crypto.cpp | Replaces TLS CA-based X509 bundle check with direct OpenSSL parsing helpers. |
| src/http/test/curl_test.cpp | Updates transient curl error classification tests (adds CURLE_SSL_CONNECT_ERROR). |
| src/http/http2_session.h | Migrates HTTP/2 server session to PlaintextSession + explicit peer cert passing. |
| src/http/http_session.h | Migrates HTTP/1 server session to PlaintextSession + explicit peer cert passing. |
| src/http/http_proc.h | Removes dependency on old tls_session.h by switching includes. |
| src/http/http_parser.h | Removes dependency on old tls_session.h by switching includes. |
| src/http/curl.h | Treats CURLE_SSL_CONNECT_ERROR as retryable and documents rationale. |
| src/host/udp.h | Removes legacy libuv UDP implementation used by the old split transport. |
| src/host/tls/openssl_session_manager.h | Adds transport-to-session bridge implementing SessionWriter over OpenSSL-native sockets. |
| src/host/test/rpc_connections.cpp | Removes tests for legacy host RPC connection containers. |
| src/host/run.cpp | Removes old host-side RPC interface setup; host now writes rpc addresses returned by enclave. |
| src/host/rpc_connections.h | Removes legacy host RPC connection container. |
| src/host/rpc_connection_manager.h | Adds new unified RPC connection manager (per-interface policy + transports + UDP demux). |
| src/host/datagram_server.h | Adds epoll-based UDP server substrate (future QUIC extension point). |
| src/host/datagram_echo_session.h | Adds temporary UDP echo session used for current QUIC/UDP behavior. |
| src/host/configuration.h | Removes host config idle timeout field (now part of startup config / RPC manager). |
| src/enclave/tls_session.h | Removes legacy enclave TLS session (ringbuffer/BIO-based). |
| src/enclave/session.h | Refactors session boundary to plaintext + SessionWriter; updates handle_incoming_data signature. |
| src/enclave/session_writer.h | Adds SessionWriter abstraction for transport-owned output. |
| src/enclave/rpc_sessions.h | Removes legacy in-enclave RPCSessions container. |
| src/enclave/no_more_sessions.h | Adds NoMoreSessionsImpl for soft session cap behavior with immediate plaintext 503. |
| src/enclave/main.cpp | Extends enclave entry to return resolved RPC addresses to host. |
| src/enclave/entry_points.h | Extends entry point signature to return resolved RPC addresses to host. |
| src/enclave/enclave.h | Wires new RPCConnectionManager, binds interfaces in enclave, and stops transports on shutdown. |
| src/enclave/abstract_rpc_sessions.h | Introduces transport-agnostic RPC sessions interface used by node/frontends. |
| src/common/configuration.h | Adds idle_connection_timeout to JSON (startup config) serialization. |
| src/clients/tls/test/main.cpp | Adds new unit tests for client tooling TLS certificate helpers. |
| src/clients/tls/README.md | Documents that TLS helpers are client-tooling-only (node uses host/tls + curl). |
| src/clients/tls/cert.h | Updates includes to new client-tooling TLS CA/cert helpers. |
| src/clients/tls/ca.h | Adds new client-tooling CA helper (OpenSSL store configuration). |
| src/clients/tls_client.h | Updates includes to new client-tooling TLS CA/cert helpers. |
| include/ccf/research/custom_protocol_subsystem_interface.h | Updates custom protocol interface to accept SessionWriter& instead of TLS context. |
| include/ccf/node/startup_config.h | Adds idle_connection_timeout to startup config API. |
| include/ccf/node/session.h | Extends session interface to accept source sockaddr for datagram transports. |
| doc/contribute/onboarding.rst | Updates onboarding diagram to reflect TLS termination in connection layer + writer boundary. |
| doc/architecture/tls_internals.rst | Rewrites TLS internals doc to match new OpenSSL-native connection design. |
| CMakeLists.txt | Replaces removed TLS/rpc connection tests with openssl_server_test and new client TLS helper tests. |
Suppressed comments (1)
src/host/rpc_connection_manager.h:547
portcan be empty for port-less/ephemeral bind addresses.std::stoi(port)will throw in that case, so UDP interfaces configured with an unspecified port cannot start. Parse empty as 0 (ephemeral) before converting.
udp->server = std::make_unique<asynchost::DatagramServer>(
host,
static_cast<uint16_t>(std::stoi(port)),
[this, li, udp_ptr, writer](
DescriptionComparing 5 available runs from this branch (#8117) against the trend of the last 30 Each chart plots every benchmark as an axis, with values normalized so 100 is the EWMA baseline of recent Axis labels show the latest branch value and its difference from the main EWMA baseline, where 0% is on the baseline. They are coloured green where the latest run improves on the baseline, red where it regresses, and grey where the difference is within one std dev of the baseline (within noise). Higher is better for throughput and rate, lower for latency and memory. Throughput (tx/s)---
config:
radar:
width: 620
height: 620
marginTop: 90
marginRight: 220
marginBottom: 60
marginLeft: 220
axisLabelFactor: 1.12
curveTension: 0.08
theme: base
themeCSS: |
.radarCurve-0{fill:color-mix(in srgb, #62B5E5 13%, var(--color-canvas-default,var(--bgColor-default,#fff)))!important;fill-opacity:1!important;stroke:none!important;stroke-width:0!important}
.radarCurve-1{fill:color-mix(in srgb, #62B5E5 40%, var(--color-canvas-default,var(--bgColor-default,#fff)))!important;fill-opacity:1!important;stroke:none!important;stroke-width:0!important}
.radarCurve-2{fill:color-mix(in srgb, #62B5E5 13%, var(--color-canvas-default,var(--bgColor-default,#fff)))!important;fill-opacity:1!important;stroke:none!important;stroke-width:0!important}
.radarCurve-3{fill:var(--color-canvas-default,var(--bgColor-default,#fff))!important;fill-opacity:1!important;stroke:none!important;stroke-width:0!important}
.radarAxisLabel,.radarTitle{fill:var(--color-fg-default,var(--fgColor-default,#111827))!important;color:var(--color-fg-default,var(--fgColor-default,#111827))!important}
.radarCurve-4{stroke-width:1.5px!important;stroke-opacity:0.20!important}
.radarCurve-5{stroke-width:1.5px!important;stroke-opacity:0.30!important}
.radarCurve-6{stroke-width:1.5px!important;stroke-opacity:0.40!important}
.radarCurve-7{stroke-width:1.5px!important;stroke-opacity:0.50!important}
.radarCurve-8{stroke-width:1.75px!important;stroke-opacity:1.00!important}
.radarAxisLabel:nth-of-type(1){fill:#E5484D!important}
.radarAxisLabel:nth-of-type(2){fill:#E5484D!important}
.radarAxisLabel:nth-of-type(3){fill:#808A94!important}
.radarAxisLabel:nth-of-type(4){fill:#E5484D!important}
.radarAxisLabel:nth-of-type(5){fill:#2DA44E!important}
.radarAxisLabel:nth-of-type(6){fill:#808A94!important}
.radarAxisLabel:nth-of-type(7){fill:#E5484D!important}
themeVariables:
cScale0: "#62B5E5"
cScale1: "#62B5E5"
cScale2: "#62B5E5"
cScale3: "#62B5E5"
cScale4: "#F97316"
cScale5: "#F97316"
cScale6: "#F97316"
cScale7: "#F97316"
cScale8: "#F97316"
radar:
axisColor: "#9CA3AF"
graticuleColor: "#E5E7EB"
graticuleOpacity: 0
axisStrokeWidth: 1
curveOpacity: 0
---
radar-beta
axis b0["Basic: 57,828 tx/s ▼ 11%"]
axis b1["Basic Blocking: 707 tx/s ▼ 30%"]
axis b2["Basic JS: 4,925 tx/s ▬ +6%"]
axis b3["Basic Multi-Threaded: 57,896 tx/s ▼ 31%"]
axis b4["Historical Queries: 244,712 tx/s ▲ 21%"]
axis b5["Logging: 56,880 tx/s ▬ -6%"]
axis b6["Logging JWT: 8,712 tx/s ▼ 12%"]
curve stddev2_high["main EWMA + 2 std dev"]{112.03, 104.69, 111.34, 113.38, 122.06, 111.99, 111.84}
curve stddev1_high["main EWMA + 1 std dev"]{106.01, 102.35, 105.67, 106.69, 111.03, 106.00, 105.92}
curve stddev1_low["main EWMA - 1 std dev"]{93.99, 97.65, 94.33, 93.31, 88.97, 94.00, 94.08}
curve stddev2_low["main EWMA - 2 std dev"]{87.97, 95.31, 88.66, 86.62, 77.94, 88.01, 88.16}
curve branch_0["#8117 (4 runs earlier)"]{111.75, 72.17, 106.23, 126.78, 70.05, 110.54, 107.26}
curve branch_1["#8117 (3 runs earlier)"]{111.57, 78.82, 108.05, 127.96, 96.23, 108.31, 106.85}
curve branch_2["#8117 (2 runs earlier)"]{108.70, 72.72, 108.75, 85.07, 71.55, 111.68, 105.96}
curve branch_3["#8117 (1 run earlier)"]{90.64, 74.01, 106.41, 70.88, 119.26, 95.77, 108.60}
curve branch_4["#8117"]{88.88, 70.33, 105.59, 69.47, 121.47, 94.29, 87.85}
graticule polygon
max 149
min 48
ticks 0
showLegend false
Latency (ms)---
config:
radar:
width: 620
height: 620
marginTop: 90
marginRight: 220
marginBottom: 60
marginLeft: 220
axisLabelFactor: 1.12
curveTension: 0.08
theme: base
themeCSS: |
.radarCurve-0{fill:color-mix(in srgb, #62B5E5 13%, var(--color-canvas-default,var(--bgColor-default,#fff)))!important;fill-opacity:1!important;stroke:none!important;stroke-width:0!important}
.radarCurve-1{fill:color-mix(in srgb, #62B5E5 40%, var(--color-canvas-default,var(--bgColor-default,#fff)))!important;fill-opacity:1!important;stroke:none!important;stroke-width:0!important}
.radarCurve-2{fill:color-mix(in srgb, #62B5E5 13%, var(--color-canvas-default,var(--bgColor-default,#fff)))!important;fill-opacity:1!important;stroke:none!important;stroke-width:0!important}
.radarCurve-3{fill:var(--color-canvas-default,var(--bgColor-default,#fff))!important;fill-opacity:1!important;stroke:none!important;stroke-width:0!important}
.radarAxisLabel,.radarTitle{fill:var(--color-fg-default,var(--fgColor-default,#111827))!important;color:var(--color-fg-default,var(--fgColor-default,#111827))!important}
.radarCurve-4{stroke-width:1.5px!important;stroke-opacity:0.20!important}
.radarCurve-5{stroke-width:1.5px!important;stroke-opacity:0.30!important}
.radarCurve-6{stroke-width:1.5px!important;stroke-opacity:0.40!important}
.radarCurve-7{stroke-width:1.5px!important;stroke-opacity:0.50!important}
.radarCurve-8{stroke-width:1.75px!important;stroke-opacity:1.00!important}
.radarAxisLabel:nth-of-type(1){fill:#808A94!important}
.radarAxisLabel:nth-of-type(2){fill:#2DA44E!important}
.radarAxisLabel:nth-of-type(3){fill:#E5484D!important}
themeVariables:
cScale0: "#62B5E5"
cScale1: "#62B5E5"
cScale2: "#62B5E5"
cScale3: "#62B5E5"
cScale4: "#F97316"
cScale5: "#F97316"
cScale6: "#F97316"
cScale7: "#F97316"
cScale8: "#F97316"
radar:
axisColor: "#9CA3AF"
graticuleColor: "#E5E7EB"
graticuleOpacity: 0
axisStrokeWidth: 1
curveOpacity: 0
---
radar-beta
axis b0["Commit Latency 16ms: 4.64 ms ▬ -27%"]
axis b1["Commit Latency 1ms: 1.7 ms ▼ 6%"]
axis b2["Commit Latency 256ms: 210 ms ▲ 1%"]
curve stddev2_high["main EWMA + 2 std dev"]{163.07, 106.11, 101.47}
curve stddev1_high["main EWMA + 1 std dev"]{131.53, 103.06, 100.73}
curve stddev1_low["main EWMA - 1 std dev"]{68.47, 96.94, 99.27}
curve stddev2_low["main EWMA - 2 std dev"]{36.93, 93.89, 98.53}
curve branch_0["#8117 (4 runs earlier)"]{93.30, 61.87, 101.70}
curve branch_1["#8117 (3 runs earlier)"]{82.02, 64.78, 101.92}
curve branch_2["#8117 (2 runs earlier)"]{84.56, 95.66, 102.43}
curve branch_3["#8117 (1 run earlier)"]{100.36, 95.61, 100.43}
curve branch_4["#8117"]{73.15, 94.10, 101.02}
graticule polygon
max 208
ticks 0
showLegend false
Memory (bytes)---
config:
radar:
width: 620
height: 620
marginTop: 90
marginRight: 220
marginBottom: 60
marginLeft: 220
axisLabelFactor: 1.12
curveTension: 0.08
theme: base
themeCSS: |
.radarCurve-0{fill:color-mix(in srgb, #62B5E5 13%, var(--color-canvas-default,var(--bgColor-default,#fff)))!important;fill-opacity:1!important;stroke:none!important;stroke-width:0!important}
.radarCurve-1{fill:color-mix(in srgb, #62B5E5 40%, var(--color-canvas-default,var(--bgColor-default,#fff)))!important;fill-opacity:1!important;stroke:none!important;stroke-width:0!important}
.radarCurve-2{fill:color-mix(in srgb, #62B5E5 13%, var(--color-canvas-default,var(--bgColor-default,#fff)))!important;fill-opacity:1!important;stroke:none!important;stroke-width:0!important}
.radarCurve-3{fill:var(--color-canvas-default,var(--bgColor-default,#fff))!important;fill-opacity:1!important;stroke:none!important;stroke-width:0!important}
.radarAxisLabel,.radarTitle{fill:var(--color-fg-default,var(--fgColor-default,#111827))!important;color:var(--color-fg-default,var(--fgColor-default,#111827))!important}
.radarCurve-4{stroke-width:1.5px!important;stroke-opacity:0.20!important}
.radarCurve-5{stroke-width:1.5px!important;stroke-opacity:0.30!important}
.radarCurve-6{stroke-width:1.5px!important;stroke-opacity:0.40!important}
.radarCurve-7{stroke-width:1.5px!important;stroke-opacity:0.50!important}
.radarCurve-8{stroke-width:1.75px!important;stroke-opacity:1.00!important}
.radarAxisLabel:nth-of-type(1){fill:#808A94!important}
.radarAxisLabel:nth-of-type(2){fill:#2DA44E!important}
.radarAxisLabel:nth-of-type(3){fill:#2DA44E!important}
.radarAxisLabel:nth-of-type(4){fill:#E5484D!important}
.radarAxisLabel:nth-of-type(5){fill:#808A94!important}
.radarAxisLabel:nth-of-type(6){fill:#2DA44E!important}
themeVariables:
cScale0: "#62B5E5"
cScale1: "#62B5E5"
cScale2: "#62B5E5"
cScale3: "#62B5E5"
cScale4: "#F97316"
cScale5: "#F97316"
cScale6: "#F97316"
cScale7: "#F97316"
cScale8: "#F97316"
radar:
axisColor: "#9CA3AF"
graticuleColor: "#E5E7EB"
graticuleOpacity: 0
axisStrokeWidth: 1
curveOpacity: 0
---
radar-beta
axis b0["Basic: 88.2 MiB ▬ +2%"]
axis b1["Basic Blocking: 71 MiB ▼ 1%"]
axis b2["Basic JS: 67.7 MiB ▼ 5%"]
axis b3["Basic Multi-Threaded: 91.7 MiB ▲ 3%"]
axis b4["Logging: 75.3 MiB ▬ 0%"]
axis b5["Logging JWT: 66.1 MiB ▼ 4%"]
curve stddev2_high["main EWMA + 2 std dev"]{106.22, 100.66, 103.82, 102.09, 102.05, 101.54}
curve stddev1_high["main EWMA + 1 std dev"]{103.11, 100.33, 101.91, 101.05, 101.02, 100.77}
curve stddev1_low["main EWMA - 1 std dev"]{96.89, 99.67, 98.09, 98.95, 98.98, 99.23}
curve stddev2_low["main EWMA - 2 std dev"]{93.78, 99.34, 96.18, 97.91, 97.95, 98.46}
curve branch_0["#8117 (4 runs earlier)"]{102.92, 101.16, 97.53, 106.34, 100.72, 96.76}
curve branch_1["#8117 (3 runs earlier)"]{102.80, 101.11, 95.72, 106.92, 99.53, 96.50}
curve branch_2["#8117 (2 runs earlier)"]{99.69, 99.40, 94.15, 101.36, 98.54, 95.62}
curve branch_3["#8117 (1 run earlier)"]{98.93, 99.29, 94.66, 101.94, 99.52, 95.89}
curve branch_4["#8117"]{102.31, 99.35, 94.97, 102.52, 99.51, 96.16}
graticule polygon
max 112
min 89
ticks 0
showLegend false
Rate (ops/s)---
config:
radar:
width: 620
height: 620
marginTop: 90
marginRight: 220
marginBottom: 60
marginLeft: 220
axisLabelFactor: 1.12
curveTension: 0.08
theme: base
themeCSS: |
.radarCurve-0{fill:color-mix(in srgb, #62B5E5 13%, var(--color-canvas-default,var(--bgColor-default,#fff)))!important;fill-opacity:1!important;stroke:none!important;stroke-width:0!important}
.radarCurve-1{fill:color-mix(in srgb, #62B5E5 40%, var(--color-canvas-default,var(--bgColor-default,#fff)))!important;fill-opacity:1!important;stroke:none!important;stroke-width:0!important}
.radarCurve-2{fill:color-mix(in srgb, #62B5E5 13%, var(--color-canvas-default,var(--bgColor-default,#fff)))!important;fill-opacity:1!important;stroke:none!important;stroke-width:0!important}
.radarCurve-3{fill:var(--color-canvas-default,var(--bgColor-default,#fff))!important;fill-opacity:1!important;stroke:none!important;stroke-width:0!important}
.radarAxisLabel,.radarTitle{fill:var(--color-fg-default,var(--fgColor-default,#111827))!important;color:var(--color-fg-default,var(--fgColor-default,#111827))!important}
.radarCurve-4{stroke-width:1.5px!important;stroke-opacity:0.20!important}
.radarCurve-5{stroke-width:1.5px!important;stroke-opacity:0.30!important}
.radarCurve-6{stroke-width:1.5px!important;stroke-opacity:0.40!important}
.radarCurve-7{stroke-width:1.5px!important;stroke-opacity:0.50!important}
.radarCurve-8{stroke-width:1.75px!important;stroke-opacity:1.00!important}
.radarAxisLabel:nth-of-type(1){fill:#808A94!important}
.radarAxisLabel:nth-of-type(2){fill:#808A94!important}
.radarAxisLabel:nth-of-type(3){fill:#808A94!important}
.radarAxisLabel:nth-of-type(4){fill:#808A94!important}
.radarAxisLabel:nth-of-type(5){fill:#808A94!important}
.radarAxisLabel:nth-of-type(6){fill:#2DA44E!important}
themeVariables:
cScale0: "#62B5E5"
cScale1: "#62B5E5"
cScale2: "#62B5E5"
cScale3: "#62B5E5"
cScale4: "#F97316"
cScale5: "#F97316"
cScale6: "#F97316"
cScale7: "#F97316"
cScale8: "#F97316"
radar:
axisColor: "#9CA3AF"
graticuleColor: "#E5E7EB"
graticuleOpacity: 0
axisStrokeWidth: 1
curveOpacity: 0
---
radar-beta
axis b0["CHAMP get: 38,338,419 ops/s ▬ +4%"]
axis b1["CHAMP put: 5,558,496 ops/s ▬ +5%"]
axis b2["KV deserialisation: 1,615,770 ops/s ▬ +2%"]
axis b3["KV serialisation: 1,474,926 ops/s ▬ +4%"]
axis b4["KV snapshot deserialis...: 4,180 ops/s ▬ +3%"]
axis b5["KV snapshot serialisation: 4,739 ops/s ▲ 8%"]
curve stddev2_high["main EWMA + 2 std dev"]{110.07, 111.17, 109.74, 109.66, 110.06, 113.87}
curve stddev1_high["main EWMA + 1 std dev"]{105.04, 105.58, 104.87, 104.83, 105.03, 106.93}
curve stddev1_low["main EWMA - 1 std dev"]{94.96, 94.42, 95.13, 95.17, 94.97, 93.07}
curve stddev2_low["main EWMA - 2 std dev"]{89.93, 88.83, 90.26, 90.34, 89.94, 86.13}
curve branch_0["#8117 (4 runs earlier)"]{102.60, 103.20, 104.56, 101.59, 102.15, 101.77}
curve branch_1["#8117 (3 runs earlier)"]{102.87, 103.29, 103.37, 102.20, 102.72, 114.47}
curve branch_2["#8117 (2 runs earlier)"]{103.00, 103.03, 101.53, 101.46, 101.93, 106.19}
curve branch_3["#8117 (1 run earlier)"]{102.41, 102.97, 102.20, 103.23, 100.19, 97.10}
curve branch_4["#8117"]{103.68, 104.66, 102.20, 103.85, 102.60, 107.77}
graticule polygon
max 125
min 76
ticks 0
showLegend false
|
…ection_manager
…/CCF into rpc_connection_manager
|
Had an agent look at why performance seems affected, particularly on the blocking commit benchmark. I found the Nagle regression right away, which seemed worth fixing. The rest is more debatable, some of the locking changes sound like easy wins, but need testing, but the big one is probably not having handshake handling delaying permanent regime requests for other sessions, and that requires non trivial re-design I think.
|
Summary
This PR replaces the old split RPC networking path with an OpenSSL-native connection layer. Previously, socket handling lived in host-side libuv code while TLS and protocol sessions were driven through enclave-side ringbuffer messages and memory BIOs. With CCF now running as a single process, that split is no longer useful, so RPC sockets, TLS, protocol session creation, and per-interface policy now live behind a single RPC connection manager.
TLS now terminates at the connection layer. Protocol sessions receive plaintext and write responses through a
SessionWriter, so HTTP, HTTP/2, and custom protocols no longer own TLS state directly. This removes the RPC ringbuffer message path, the memory-BIO TLS session layer, and the old libuv RPC connection containers.The new transport uses non-blocking sockets bound directly to OpenSSL, and splits the work in two. The existing host libuv loop owns the server's own state - accepting connections,
uv_poll_tregistration, theSSL_CTX, idle connection cleanup viauv_timer_t, and closing file descriptors - and performs noSSLoperations itself. EverySSLoperation for a connection instead runs on that connection's ownOrderedTasksqueue, keeping handshakes and bulk encryption off the loop thread. The loop only schedules a pass over a connection, driven either by file descriptor readiness or by a cross-thread request (a queued write, a close, or a certificate update) marshalled throughuv_async_t. A connection is serviced by at most one pass at a time. Per-interface behavior such as certificates, session caps, metrics, HTTP parser settings, and custom protocol dispatch is centralized inRPCConnectionManager.Session caps are applied when a connection is accepted, before any TLS state exists, rather than when its first request arrives.
max_open_sessions_hardis documented as a bound on connections, and counting at first-request time missed any client that completed the TCP and TLS handshakes and then went silent while still holding a file descriptor and TLS state.Node outbound requests are outside this transport and use libcurl.
UDP remains as a small datagram transport driven by
uv_poll_t. The temporary QUIC/UDP echo behavior is stateless, so it consumes no session and no interface capacity, while custom UDP protocols are routed to per-peer sessions. Native QUIC is still future work and requires OpenSSL 3.5 or later.Structural Breakdown
OpenSSLServeris the low-level inbound connection transport. It owns the listening and accepted socket file descriptors,SSLobjects,uv_poll_thandles, read/write buffers, handshake state, graceful-close state, certificate reload requests, and idle-timeout sweeping.OpenSSLSessionManagerbridges transport connections toccf::Session. It lazily creates sessions for inbound connections, forwards plaintext bytes into sessions, implementsSessionWriter, and reports connection closure back to the owner.RPCConnectionManageris the higher-level RPC owner. It replaces the old RPC session container and owns one transport bridge per TCP interface, plus UDP interface state. It applies per-interface admission and caps, certificates, parser settings, application protocol selection, session metrics, custom protocol routing, and UDP peer demultiplexing.SessionWriter,Session, andPlaintextSessionform the new session boundary. Sessions no longer encrypt or decrypt; they parse plaintext and emit plaintext responses to their writer. HTTP/1 and HTTP/2 sessions now use this boundary.CustomProtocolSubsystemInterfacenow creates sessions from(ConnID, SessionWriter&)rather than a TLS context. This matches the new layering: custom protocols see plaintext and write through the transport-neutral writer.DatagramServeris the UDP socket transport. It uses auv_poll_thandle on the existing libuv loop.RPCConnectionManagerechoes datagrams directly for the temporary QUIC behavior, and maps UDP peers to sessions for custom datagram protocols.Startup wiring moved accordingly. The enclave creates and owns the RPC manager, binds RPC interfaces, resolves actual bound addresses including ephemeral ports, and reports those addresses back through the enclave entry point so the host can write the RPC addresses file.
The removed files are the old RPC transport stack:
RPCSessions,TLSSession, host RPC connections, legacy UDP plumbing, the old QUIC session, and the TCP/UDP ringbuffer message types that were specific to the split RPC path. Ledger, consensus, and node-to-node uses of ringbuffer and libuv are not part of this change.