Skip to content

fix(reservations): review-round fixes for reservation ABI, watcher plumbing, and lookback bounds - #4324

Open
piotr-roslaniec wants to merge 27 commits into
reservations-epicfrom
fix/reservations-review-findings
Open

piotr-roslaniec wants to merge 27 commits into
reservations-epicfrom
fix/reservations-review-findings

Conversation

@piotr-roslaniec

@piotr-roslaniec piotr-roslaniec commented Sep 9, 2026

Copy link
Copy Markdown
Collaborator

Fixes surfaced during multi-agent review of the reservations stack (P0-P3), landed as one branch off reservations-epic.

Summary

  • ABI/router mismatches (P0): reservation key mapping, RequestNonce field wiring.
  • Stranding/stale-deposit watcher fixes: watcher-death alerting via panic-recover wrappers, error-message wording enhanced to describe stale-deposit (not stranding) recovery, per-operator notification staggering to avoid collisions on permissionless first-notify attempts, a per-tick RPC cap with rotating cursor on the action-timeout watcher, and cross-watcher coordination (passed as a constructor parameter to NewReservationActionTimeoutWatcher, not a SetStrandingWatcher setter) so a reservation restored to Active by an action-timeout notify is scheduled for a stranding recheck on the next poll tick (not immediately -- the notify transaction has only just been submitted, not mined).
  • New CI job (reservation-router-vendored-fallback-verify): compiles the real tbtc-v2 Solidity contracts from the pinned upstream ref and diffs the generated ABI against the vendored fallback, catching silent drift.
  • New chain method WalletTerminationCause: infers which pre-termination event (MovingFundsTimedOut / MovedFundsSweepTimedOut / FraudChallengeDefeatTimedOut) preceded a wallet's termination.
  • Config: split shared ReservationsConfig into plainly-named flags (Tbtc.ReservationsEnabled in the client, Maintainer.Spv.ReservationProofsEnabled in the maintainer). Both processes independently wire the reservation watchers (stranding / stale-deposit / action-timeout) under their own flag; this is intentional - the watchers are permissionless, network-wide duties, not leader-election duties, so redundant wiring in a colocated deployment is functionally redundant (a Notify* call against an already-settled reservation is a chain-level no-op), though two independently running watchers can still race before settlement.
  • Precision: WalletReservations now derives its result from NewWalletRegistered/reservation events (an event-derivation switch away from the prior router view call) and bounds reservation-acceptance and reanchor event queries by the wallet's own registration block (resolved via a bounded backward walk, memoized per wallet), reducing scanned range vs. the prior genesis-to-tip fallback.
  • Determinism: resolveCustodiedReservationKeys sorts candidate keys before the per-key lookup loop so the partial-failure path reproduces across runs.
  • Reservation acceptance: pkg/tbtcpg gained a bounded concurrent worker pool that fetches Bitcoin transaction bodies/confirmations for reservation-acceptance proof candidates. Separately, pkg/maintainer/spv's proof loop gained RBF multi-candidate proof selection and a wallet transaction cache -- these are two distinct changes in two different packages, not one combined feature.
  • pkg/maintainer/spv: the stale-deposit poller was restructured into a two-tier pending/parked model (ReservationStaleDepositWatcher) with its own Run/pollTick and a periodic parkedReconcile pass, replacing the prior flat polling loop.
  • pkg/tbtc: Marshal and Unmarshal validation tightened to reject non-positive-but-nonzero byte encodings on the reservation fields (both directions, not Marshal only); the deposit-sweep reserved-deposit sweep-protection gate now fails closed (hard error) on an unrecoverable proof loop error instead of silently degrading, and reads the reservation-activation predicate through the newly-exported ReservationsActivationBlock, which -- unlike the removed reservationsActivationBlock it replaces -- returns math.MaxUint64 (never active) for every network except ethereum.Developer until an explicit entry is added to reservationsActivationBlocks, including for ethereum.Unknown. reservationParametersFetchRetries was removed and a new reservationsActive parameter threaded through five exported functions to carry this activation decision explicitly.
  • API changes: SubmitReservationProof was split into two methods (dropping the proofType/mainUtxo parameters each no longer needs), and NotifyReservationActionTimeout no longer takes walletMembersIDs.
  • Test coverage: TestResolveCustodiedReservationKeys added, covering dedup, custody filter, reanchored-in, sorted output, and the lookup-error short-circuit (5 subtests); mutation-verified the RequestNonce mapping actually fails when broken.
  • Unrelated go.sum churn (~300 lines): module-graph hash refresh only (go mod tidy-style /go.mod h1: entries), no go.mod change and no new content hashes -- verified, not a dependency bump.

Review fixes (round 1, prior commits on this branch)

Applied all confirmed P1/P2/P3 findings from the first multi-agent review pass (see agent-docs/reviews/pr-4324/report.md), including: the misconfiguration self-check's RPC-failure/zero-activity conflation, the action-timeout stranding-recheck timing race, FindDeposits' hardcoded reservationsActive=false blocking moving-funds proposals, the registration-block bounding gap, the leader/follower activation-height mismatch, the stale-deposit watcher's submission-vs-mined terminal-state bug, and a set of documentation/simplicity/performance corrections. One architectural question (dual watcher wiring across the client and maintainer processes) was reviewed and confirmed as intentional split-deployment support, not a defect. One deployment-sequencing question (does the activation-height table land before the ReservationRouter/vault is trusted by the Bridge) was deferred to the deployment/ops team.

Review fixes (round 2, this update)

A second multi-agent review pass (see agent-docs/reviews/pr-4324-round2/report.md) against the larger diff above found 1 P0, 3 P1, and 16 P2/P3 confirmed issues (after two adversarial validation passes), now fixed:

  • P0: the misconfiguration self-check could Fatalf/os.Exit a correctly-configured node on a quiet/freshly-activated network -- downgraded to a logged error, no more process kill.
  • P1s: a missing uint32-underflow guard on the action-timeout watcher's renotify backoff (the sibling stale-deposit watcher already had it); an IsReservedDeposit RPC-error handling asymmetry that could let a moving-funds proposal proceed on a still-reserved deposit; and the wallet-registration-block bound that fell back to an unbounded genesis scan for the dominant (months-old wallet) case.
  • P2/P3s: a self-check panic-deadlock/goroutine-leak, a stop() scheduling race and total-RPC-bound doc gap in the reservation-acceptance worker pool, a candidatesExamined budget-ordering bug, several safety-adjacent doc/comment corrections, a WalletTerminationCause unbounded-scan cost (now bounded by registration block), missing integration/regression test coverage, and a proofInfoCache method relocated off a type it doesn't actually cache anything on.
  • The deployment-sequencing question from round 1 (activation-height table vs. router/vault trust ordering) was walked interactively post-report: resolved as runbook discipline -- add a deployment-runbook checklist item plus a startup sanity check, not a code change to this PR (see agent-docs/gap-inventory.md).
  • Full findings, dropped/refuted items with reasons, and arbitration rationale: agent-docs/reviews/pr-4324-round2/report.md.

Verification

  • go build ./... clean.
  • go vet clean on touched packages.
  • Full package test suite green: pkg/maintainer/spv, pkg/tbtc, pkg/tbtcpg, pkg/chain/ethereum, pkg/clientinfo, config, cmd.

Regenerated ReservationRouter/WalletProposalValidator/Bridge Go bindings
from the real compiled Solidity ABI (tbtc-v2 PR #1116 build artifacts),
replacing the self-validating vendored fallback. This surfaced and fixed
5 P0-severity defects where the vendored bindings diverged from the real
on-chain interface:

- ProofType constants were off-by-one against the zero-based on-chain
  enum (Acceptance=0, Reanchor=2, not 1/3).
- SubmitReservationProof called a combined entry point that does not
  exist on the router; split into SubmitReservationAcceptanceProof and
  SubmitReservationReanchorProof matching the real router functions.
- Reservation proposal ABI structs were missing requestNonce, which the
  real WalletProposalValidator contract requires.
- WalletReservations called a router view function that does not exist;
  reimplemented by deriving the wallet's currently-custodied reservation
  keys from acceptance/reanchor request events, filtered by each
  reservation's current on-chain wallet.
- NotifyReservationActionTimeout took an extra walletMembersIDs argument
  the real router does not accept.

Updates the spv.Chain and tbtc.Chain interfaces and all implementations/
test doubles/call sites accordingly.
…iew round 2

P1 fixes (all findings from the second multi-agent review pass on keep-core
PR #4282):

- Wire reservation watchers from the maintainer process (spv.go), not just
  the client (cmd/start.go); add a hard-fail startup self-check when the
  paired LeaderDutiesEnabled flags disagree and every watcher's initial
  scan found zero activity.
- Harden the vendored ABI fallback CI guard to diff full function
  signatures, not just names; regenerate the two vendored fallback
  artifacts (ReservationRouter.fallback-artifact.json,
  WalletProposalValidator.reservation-methods-fallback.json) from the real
  compiled ABI, since they had drifted the exact same way the P0 fixes
  already corrected; add a CI job that compiles tbtc-v2 and verifies no
  future drift.
- Add bounded retry to the stranding watcher's chain reads so a transient
  RPC failure does not permanently miss a wallet's stranding notifications.
- Fix the startup catch-up scan to retry (not silently assume Live) a
  wallet whose GetWallet call fails during startup.
- Re-check stranding after a Reanchor-type action timeout restores a
  reservation to Active under its (possibly dead) source wallet.
- Gate the reserved-deposit sweep filter on the real per-network
  activation block instead of chain-call success/failure.
- Bound reservation-acceptance candidate selection's Electrum calls with a
  worker pool and a real timeout instead of unbounded serial calls.
- Park stale-deposit-poll entries assigned to a Live wallet instead of
  re-checking the entire tracked set every tick.
- Capture and log the wallet's on-chain termination cause (moving funds
  timeout / moved funds sweep timeout / fraud challenge defeat) alongside
  each stranding notification, inferred from the three pre-termination
  Bridge events; best-effort, never blocks notification on failure.
- Bound the action-timeout watcher's per-tick RPC volume with a rotating
  cursor instead of scanning every tracked action every tick.

P2 fixes bundled in the same pass (landed in the same files as the P1
work above, by the same reviewing/fixing pass):

- Remove the dead CI artifact shim in Dockerfile/client.yml whose
  compile step never existed; document the real vendored-fallback
  mechanism instead.
- Fix the reservation proof loop's RBF-replacement handling (candidate
  map was silently dropping earlier transactions) and add a per-wallet
  transaction cache to avoid refetching full history every pass.
- Reorder the stale-deposit watcher's timeout derivation to check its own
  memo cache before paying a chain RPC.
- Correct two misattributed TODO(test-coverage) comments in tbtc.go and
  broaden the follow-up note to cover every proposal validator in the
  package.
- Fail closed for ethereum.Unknown in ReservationsActivationBlock: only
  the local Developer network activates reservations immediately: every
  other network, including the unset/unrecognized zero value, now
  requires an explicit activation-block entry or never activates.
- Fix proofInfoCache re-reading a stale cached chain tip against a fresh
  per-transaction confirmation count within the same proof pass; the tip
  is now re-fetched per transaction like the confirmation count is,
  while the genuinely pass-invariant fields keep their existing cache.
- Add a deterministic per-operator stagger before each watcher's first
  notify attempt (action-timeout and stale-deposit paths), so multiple
  reservation-enabled operators no longer race identical permissionless
  notify calls on simultaneous first detection.
- Consolidate 5 duplicated 30-day lookback constants, 2 duplicated
  scan-range helpers, and 2 differently-formatted event-key helpers
  into one canonical implementation each across the reservation proof
  loop, action-timeout watcher, and wiring layer.
- Fold the stale-deposit watcher's previously wiring-owned ticker,
  cursor, and pending/parked state into the watcher itself as a
  self-contained Run(ctx, interval) loop, matching the action-timeout
  watcher's existing shape; removes ~300 lines of split state machine
  from the wiring layer.
- Add a wiring test asserting the stale-deposit and action-timeout
  watchers actually drive real Bridge notifications on startup, not
  just that WireReservationWatchers returns without error.
…named flags

tbtc.ReservationsConfig was reused verbatim as pkg/maintainer/spv's own
Config.Reservations field, creating two identically-typed, identically-named
LeaderDutiesEnabled flags (Tbtc.Reservations.LeaderDutiesEnabled and
Maintainer.Spv.Reservations.LeaderDutiesEnabled) with different real meanings
- one gates proposal generation/watcher wiring/metrics in the client process,
the other gates SPV proof submission in the maintainer process.

Replace both with plainly-named booleans on their own owning configs:
Tbtc.ReservationsEnabled and Maintainer.Spv.ReservationProofsEnabled. Delete
tbtc.ReservationsConfig entirely. Updates every call site, test fixture
(toml/json/yaml), and config_test.go path, plus comments that referenced the
old dotted paths.
- Remove the false shared-cadence claim between
  DefaultReservationStaleDepositPollInterval and
  DefaultReservationActionTimeoutPollInterval; document both as
  independently tuned (currently coincidentally equal), not coupled.
- Escalate both reservation watchers' Run-loop exit to Fatalf, matching
  this function's established severity for equivalent-consequence wiring
  failures: Run only returns non-nil post-construction on an
  unreachable-in-practice misconfiguration, so reaching that branch means
  the watcher silently stopped monitoring reservations - worth crashing
  loudly over, not logging at the same level as a routine per-tick error.
Address advisory gaps caught after the main P0/P1/P2/P3 batch:

- TbtcChain.WalletReservations: extracted the deduplication, custody
  filter, and sort into a standalone resolveCustodiedReservationKeys
  helper (mirroring this file's existing buildReservationAnchorProposalAbi
  pattern), so the logic can be unit-tested without simulated-backend
  infrastructure. Added a WalletReservationsCount short-circuit so
  wallets that have never custodied a reservation do not pay the full
  genesis-to-tip eth_getLogs scan on every wallet-close notification;
  added deterministic big.Int sort on the returned key slice (Go map
  iteration order is non-deterministic).

- WalletReservations test coverage: added TestResolveCustodiedReservationKeys
  exercising dedup (acceptance + reanchor event union), nil-event guard,
  custody filter (re-anchored-away exclusion), lookup-error propagation,
  and deterministic order. The riskiest untested new logic in the P0
  batch now has regression coverage.

- P0 #3 anchor/reanchor proposal mapping test vacuity: the existing
  TestBuildReservationAnchorProposalAbi and TestBuildReservationReanchorProposalAbi
  both used RequestNonce: 0 in input and expected RequestNonce: 0 in
  output, so the new RequestNonce field mapping would have silently
  regressed to omitting the field without any test failure. Set
  RequestNonce to non-zero values (17, 23) in both tests so the new
  mapping is actually exercised.

- Marshal parity: tightened both ReservationAnchorProposal.Marshal and
  ReservationReanchorProposal.Marshal to reject zero-value fees (was
  Sign() < 0 only, accepting Sign() == 0 since big.Int(0).Bytes() is
  empty) and zero-value ReservationKey. Moved the three affected
  table-driven subtests into a new TestReservationProposals_MarshalRejectsZeroValues
  asserting directly against Marshal (the original table uses
  marshalThroughProposal which t.Fatalf's on any Marshal error, so it
  cannot cover Marshal-side rejection).

- Stranding watcher operator-address plumbing: added operatorAddress
  field and SetOperatorAddress setter to reservationStrandingWatcher,
  and called SetOperatorAddress in the wiring layer at the existing
  strandingWatcher construction site. Stranding is intentionally NOT
  staggered - a blocking delay here would silently suppress a one-shot
  OnWalletClosed event (the trigger never replays), which is strictly
  worse than a transient gas race; the field is retained only for
  observability and forward-compatibility, documented in the struct
  field's doc comment.

- Watcher Run goroutine death: replaced Fatalf escalation with defer
  recover() + distinct Errorf. Run only returns non-nil on the interval
  misconfiguration guard at loop start (per-tick errors are logged and
  the loop continues), so Fatalf was both unreachable in practice and
  architecturally wrong - WireReservationWatchers is called from two
  callers (cmd/start.go's client process and spv.go's maintainer
  process) and the caller decides process-level fatality, not this
  shared helper.
…c sort, test coverage

Address gaps caught after the post-review batch landed:

- TbtcChain.WalletReservations: thread the wallet's own on-chain
  registration block (earliest, since wallets can re-register during
  recovery) as StartBlock on both PastReservationAcceptanceRequestedEvents
  and PastReservationReanchorRequestedEvents filter queries. A wallet
  cannot have reservation events before it existed, so this is an
  exact lower bound for the eth_getLogs scan - the previous genesis-to-tip
  fallback only fired when the registration lookup itself failed, so a
  transient RPC error degrades to the old behavior rather than skipping
  real reservations.

- resolveCustodiedReservationKeys: sort the candidate keys BEFORE
  the per-key GetReservation lookup loop, so the order of chain calls
  is deterministic and the partial-failure (first lookup error
  propagated) path reproduces across runs instead of depending on Go
  map iteration order.

- TestResolveCustodiedReservationKeys: rewrite as four t.Run subtests
  covering the actual failure modes the original P0 fix turns on:
  dedup (key present in both event lists appears exactly once),
  custody filter (reanchored-away excluded), reanchored-in (key only
  reachable via the reanchor event list), and sorted output regardless
  of event order. Plus a lookup-error subtest.

- Mutation-tested the RequestNonce mapping: temporarily setting line
  529's mapping to RequestNonce: 0 makes TestBuildReservationAnchorProposalAbi
  fail; restoring makes it pass. The non-zero RequestNonce values
  (17, 23) in the input and expected structs are now actually exercised.

- Stranding watcher operatorAddress plumbing: REMOVED. The field
  and setter I added previously were dead code - no caller reads the
  field and the wiring-layer setter call was on a one-shot path where
  adding a blocking stagger would silently suppress notifications
  rather than race them (the OnWalletClosed event never replays).
  Recording the stranding-stagger item as deliberately deferred with
  the one-shot rationale.

- Stale-deposit watcher recover wording: corrected 'reservation
  stranding notifications' to 'stale reserved deposit notifications'
  in both the panic-recover block and the post-Run error message so
  the log doesn't misdirect whoever reads it during an incident.
@coderabbitai

coderabbitai Bot commented Sep 9, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Advanced

Run ID: 8538281e-3280-4ba0-a198-3402806a30a9

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

CI client-format check caught a trailing blank line left in
deposit_sweep_test.go in both packages from an earlier commit
this session.
…d-fallback-verify

The job I added in 8ec859d compiles tbtc-v2 upstream Solidity
contracts via 'yarn install --frozen-lockfile', which failed
deterministically on every run (checked both pushes to this PR): a
transitive dependency (umpirsky/country-list, pulled in via
@celo/contractkit) resolves over the unencrypted git:// protocol
(port 9418), which times out against GitHub Actions runners' network
egress - unrelated to any Go/Solidity code in this change, but it is
this job's own CI wiring and belongs in scope here.

Fix: rewrite git://github.com/ to https://github.com/ for git's own
URL resolution before yarn install, so dependency fetches use the
same protocol every other checkout/fetch step in this job already
uses successfully.
The https rewrite in the prior commit fixes the git:// timeout but
changes how yarn resolves the country-list git dependency vs the
exact hash frozen in tbtc-v2's own yarn.lock, which --frozen-lockfile
then rejects as drift even though no real dependency version changed.
This checkout is a scratch clone of the upstream repo used only to
compile and diff the real ABI - never committed back - so an
unpinned, job-local lockfile resolution is safe.
ReservationRouter.sol does not exist on tbtc-v2's main branch yet -
verified via the GitHub API (bridge/ dir has no Reservation* files on
main). PR threshold-network/tbtc-v2#1112, which adds it, merged into
tbtc-v2's own 'reservations-upgrade' integration branch instead -
mirrors this repo's own reservations-epic staging pattern. Pointing
TBTC_V2_REF at main made this job fail unconditionally on every run,
independent of any lockfile or network fix.

Also replace the raw 'cp: cannot stat' failure with a clear
::error:: pointing at TBTC_V2_REF, so a future re-drift of this ref
(e.g. once the reservations work lands on tbtc-v2 main and this
should switch back) fails with an actionable message instead of a
bare missing-file error.
…re-terminal state

- misconfiguration self-check no longer conflates RPC-failure with
  genuine zero on-chain activity; each scan signal is now tri-state
  and the check runs async instead of blocking client/maintainer
  startup on reservation-history scan depth
- stranding recheck after an action-timeout notify now waits for the
  notify tx to mine instead of reading pre-mined state, and dedupes
  per wallet per tick
- WalletTerminationCause is skipped entirely when a closed wallet
  holds zero reservations
- stale-deposit Notified resolution is no longer terminal on tx
  submission; it is confirmed on a later tick's on-chain re-check and
  retried if unconfirmed, so a reverted notify can no longer
  permanently lose a tracked deposit
- isWalletLive and the per-tick ReservationParameters read now reuse
  the existing per-tick memo instead of re-fetching
- removed SetOperatorAddress/SetStrandingWatcher compatibility
  setters in favor of required constructor parameters
- deleted the reservationDefaultLookBackBlocks compatibility aliases
  in reservation_proof_loop.go and reservation_stale_deposit_watch.go
- corrected ReservationProofsEnabled's doc comment and stale
  LeaderDutiesEnabled/SubmitReservationProof references
- removed misleading no-state-mutation claims on the Unknown/Keep
  resolution doc comments
…servation filtering

WalletReservations' registration-block establishment no longer costs
an unbounded genesis-to-tip eth_getLogs; earliestWalletRegistrationBlock
is now bounded by the same 30-day-plus-reorg-margin convention used
elsewhere in the reservation feature. WalletTerminationCause's doc
comment is corrected: it does not verify mutual exclusivity of the
three pre-termination events and returns the first found in a fixed
priority order, not the most recent; its call-frequency doc comment
now matches its actual call sites instead of claiming "once per
wallet close".

FindDeposits hardcoded reservationsActive=false, so moving_funds.go's
unswept-deposit guard always treated reserved deposits as unswept,
permanently blocking moving-funds proposal generation for any wallet
holding one once reservations go live. FindDeposits now takes an
explicit reservationsActive parameter: moving_funds.go passes the
proposal request's real value, and the listing-only maintainer CLI
caller passes false explicitly.
…nding-tx fetch

depositSweepAction.execute() derived reservationsActive from
proposalProcessingStartBlock, a strictly later block than the
coordinationBlock the leader uses in executeLeaderRoutine, breaking
the invariant that both sides evaluate the activation predicate at
the same height. The action now carries and uses the real
coordinationBlock, threaded from processCoordinationResult through
handleDepositSweepProposal rather than reversed from a fixed offset
(the reversal was an unguarded uint64 subtraction that could
underflow).

The reservation-acceptance funding-tx worker pool eagerly fetched
every phase-one candidate's Bitcoin transaction body/confirmations
before the oldest-first eligibility loop could pick one and stop,
turning ~2 RPCs into up to the full backlog size. It now schedules
candidates lazily, oldest-first, and stops dispatching once an
eligible candidate is found, with new coverage for the concurrency
cap, per-candidate timeout, and ordering under out-of-order
completion.
Marshal was tightened to reject non-positive big.Int values on
AnchorTxFee, ReservationKey, and ReanchorTxFee, but Unmarshal did not
enforce the same invariant on receipt, so a hand-crafted protobuf
carrying a non-empty zero-value byte encoding for any of the three
fields passed unchanged.

The vendored-fallback-verify CI job's rationale comment claimed to
diff against a "real, currently-deployed" contract ABI when it
actually diffs against the ABI compiled from the pinned upstream
tbtc-v2 ref; also removed a dangling reference to prior-review
finding IDs whose source artifacts don't exist in this checkout.
…nderflow

now is a caller-supplied, not-guaranteed-monotonic tick token. If now
ever precedes a deposit's recorded notifiedAt (clock adjustment, or a
non-monotonic tick), now-notifiedAt underflows to approximately 2^32
and the backoff check fails open, resubmitting NotifyStaleReservedDeposit
immediately instead of waiting out actionTimeoutRenotifyInterval.
now < notifiedAt is now treated the same as "interval not yet
elapsed".
…t wiring docs

- misconfiguration self-check now logs at Errorf instead of Fatalf/os.Exit,
  so a correctly-configured node on a quiet network no longer self-terminates
- initial watcher-scan goroutines publish a scanResult even when their pass
  panics, so a panic can no longer permanently block the self-check goroutine
  on resultCh and leak it
- corrected wiring doc comments describing the self-check's actual behavior
  and the deferred (not immediate) stranding recheck after an action timeout
- simplified reservationOperatorStaggerOffset's internal hash reduction to
  plain uint64 modulo (signature unchanged)
- added an integration test proving the real WireReservationWatchers wiring
  drains a deferred stranding recheck end to end
…flow

- add the same now < notifiedAt underflow guard the stale-deposit watcher
  already has, preventing a backward clock step from causing a renotify
  storm on the action-timeout watcher
- drainStrandingRechecks now re-queues a wallet on a transient GetWallet or
  stranding-check error instead of permanently dropping its recheck
…osits

An IsReservedDeposit RPC error inside findDeposits now returns a hard error
instead of silently dropping the deposit, matching pkg/tbtc/deposit_sweep.go's
posture. Previously an RPC error could make a still-reserved deposit look
unreserved to the moving-funds unswept-deposit check. Also corrects the
reservationsActive doc comment to reference the coordination block, not the
current block.
…path

- reservationsActive doc now correctly describes evaluation at the
  coordination block, not the current/processing block
- removed a stale coordinationBlock field-doc sentence contradicted by the
  code and its sibling comment
- added a regression test for ValidateDepositSweepProposal's fail-closed
  behavior when reservationsActive is true and ReservationParameters errors
…ting

- give cancellation priority in the funding-tx lookup dispatcher so an
  observed stop() is checked before a dispatch slot is acquired, narrowing
  (per the pipeline's own tolerance) the early-stop race
- correct pipeline doc comments to describe the concurrency cap as strict
  and the total-RPC/early-stop savings as best-effort, not guaranteed
- move the skipDepositKeys check ahead of the per-run candidate cap so
  already-rejected deposits no longer consume fresh candidate budget
earliestWalletRegistrationBlock now resolves and memoizes a wallet's actual
earliest registration block via a bounded backward walk instead of a fixed
~30-day window that silently fell back to a genesis-to-tip scan for any
older wallet - the dominant case for the stranding watcher's callers.
WalletTerminationCause reuses this bound instead of always scanning from
block 0.
- operatorAddress field doc no longer cites a nonexistent SetOperatorAddress
  setter; points to the constructor-parameter rationale instead
- pollTick's Notified/Drop description corrected to match
  StaleDepositResolutionNotified's actual documented contract
- dropped stale 'Finding 6' review-report references from test comments
walletTransactionsForProof now fetches transaction bodies for its own
already-known hash list instead of calling GetTransactionsForPublicKeyHash,
whose Electrum implementation re-fetches the same hash list internally -
removing one of two redundant full wallet-history scans per cache miss.
Added a proveReservationTransaction test covering a non-matching candidate
ahead of an unconfirmed matching one, and dropped stale review-report
'Finding N'/'P0 #N' references from test comments.
currentBlockHeight never cached its result (by design, per its own doc
comment - the Bitcoin tip can change between two calls in the same pass),
so attaching it to proofInfoCache was misleading. It is now a package-level
function; behavior is unchanged.
- coordination.go's activation-block table doc no longer claims
  ethereum.Unknown activates at block 0 or names the removed
  reservationsActivationBlock symbol
- client.yml's vendored-fallback-verify job comment no longer claims it
  runs on every event/branch; it runs whenever the Client workflow runs
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