Skip to content

cl/phase1/network/services: extract shared pending-job queue for the Gloas services#22177

Draft
yperbasis wants to merge 3 commits into
mainfrom
yperbasis/cl-pending-queue
Draft

cl/phase1/network/services: extract shared pending-job queue for the Gloas services#22177
yperbasis wants to merge 3 commits into
mainfrom
yperbasis/cl-pending-queue

Conversation

@yperbasis

Copy link
Copy Markdown
Member

The three Gloas gossip services (execution_payload, payload_attestation, execution_payload_bid) each carried a token-identical pending-job subsystem (~100 lines each): sync.Map keyed by root, capacity cap with increment-first accounting, expiry, cond+ticker wakeups, and a retry loop. Every new topic has been copying it wholesale.

They now share one pendingJobQueue[K, M]. Readiness/validation logic stays in per-service callbacks — the tryProcess(ctx, key, msg) (process func(), done bool) two-phase contract exists specifically to preserve the originals' delete-before-process ordering, so a concurrent same-key re-enqueue is not lost. Per-service capacity/expiry/keying/drop rules are parameters, enumerated in the commit body. Deliberate normalizations (all unobservable or full-queue-only): envelope key hashing now precedes the capacity check; drop-path trace logs precede the map delete.

One audit-note correction along the way: the suspected bid-service count-ordering delta turned out to be stale — #21655 already normalized it; all three enqueues are identical today. The three looser ticker-only services (block, blob_sidecar, data_column_sidecar) are deliberately NOT converted — they differ in collision policy (Store-replace), retry-on-error semantics, and removal ordering; forcing them under the same type would mean knobs for each.

Also: isLocalValidatorProposer was token-identical (docstring included) in aggregate_and_proof and sync_contribution — extracted into an embedded localProposerChecker, per-service LRU instances unchanged.

Safety net: pre-refactor pinning tests for the bid queue (expiry, stale-slot drop, end-to-end loop drain), green on the old code including -race -count=3, unchanged after; existing whitebox tests mechanically re-pointed at the queue internals with assertions untouched. Consensus [IGNORE]/[REJECT] semantics untouched.

Net −84 production lines and each future gossip topic stops copying the subsystem. Verified: go test ./cl/phase1/network/... (+-race on services), scoped golangci-lint clean (×2), make erigon.

Part of a dedup series; siblings #22165#22176.

yperbasis added 3 commits July 2, 2026 22:02
…ehavior

The bid service's pending-queue drop paths (expiry, stale slot) and the
background loop's enqueue-to-drain flow had no test coverage, unlike the
sibling execution payload and payload attestation services. Pin them
before consolidating the three token-identical queue implementations.

The loop test populates the forkchoice mock's plain maps before the
service constructor spawns the loop goroutine, so it is race-free; the
preferences arrive later via the thread-safe LRU pool.
…gossip services

The execution payload, payload attestation, and execution payload bid
services each carried a token-identical pending-job queue (sync.Map +
atomic count + cond-signalled polling loop). Consolidate it into an
unexported generic pendingJobQueue[K, M]; readiness checks stay in
per-service tryProcess callbacks since they differ on purpose per topic
(block arrived / block arrived at current slot / preferences matched).

Per-service deltas, kept as queue parameters or callback logic:
- capacity: envelopes 1024, attestations 2048, bids 1024
- expiry: envelopes and attestations 30s, bids 12s (1 slot); tick 100ms everywhere
- keys: (blockRoot, envelopeHash) / (blockRoot, validatorIndex) / (builderIndex, slot)
- drop rules: attestations drop on stale slot; bids drop on stale
  slot and on non-dependency preference-matching errors, and keep
  waiting on errBidDependencyUnavailable
- the envelope service hashes the envelope to build its key and drops
  with a warning if hashing fails

The audit note that the bid service incremented pendingCount after
LoadOrStore is stale: that ordering existed at Gloas introduction
(f3ad70f) but was fixed to increment-first in #21655 (a2f483a);
all three enqueues are token-identical today and the queue keeps that
increment-first accounting.

The tryProcess contract returns (process func(), done bool) so the queue
removes a finished job before running its processing, exactly as the
originals deleted before calling ProcessMessage/validateAndStoreBid;
a concurrent re-enqueue of the same key during processing is therefore
still stored rather than lost.

Deliberate normalizations, none affecting accounting or validation:
- envelope enqueue computes HashSSZ before the capacity check instead
  of after the capacity reservation; visible only when the queue is
  full (hash cost, and the hash-failure warning now logs even then)
- drop-path trace logs now precede the map removal instead of following
  it (same goroutine, not observable)
- the bid callback receives the loop context and ignores it, where
  processPendingBids previously took no context

The ticker-only schedulers in block, blob sidecar, and data column
sidecar services are intentionally not converted: they have no cond or
capacity accounting, use Store (replace) rather than LoadOrStore (drop)
on key collision, retry failed jobs until expiry instead of removing
them before processing, and the data column variant increments its
count after LoadOrStore and logs on capacity drops. Covering those
would need option knobs for enqueue collision policy, error retry, and
removal ordering, defeating the consolidation.
…lProposerChecker

The aggregate-and-proof and sync-contribution services carried
token-identical isLocalValidatorProposer methods (docstring included)
along with duplicated proposerIndicesCache fields and LRU setup. Move
them to a localProposerChecker type embedded in both services, keeping
call sites unchanged via method promotion. Each service still owns its
own cache instance, as before.
@yperbasis yperbasis requested a review from Copilot July 2, 2026 20:17
@yperbasis yperbasis added tech debt reduction Glamsterdam https://eips.ethereum.org/EIPS/eip-7773 Caplin Caplin: Consensus Layer, Beacon API labels Jul 2, 2026
@yperbasis yperbasis marked this pull request as draft July 2, 2026 20:17

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR reduces duplicated gossip-service logic by extracting (1) a shared pending-job retry/expiry queue used by the Gloas gossip services and (2) a shared “local proposer” checker used by two services, keeping per-topic validation/processing decisions in service-specific callbacks.

Changes:

  • Introduces a generic pendingJobQueue[K, M] with capacity limiting, expiry, cond/ticker wakeups, and a two-phase tryProcess contract to preserve delete-before-process ordering.
  • Refactors execution_payload, payload_attestation, and execution_payload_bid services to use the shared pending queue; updates corresponding whitebox tests to target the new internals.
  • Extracts the previously duplicated isLocalValidatorProposer logic into an embedded localProposerChecker, used by aggregate_and_proof and sync_contribution.

Reviewed changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated no comments.

Show a summary per file
File Description
cl/phase1/network/services/pending_job_queue.go Adds shared generic pending queue with capacity/expiry/retry loop and two-phase processing contract.
cl/phase1/network/services/execution_payload_service.go Switches execution payload service pending-envelope handling to the shared queue.
cl/phase1/network/services/execution_payload_service_test.go Re-points pending-envelope tests to the shared queue internals and helpers.
cl/phase1/network/services/payload_attestation_service.go Switches payload attestation pending-block handling to the shared queue and adds tryProcess callback.
cl/phase1/network/services/payload_attestation_service_test.go Re-points pending-attestation tests to the shared queue internals.
cl/phase1/network/services/execution_payload_bid_service.go Switches pending-bid subsystem to the shared queue and moves retry logic into tryProcess.
cl/phase1/network/services/execution_payload_bid_service_test.go Updates tests to shared queue internals and adds end-to-end loop coverage for queued bid drain.
cl/phase1/network/services/local_proposer_checker.go Adds extracted localProposerChecker (LRU-backed proposer cache + lookahead logic).
cl/phase1/network/services/aggregate_and_proof_service.go Uses embedded localProposerChecker instead of local duplicated cache+method.
cl/phase1/network/services/sync_contribution_service.go Uses embedded localProposerChecker instead of local duplicated cache+method.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Caplin Caplin: Consensus Layer, Beacon API Glamsterdam https://eips.ethereum.org/EIPS/eip-7773 tech debt reduction

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants