Skip to content

fix(reservations): close cross-repo M1 review findings - #4343

Merged
piotr-roslaniec merged 6 commits into
reservations-epicfrom
fix/m1-cross-repo-review
Sep 30, 2026
Merged

piotr-roslaniec merged 6 commits into
reservations-epicfrom
fix/m1-cross-repo-review

Conversation

@piotr-roslaniec

Copy link
Copy Markdown
Collaborator

Context

A cross-repo review compared tbtc-v2 #1116 and keep-core #4282 against the M1 reservations specification. It raised 60 candidates; two independent validation passes confirmed 48 and dropped 7. This PR closes the keep-core code findings on top of reservations-epic.

The three P0s made the wallet-side flow nonfunctional or discarded contract-settleable proofs:

  • acceptance tried to validate and request an action before the depositor-created action existed;
  • re-anchor validated before requesting its on-chain action;
  • the SPV loop evicted timed-out generations that the contract still accepts.

Changes

Wallet coordination

  • Consume an existing depositor-created Pending Acceptance action at its real nonce and snapshots. Remove the invalid operator-side acceptance request.
  • Request re-anchor first, track the transaction by hash, then validate and propose against the mined Pending Reanchor action.
  • Resume pending re-anchors across rounds. Pending receipts suppress duplicate requests; reverted or dropped requests can retry; mined requests resume without another request.
  • Select re-anchor targets with count and amount headroom and preserve Solidity's zero-global-cap semantics.
  • Accept Live and MovingFunds source wallets and mirror the validator's 7,200-second request safety margin.
  • Tighten LocalChain validator fakes so tests enforce the same action-state, nonce, wallet-state, fee and timeout preconditions as Solidity.

SPV maintainers and watchers

  • Keep TimedOut acceptance proofs through timeoutAt + termSeconds; keep TimedOut re-anchor proofs without an added Go-side expiry.
  • Match both P2WPKH and P2PKH outputs by public-key hash.
  • Start proof and watcher recovery scans at the configured reservation activation block, in bounded chunks. Unknown networks skip startup scans.
  • Derive stale-deposit deadlines from the reveal snapshot, defer all per-deposit chain reads until that deadline, and keep tracking until the on-chain pending-deposit record clears.
  • Match timeout equality with Solidity and count watcher goroutine deaths without panicking on a typed-nil metrics recorder.

ABI, chain and observability

  • Preserve reservation action termSeconds and minAmount snapshots and the re-anchor miner fee in domain conversions.
  • Remove the unused generic timeout-event model that does not exist in Solidity.
  • Add ReservationVault bindings and publish in-kind fee-debt and TBTC reserve gauges.
  • Add an in-memory EVM harness around the real WalletProposalValidator and a stub Bridge.
  • Pin and extend CI ABI drift checks to Bridge, both reservation validator methods, ReservationRouter and ReservationVault. Mismatches now fail the job.

Verification

  • gofmt on every changed/new Go source: clean.
  • go build ./...: pass.
  • go vet ./cmd/... ./pkg/chain/ethereum ./pkg/clientinfo ./pkg/maintainer/spv ./pkg/tbtc ./pkg/tbtcpg/...: pass.
  • go test -count=1 ./cmd/... ./pkg/chain/ethereum ./pkg/clientinfo ./pkg/maintainer/spv ./pkg/tbtc ./pkg/tbtcpg/...: pass.
  • make -C pkg/chain/ethereum/tbtc/gen verify-vendored-fallback against freshly compiled tbtc-v2 artifacts: all four ABI comparisons pass.
  • Full go test -count=1 ./... was attempted. Every changed package passed; the run failed only in unmodified pkg/tecdsa/dkg when TestTssFinalize_IncomingMessageCorrupted_WrongPayload hit its internal TSS round-one timing guard. Re-running that test produced nondeterministic outcomes across identical source and an identical 87-package dependency closure, including a pass at the exact base commit. This PR does not suppress or modify that test.

Targeted regression tests were mutation-checked: each failed when its corresponding production branch was temporarily removed, then passed after exact restoration. Covered paths include late TimedOut proofs, action snapshots, P2PKH matching, activation-block recovery, re-anchor receipt states, the timeout safety margin, and typed-nil metrics.

Scope notes

  • Base: reservations-epic at f66f11240 (includes fix(reservations): review-round fixes for reservation ABI, watcher plumbing, and lookback bounds #4324).
  • This PR does not reconcile the epic branch with dev; Reservations epic -> dev tracking #4282 remains conflicting until that separate integration step.
  • File-size debt is intentionally not refactored here. Most touched files already exceeded the repository's 500-line guideline at the base, and splitting them would mix a broad mechanical rewrite into correctness fixes. The largest new exception is pkg/chain/ethereum/tbtc_validator_harness_test.go at 981 lines; pkg/maintainer/spv/reservation_proof_loop_test.go grows from 1,668 to 2,168 lines. This exception was explicitly chosen for this PR and should be paid down separately.
  • The existing full-repository go vet ./... warning in pkg/tecdsa/signing/protocol.go is outside this diff; changed reservation packages are vetted separately.

Related

@coderabbitai

coderabbitai Bot commented Sep 29, 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: ae876c5f-1e2e-4fec-8a0c-b9899760ddfb

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.

…ce anchor

Address review findings in the Ethereum reservation adapter:

- pkg/chain/ethereum/tbtc.go:915-925 - map only ethereum.NotFound or a nil
  receipt to NotFound; propagate every other TransactionReceipt error.
- pkg/chain/ethereum/tbtc.go:918-919 - bound the receipt lookup with a 30s
  context, matching the baseChain header helpers.
- pkg/chain/ethereum/tbtc.go:1767-1782 - drop the unreachable empty-string
  vault address branch.
- pkg/tbtc/chain.go:612 - remove RequestReservationAcceptance from the
  reservation chain interfaces and the Ethereum implementation; only the
  depositor may request acceptance.
- Expose the action's on-chain SourceAnchorUtxoHash snapshot for the SPV
  proof loop's TimedOut re-anchor eviction.
- pkg/chain/ethereum/tbtc.go:915, :1795 - add adapter tests for receipt
  status mapping and vault fee-debt/reserve reads.
Address review findings in wallet coordination:

- pkg/tbtcpg/reservation_reanchor.go:981 - forget in-flight re-anchor
  requests whose reservation left the source wallet.
- pkg/tbtcpg/reservation_acceptance.go:427 - zero the vault fee gauges
  when the reservation vault is unconfigured.
- pkg/tbtcpg/chain_test.go:1420 - anchor validator fake enforces wallet
  state, deposit, fee, minimum amount and refund margin preconditions.
- pkg/tbtcpg/chain_test.go:1515 - re-anchor validator fake enforces source
  custody, source != target and the fee bound.
- pkg/tbtcpg/chain_test.go:1728 - re-anchor request fake enforces the
  target amount cap; Run test covers moving to the next candidate.
…oofs

Address review findings in the SPV maintainer and watchers:

- pkg/maintainer/spv/reservation_event_scan.go:13-24 - use 10,000-block
  startup scan chunks; test chunk boundaries and error propagation.
- pkg/maintainer/spv/reservation_proof_loop.go:625-690 - evict a TimedOut
  re-anchor generation only once the reservation's current anchor no
  longer matches the action's on-chain source anchor snapshot.
- pkg/maintainer/spv/reservation_proof_loop.go:285 - keep scan state across
  inner-loop restarts so recovery resumes instead of rescanning.
- pkg/maintainer/spv/reservation_event_scan.go:41 - drop the unused
  currentBlock parameter.
- pkg/maintainer/spv/reservation_stale_deposit_watch.go:125, :325-330 -
  remove the parameters tick cache and the redundant IsReservedDeposit read.
- pkg/maintainer/spv/reservation_stale_deposit_watch.go:36-39,84-87,245-253,
  :77-79,253-257, :729; reservation_wiring.go:438-446;
  reservation_action_timeout_watch.go:251 - correct watcher comments.
- pkg/maintainer/spv/reservation_wiring_test.go:1079, :1183 - synchronize the
  recording metrics recorder and replace the sleep with a completion signal.
- pkg/maintainer/spv/reservation_stale_deposit_watch_test.go:136 - drop
  review-history narration from test comments.
- .github/workflows/client.yml:240 - the ABI drift gate now also compares
  the ABI embedded in the committed generated bindings (extracted with
  go run ./checkabi) against the compiled tbtc-v2 artifacts, failing on
  mismatch.
- pkg/clientinfo/performance.go:242, :767 - state the TBTC reserve gauge
  publishes base units and is approximate above 2^53.
- pkg/chain/ethereum/tbtc_validator_harness_test.go:8 - replace review-ID
  narration with evergreen intent.
@piotr-roslaniec
piotr-roslaniec marked this pull request as ready for review September 30, 2026 08:13
@piotr-roslaniec
piotr-roslaniec merged commit 76aaecd into reservations-epic Sep 30, 2026
19 checks passed
@piotr-roslaniec
piotr-roslaniec deleted the fix/m1-cross-repo-review branch September 30, 2026 08:51
piotr-roslaniec added a commit that referenced this pull request Sep 30, 2026
# fix(reservations): address validated review findings on #4343

## Context

#4343 merged into `reservations-epic` before its multi-lens review was
acted on. That review raised 55 findings: 45 were confirmed (listed as
42) and 10 were dropped. This PR fixes the confirmed ones on top of the
merge commit `76aaecdd3`, whose code is identical to #4343's head.

It also corrects several claims in the #4343 description that did not
hold. They are listed under "Corrections to #4343" below.

## Changes

### Re-anchor (`pkg/tbtcpg/reservation_reanchor.go`)

- Re-anchor out of **Closing** source wallets, on both the request and
the resume path. Solidity allows MovingFunds and Closing sources.
Before, a wallet that entered Closing while it still held reservations
could never finish closing. The below-dust notification stays
MovingFunds-only.
- **At most one on-chain request per pass.** The fee is estimated and
the transaction assembled against the live `reservationTxMaxFee` before
the request is sent. Any failure after a request is sent ends the pass.
The mined-wait stops early if another requester's action takes the
nonce.
- **Removed the in-memory in-flight map,** the
`GetReservationReanchorRequestReceipt` method and its receipt enum.
`RequestReservationReanchor` returns `error` again. Every round decides
from re-read chain state. A duplicate request after a restart or a
leader change can still revert on-chain, which costs gas but is safe.
- Skip reservations in re-anchor **cooldown**
(`tbtc.Reservation.ReanchorCooldownUntil` is now converted), and skip
reservations whose anchor is at or below `txMaxFee + minAmount`. Both
cases revert on-chain.
- The target search runs **once per pass**. Wallets that are full on
count are excluded for the rest of the pass, and the smallest anchor
that fits nowhere is remembered.
- `TestReservationRequestTimeoutSafetyMarginMatchesValidator` pins the
Go copy of the 7,200-second margin. The EVM harness pins the on-chain
value.

### Acceptance (`pkg/tbtcpg/reservation_acceptance.go`, `pkg/tbtc`)

- Candidates now come from `ReservationAcceptanceRequested` events for
the wallet. The scan covers the on-chain action timeout plus one day, in
10,000-block chunks.
- Only confirmed Pending Acceptances targeting the wallet count toward
the 50-candidate budget, and that check runs before any Electrum call.
Before, never-requested or fake reserved reveals could use up the budget
and starve real requests.
- Requests made more than 30 days after the reveal are now proposed and
signed:
- The proposer scans backward from the request block, bounded by the
snapshotted term plus 7 days.
- The signer estimates the reveal block from `RevealedAt` and widens the
window once if needed.
  - Both use the new chunked helper `tbtc.FindDepositRevealedEvent`.
- A fee-estimate or proposal error now skips that candidate instead of
aborting the window.
- Vault fee gauges are published once per Run and reuse the vault
address that was already read. The gauge is renamed to
`reservation_vault_fee_reserve_tbtc_base_units`, because its value is in
1e18 base units.

### SPV maintainers (`pkg/maintainer/spv`)

- The proof loop, the stale-deposit watcher and the action-timeout
watcher share one first-scan rule:
  - Start at the reservation activation block.
  - Skip the startup scan on networks without an activation entry.
  - Scan in 10,000-block chunks.
- The stranding startup scan is chunked. It keeps its 30-day start,
because wallets registered before activation can hold reservations, and
it no longer falls back to scanning from genesis.
- Progress is saved per chunk, so a failed chunk keeps the earlier work.
A scan error no longer skips proving or checking items already tracked.
Scan cursors stay 12 blocks behind the tip to cover shallow reorgs.
- A TimedOut acceptance generation is evicted once its reservation is no
longer Unknown. Before, it was retried for up to `termSeconds` after a
newer generation settled.
- Each reservation gets at most one proof per pass: Pending first, then
TimedOut by descending nonce. This applies to both acceptance and
re-anchor.
- TimedOut re-anchor generations whose reservation cannot settle are
evicted. Stranded ones are kept, because late settlement is allowed.
Generations with nothing to prove back off up to 32 passes. The
reservation is read once per generation per pass.
- Stale-deposit deadlines are stored in the tracked entry. The unused
`snapshotRefundDeadline` fallback is removed.
- `cmd/maintainer.go` copies the Ethereum network into the SPV config
through a tested helper.

### CI, harness and tooling

- `TBTC_V2_REF` is pinned to tbtc-v2 `eec999aa`, the merge of
tbtc-v2#1161 into `reservations-upgrade`. The harness
`WalletProposalValidator.json` was re-vendored from that same commit.
The bindings and the four ABI fallbacks are unchanged and verified
against it.
- New CI check: the harness validator must match the compiled one. The
ABI is compared exactly, and the creation and deployed bytecode are
compared with the CBOR metadata stripped. This check fails on the old
`9f8f5ef1` validator, which has the same ABI but different logic.
- New harness cases against the real bytecode:
  - timeout margin at +7200 (rejected) and +7201 (accepted);
  - an anchor from a MovingFunds wallet;
  - re-anchor target count at the cap and at cap + 1;
  - an anchor fee above the maximum;
  - an anchor naming the wrong wallet.
- `verify-vendored-fallback` is now a single macro. A `checkabi` failure
is reported as a tool failure, not as ABI drift. `checkabi` no longer
double-spaces an `internalType` that already contains a space.
- `regenerate.sh` requires `TBTC_V2_BUILD_DIR` and compiles by relative
path, so regenerating is reproducible across machines.

### Tests and fakes

- The LocalChain `RequestReservationReanchor` fake now mirrors
`Reservation.sol` (source and target state, cooldown, anchor floor, caps
where zero blocks all, reserved target capacity). It writes only the
action fields Solidity writes.
- All acceptance Run tests now go through the strict validator fake.
That fake now also checks the funding tx hash and the deposit script.
Fixtures the contracts would reject were fixed. Misleading "cap"
scenarios were removed.
- The SPV fakes return zero records for absent keys, as the real
mappings do, and honour `EndBlock`.
- The conversion tests now cover `TermSeconds`, `MinAmount` and
`ReanchorCooldownUntil` with non-zero values.
- Each new regression test was checked by reverting its production line,
confirming the test fails, and restoring the line.

### Docs

- Removed the duplicated `tbtc.go` header.
- Completed the lists of fields the conversions deliberately skip.
- Documented that `TermSeconds` and `MinAmount` are set only for
acceptance generations.
- Rewrote comments that described removed behaviour or cited internal
review IDs.

## Behaviour change to note

Only Developer has a reservation activation entry today. On Mainnet,
Sepolia and other networks, none of the three loops scans history at
startup; they process new blocks only. For the action-timeout watcher
this replaces the previous 30-day lookback. It matches the other loops,
and reservations are not enabled on those networks until an entry is
added. When an entry is added, restart recovery covers everything since
activation.

## Corrections to #4343

- "Pending receipts suppress duplicate requests": the adapter never
returned Pending, and entries expired after 6 blocks while rounds are
900 blocks apart. The mechanism is removed; see Re-anchor.
- "Accept Live and MovingFunds source wallets": this was true only for
the acceptance wallet. Re-anchor sources were MovingFunds only; Closing
is now also accepted.
- "Watcher recovery scans at the activation block, in bounded chunks;
unknown networks skip startup scans": the action-timeout watcher did
neither. It does now.
- "Preserve the re-anchor miner fee in domain conversions": no
conversion existed. The unused `MinerFee` field is removed.
- "Tightened LocalChain validator fakes": most acceptance Run tests
bypassed them. They no longer do.
- "Action snapshots" mutation-checked: the conversion test compared zero
with zero. It now uses non-zero values.
- The undeclared dependency on tbtc-v2#1161 is resolved by the new pin.

## Not changed

- Re-anchor requests stay inside leader proposal generation. Moving them
into a maintainer loop was raised as a follow-up design option, not a
defect.
- The test capacity ledger does not model capacity held by pending
acceptances.
- `StubBridge.json` is not checked in CI; it is a test stub, not an
upstream contract.

## Verification

- `gofmt` on every changed Go file: clean.
- `go build ./...`: pass.
- `go vet` on `./pkg/tbtcpg/... ./pkg/tbtc/... ./pkg/chain/ethereum/...
./pkg/maintainer/... ./cmd/... ./pkg/clientinfo/...`: pass.
- `go test -count=1` on the same packages: pass.
- `go test -race` on `pkg/maintainer/spv` and `pkg/tbtcpg`: pass.
- The drift check and the harness check, run with `bash -e` against a
local `eec999aa` build: pass.

## Related

- Follow-up to #4343. Tracker: #4282.
- tbtc-v2#1161 (merged into `reservations-upgrade` as `eec999aa`).
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