From 248f01dbec363f81f929cf999de1bcfd81483d2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ste=CC=81phane=20Duchesneau?= Date: Fri, 10 Jul 2026 16:34:51 -0400 Subject: [PATCH] Log tier1 client disconnects as Canceled, not Unknown toGrpcTier1Error returned a connect error for the context-canceled case, while every other branch returns a gRPC status error. status.Code() on a connect error resolves to Unknown, so client disconnects were logged as a WARN ("Blocks request completed with error") and the gRPC middleware reported the call at ERROR with code Unknown. Return codes.Canceled so the disconnect is logged quietly at Debug/Info. Co-Authored-By: Claude Opus 4.8 --- docs/release-notes/change-log.md | 2 ++ service/error.go | 8 ++++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/docs/release-notes/change-log.md b/docs/release-notes/change-log.md index 733aff84e..f9211b254 100644 --- a/docs/release-notes/change-log.md +++ b/docs/release-notes/change-log.md @@ -29,6 +29,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ### Fixed +- Server: client disconnects (`context canceled`) on a tier1 Blocks stream are no longer logged as `WARNING`/`ERROR`. `toGrpcTier1Error` returned a `connect` error for the canceled case (unlike every other branch, which returns a gRPC `status` error), so the caller's `status.Code()` resolved to `Unknown` — logging the request completion as a WARN and the gRPC middleware call as an ERROR with `code Unknown`. It now returns `codes.Canceled`, so the disconnect is logged at Debug/Info as intended. + - Server: bumped `dstore`, which now sends S3 request checksums only when the target requires them, fixing uploads/downloads against S3-compatible stores that reject the newer default checksum headers. - Server: the quicksave block count now counts settled blocks (normal or last-partial) instead of skipping partials entirely, so quicksave arms correctly on flash-block chains. The minimum sent-block threshold before a quicksave triggers was raised from 25 to 50. - Server: `tier1` calls to hosted foundational stores now forward the `x-organization-id` identity header (alongside the existing trusted headers), so a store's internal trust-based listener can authorize the request without an end-user JWT. This fixes `Unauthenticated: required authorization token not found` errors when reading from hosted foundational stores resolved via the control-plane registry. diff --git a/service/error.go b/service/error.go index 51520e470..bc9e23f79 100644 --- a/service/error.go +++ b/service/error.go @@ -132,10 +132,14 @@ func toGrpcTier1Error(ctx context.Context, err error) error { if contextCause := context.Cause(ctx); contextCause != nil { err = contextCause // unwrap errors in canceled contexts if errors.Is(err, context.Canceled) { - return connect.NewError(connect.CodeCanceled, err) + // Return a gRPC status error (like every other branch below) so the caller's + // status.Code() resolves to codes.Canceled and logs at Debug. A connect error + // here resolves to codes.Unknown, wrongly logging a client disconnect as WARN + // (and the gRPC middleware then reports "code Unknown" at ERROR). + return status.Error(codes.Canceled, err.Error()) } } else { - return connect.NewError(connect.CodeCanceled, err) + return status.Error(codes.Canceled, err.Error()) } }