Skip to content

Cleanups from reviewing #178: single-pass CBOR decode, TTL default, drop metric - #180

Merged
MastaP merged 4 commits into
service-timefrom
service-time-cleanups
Aug 25, 2026
Merged

Cleanups from reviewing #178: single-pass CBOR decode, TTL default, drop metric#180
MastaP merged 4 commits into
service-timefrom
service-time-cleanups

Conversation

@MastaP

@MastaP MastaP commented Aug 25, 2026

Copy link
Copy Markdown
Member

Stacked on #178 — base is service-time, not main. Merge #178 first, or merge this into it.

Four independent cleanups from reviewing #178. Each commit stands alone; take, drop, or squash whichever you like. No behaviour change except the drop-log level.

1. CertificationData.UnmarshalCBOR decoded twice (c819645)

UnmarshalTagged probed the tag, element count and version, then UnmarshalTaggedValue decoded the same bytes again. The probe is redundant: the toarray decode rejects a wrong tag and a wrong element count on its own, so only the version check needs the decoded value.

This is the per-request path (internal/gateway/handlers.go:45), and the probe was roughly half the decode cost:

before  5701-5781 ns/op  1288 B/op  32 allocs/op
after   3010-3098 ns/op   576 B/op  13 allocs/op

Verified by differential test against the old implementation across 14 inputs — wrong tag, 5 and 7 fields, versions 0/1/3, untagged array, tagged non-array, truncated, garbage, empty, nil — asserting identical accept/reject decisions and identical decoded values. Error text differs; nothing matches on it. The interesting cases are kept as pkg/api/certification_data_decode_test.go so the validation surface cannot be weakened silently later.

ValidateCoreDeterministic is unaffected: it runs on the whole payload in UnmarshalCertificationRequestCBOR before the nested decode.

2. One source for the default request TTL (5665511)

The 1h default was written twice — as the DEFAULT_REQUEST_TTL env default (config.go:393) and as the zero-value fallback in RequestTTL() (config.go:126-131). They could drift, and which one applied depended on whether the config came from the environment or was constructed in code. Both now derive from DefaultRequestTTLFallback.

Validate is unchanged and still accepts 0 as the "unset, use the default" signal for programmatic configs.

3. Name the leaf builder for the write it performs (ffb3e31)

commitmentLeafInput reads as a pure builder but assigns commitment.ReferenceTime (leaf_add.go:50). The adjacent models.CertificationRequest.LeafValue is genuinely pure — TestCertificationRequestLeafValue_DoesNotMutateReferenceTime asserts exactly that — so two near-identically-named functions sat side by side with opposite contracts.

Renamed to materializeCommitmentLeaf, with the write called out in the doc comment. Test names updated to match.

4. Count commitments dropped before reaching a block (93dab79)

An expired request is acked out of the queue after the service already answered SUCCESS, leaving no aggregator record and no durable trace. It was logged at Debug with no counter, so a node could discard an arbitrary volume of acknowledged work with nothing visible on a dashboard — and a backlog exceeding DEFAULT_REQUEST_TTL drops requests in bulk.

  • Adds aggregator_commitments_dropped_total{reason}, covering the pre-existing duplicate and rejected drop paths as well, neither of which was instrumented either.
  • Raises the expiry log from Debug to Warn, matching the neighbouring rejected-leaf path, and logs effectiveTimeout alongside the requester's own expiresAt.
  • Adds a Grafana panel, since these metrics already feed deploy/grafana/dashboards/aggregator.json.

Label values are resolved once at init rather than per call: these increments happen while roundMutex is held (round_manager.go:896-922), so they should not pay for a CounterVec label lookup per dropped commitment.

The counter counts drop events, not distinct commitments — if the queue ack fails the commitment is retried and counted again. The Help text says so.

Verification

go build and go vet clean on Go 1.26. Passing: pkg/..., internal/config, internal/models, internal/gateway, internal/service, internal/ha.

internal/round shares the base branch's pre-existing container flakiness on the machine I ran this on — every failure on both sides is a MongoDB testcontainer i/o timeout during index creation, not an assertion. Compared against unmodified service-time:

--- FAIL count all container timeouts fails here but not on base
service-time (base) 20 20/20
this branch 18 18/18 0

The failing set on this branch is a strict subset of the base's, so nothing here introduces a regression. CI on less contended hardware is the real check.

MastaP added 4 commits August 24, 2026 20:53
UnmarshalCBOR ran UnmarshalTagged to check the tag and element count, then
UnmarshalTaggedValue to decode the same bytes again. The probe pass was
redundant: the toarray decode rejects a wrong tag and a wrong element count
on its own, so only the version check needs the decoded value.

This is the per-request path (internal/gateway/handlers.go decodes every
certification_request), and the probe was roughly half of the decode cost:

  before  5701-5781 ns/op  1288 B/op  32 allocs/op
  after   3010-3098 ns/op   576 B/op  13 allocs/op
The 1h default was written twice: once as the DEFAULT_REQUEST_TTL environment
default and once as the zero-value fallback in RequestTTL(). The two could
drift, and which one applied depended on whether the config came from the
environment or was built in code.

Both now derive from DefaultRequestTTLFallback. Validate still accepts 0,
which remains the "unset, use the default" signal for programmatic configs.
commitmentLeafInput reads as a pure builder but assigns
commitment.ReferenceTime. The adjacent models.CertificationRequest.LeafValue
is genuinely pure, and the PR's own test asserts that -- so the two sat side
by side with opposite contracts and near-identical names.

Renamed to materializeCommitmentLeaf and made the write explicit in the doc
comment. No behaviour change.
An expired request is acked out of the queue after the service already
answered SUCCESS, leaving no aggregator record and no durable trace. It was
logged at Debug with no counter, so a node could discard an arbitrary volume
of acknowledged work with nothing visible on a dashboard -- and a backlog
exceeding DEFAULT_REQUEST_TTL drops requests in bulk.

Adds aggregator_commitments_dropped_total{reason}, covering the pre-existing
duplicate and rejected drop paths as well, neither of which was instrumented
either. Raises the expiry log from Debug to Warn to match the neighbouring
rejected-leaf path, and includes effectiveTimeout so the service-assigned
deadline is visible alongside the requester's own.
@MastaP
MastaP merged commit 0c7f70b into main Aug 25, 2026
3 checks passed
@MastaP
MastaP deleted the service-time-cleanups branch August 25, 2026 09:52
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