Skip to content

Support grpc requests in NativeCall - #354

Merged
KirillPamPam merged 3 commits into
mainfrom
grpc-nativecall
Aug 31, 2026
Merged

Support grpc requests in NativeCall#354
KirillPamPam merged 3 commits into
mainfrom
grpc-nativecall

Conversation

@KirillPamPam

@KirillPamPam KirillPamPam commented Aug 28, 2026

Copy link
Copy Markdown
Collaborator

NativeCall: serve native gRPC calls via grpc_data items

Summary

The dshackle Blockchain.NativeCall stream now accepts native unary gRPC calls (Sui
sui.rpc.v2.*) alongside JSON-RPC and REST items. A NativeCallItem with the new
grpc_data oneof branch is routed through the same execution flow as the gRPC chain
ingress and answered with one buffered reply item carrying the response message bytes,
the upstream's headers and trailers, and — on failure — a canonical gRPC status.

Bytes-only throughout: request and response messages are never parsed.

Depends on github.com/drpcorg/public v1.1.0 (bumped here), which added:

message NativeCallItem { oneof data { ...; GrpcData grpc_data = 9; } }
message GrpcData { bytes payload = 1; repeated KeyValue metadata = 2; }
message NativeCallReplyItem { ...; repeated KeyValue response_trailers = 16; }

Contract for a gRPC item

Requestmethod is the full gRPC method name (/sui.rpc.v2.LedgerService/GetObject),
grpc_data.payload the serialized request message (no 5-byte wire frame prefix),
grpc_data.metadata the call metadata to forward. Credential metadata (x-nodecore-key,
x-nodecore-token, authorization) is stripped before forwarding, exactly like the ingress does.
chunk_size is ignored: a unary gRPC reply is always one unchunked message.

Successpayload = response message bytes verbatim, signature over those bytes when
a nonce is given, response_headers / response_trailers = upstream metadata.

Error — existing fields, gRPC vocabulary:

field value
item_error_code canonical gRPC code (0–16) — never nodecore's internal ResponseError.Code
error_message the status message
error_as_is serialized google.rpc.Status when the upstream attached typed details; empty otherwise
error_data always empty for gRPC items
response_headers / response_trailers present on error items too (e.g. RESOURCE_EXHAUSTED rate-limit hints ride in trailers)

An upstream status is replayed verbatim; nodecore's own errors (no upstreams, rate limit,
unknown method, timeout, auth, …) are mapped onto the 17-code model in nodecore, so the
client needs no mapping table of its own: status.FromProto(error_as_is) when set, else
status.New(item_error_code, error_message).

Rejected up front (no upstream involved): unknown method → UNIMPLEMENTED; a server-stream
spec method → INVALID_ARGUMENT pointing at NativeSubscribe; missing grpc_data
INVALID_ARGUMENT.

Changes

  • protocol.GrpcStatusOf(*ResponseError) *status.Status — the nodecore-error → gRPC
    status mapping, moved out of grpc_ingress/chain_ingress.go (grpcStatusFromResponseError)
    so the ingress and the emerald server share one table. The ingress behaviour is unchanged.
  • internal/server/emerald
    • native_call_adapter.go is split per API kind: native_call_adapter_jsonrpc.go,
      native_call_adapter_rest.go, native_call_adapter_grpc.go (new adapter); the shared
      file keeps the nativeCallAdapter interface, adapterFor and the reply machinery.
    • nativeCallAdapter gains ErrorItem(requestID, err), so pre-dispatch failures (invalid
      payload, selector conflicts, signature requested without a signer) render in the item's
      own error vocabulary. The adapter is now chosen before the signing check.
    • sendReply takes an errorItemRenderer — nodecore codes + error_data for JSON-RPC/REST,
      canonical status for gRPC — and reads response metadata via the
      HasResponseHeaders/HasResponseTrailers capabilities instead of a
      *GenericUpstreamResponse type assertion, so metadata on *ReplyError (retryable upstream
      errors) is no longer dropped. Trailers are stamped on success, error and first stream chunk.
    • New replyMeta (request id, upstream id/version, finalization, headers, trailers) built
      once per response and stamped onto items; streamNativeCallBody goes from 11 positional
      params to (stream, reader, mode, hint, meta).
  • go.modgithub.com/drpcorg/public v1.0.0 → v1.1.0.

JSON-RPC and REST items are unaffected apart from the (previously empty) trailers field and
the *ReplyError headers that now ride along.

Out of scope (follow-ups)

  • NativeSubscribe for gRPC server-stream methods (still JSON-RPC only).
  • Quorum for gRPC items — quorum parameters are currently read only from the HTTP query
    string, so quorum is unreachable via NativeCall for any item kind today.

Testing

  • internal/protocol/grpc_status_of_test.go — verbatim replay with/without details, every
    nodecore code mapping, nil input.
  • internal/server/emerald/grpc_blockchain_test.go — gRPC item build (method, body, sanitized
    metadata, chunk_size ignored), unknown / server-stream / missing-data / signing-unavailable
    items, success item with headers + trailers, upstream status with details round-tripping
    through error_as_is, status without details, nodecore error → UNAVAILABLE, trailers on
    *ReplyError, JSON-RPC regression (nodecore codes kept, trailers empty).
  • go test -race ./... green, golangci-lint clean.

Comment thread internal/server/emerald/grpc_blockchain.go Outdated
Comment thread internal/server/emerald/grpc_blockchain.go
Comment thread internal/server/emerald/native_call_adapter_grpc.go Outdated
Comment thread internal/server/emerald/native_call_adapter.go
Comment thread internal/server/emerald/native_call_adapter.go Outdated
Comment thread internal/server/emerald/native_call_adapter.go Outdated
Comment thread internal/server/emerald/native_call_adapter_grpc.go Outdated
@KirillPamPam
KirillPamPam merged commit d12f32f into main Aug 31, 2026
5 checks passed
@KirillPamPam
KirillPamPam deleted the grpc-nativecall branch August 31, 2026 10:19
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.

2 participants