Skip to content

refactor(service): centralize per-round DKG mutation locking - #95

Open
0xHansLee wants to merge 2 commits into
mainfrom
dkg/refactor-mutation-locking
Open

refactor(service): centralize per-round DKG mutation locking#95
0xHansLee wants to merge 2 commits into
mainfrom
dkg/refactor-mutation-locking

Conversation

@0xHansLee

@0xHansLee 0xHansLee commented Jul 24, 2026

Copy link
Copy Markdown
Collaborator

Problem

getDKGMutationMu(round) (M) was taken at scattered sites with manual Lock/Unlock and ad-hoc closures across the six DKG-mutating critical sections — easy to misuse or forget to release. Separately, ProcessDeals resolved the cached *dkg.DistKeyGenerator before taking M, so a timed-out handler and its retry could capture the same pointer; when the winner evicted it on a persist failure (#87), the loser processed a stale, evicted instance.

Fix

  • Add withRoundMutation(round, fn) and route all six mutating critical sections (ProcessDeals/ProcessResponses/ProcessJustification/FinalizeDKG/GenerateDeals and the resharing from-round share read) through it, so Unlock cannot leak on an early return or panic. M stays a leaf lock.
  • ProcessDeals warms the generator OUTSIDE M (a cache-miss build does light-client IO), then under M takes it via a pure cache-get and processes; on an eviction between warm and lock it re-warms outside M and retries, bounded by maxResolveDealsAttempts, so the loser resolves a fresh generator. Returns Unavailable if exhausted.

Test coverage

go test -race ./service/...

Symbol Coverage
withRoundMutation 100%
applyDealsUnderLock 92.9%
resolveAndApplyDeals, attemptDeals 75%
warmGenerator 60% (non-resharing path directly tested)

issue: #94

Route every DKG-mutating RPC handler through a single withRoundMutation
helper instead of scattered getDKGMutationMu + manual Lock/Unlock and
ad-hoc closures. The helper defers Unlock so the lock cannot leak on an
early return or panic. No behavior change.
…tation lock (#94)

Resolving the generator outside getDKGMutationMu(round) let a timed-out handler
and its retry share the same cached pointer; the winner could evict it on a
persist failure, leaving the loser on a stale, evicted instance.

Process and persist now run under the lock as one critical section. The generator
is warmed OUTSIDE the lock so a cache-miss build -- for resharing an unbounded
light-client query -- never runs while the lock is held; under the lock it is
taken via a pure cache-get, re-warming outside the lock and retrying if the round
was evicted concurrently (bounded). The loser re-resolves a fresh generator.
@jinn-agent

jinn-agent Bot commented Jul 24, 2026

Copy link
Copy Markdown

The refactor correctly centralises per-round locking via withRoundMutation and the warm-outside-lock pattern for ProcessDeals soundly addresses issue #94. Lock-order invariants (initDKGMu / reshareNextMu → M(round) → stateMu) are maintained across all call sites, and the deferred unlock in withRoundMutation prevents lock poisoning on panic.

Two narrower observations below; neither is a showstopper but both are worth a look before merge.


Review iteration 1 · Commit 1bbf65b · 2026-07-24T05:57:34Z

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.

3 participants