fix(reservations): address validated review findings on #4343 - #4346
Merged
Merged
Conversation
… EndBlock in event fakes
The eight copy-pasted mktemp/jq/diff blocks become calls to a single verify_abi macro. The local side is written to a temp file and chained with && instead of being piped into jq, so a checkabi build failure now stops make with checkabi's own error instead of being reported as ABI drift. Explanatory comments that sat inside the recipe (and were echoed by make) move above the target.
An internalType that already reads "struct X" became "struct X". Also terminate the unknown-contract error with a newline.
Remove the duplicated tbtc.go file header, list DissolutionDelay among the intentionally unconverted action fields, document that TermSeconds and MinAmount are acceptance-only snapshots, drop the never-populated ReservationReanchoredEvent.MinerFee field, and move the deposit refund safety margin constant next to the test fake that uses it.
…t it The drift job compiled tbtc-v2 9f8f5ef1 while the harness validator was vendored from e635e229, whose validateReservationAnchorProposal logic differs under an identical ABI, so no check could notice. - Pin TBTC_V2_REF to eec999aa (merge of tbtc-v2#1161 into reservations-upgrade); the four reservation artifacts and the Go bindings still match it. - Re-vendor WalletProposalValidator.json from an eec999aa build (code unchanged apart from the metadata hash) and record that commit. - Add verify.sh and a job step comparing the vendored validator to the compiled one: ABI exactly, creation and deployed bytecode with solc's CBOR metadata removed, since the job's unfrozen yarn install can move the metadata hash. - regenerate.sh now requires TBTC_V2_BUILD_DIR instead of defaulting to a local sibling checkout.
… lookup The in-memory in-flight map did not suppress duplicate requests across rounds: rounds are far apart, the leader rotates, restarts clear it, and the adapter never reports a mempool transaction as pending. Safety comes from re-reading chain state, since Reservation.sol only accepts a request against an Active reservation. Remove the map and its helpers, GetReservationReanchorRequestReceipt with its receipt enum, and restore RequestReservationReanchor to return only an error. Run now dispatches on chain state alone: a mined request shows up as an ActionPending reservation and takes the resume path.
…alidator harness Run the real WalletProposalValidator bytecode at the boundaries only it can check: an action timing out exactly REQUEST_TIMEOUT_SAFETY_MARGIN from now is rejected and one second later is accepted (anchor and re-anchor), a MovingFunds wallet can anchor, a re-anchor target at its reservation cap is accepted and one above it is rejected, and an anchor fee above the action's max fee or naming a wallet other than the action's target is rejected. Times use the harness block timestamp so the boundaries are exact. Also state the real reason the simulated backend is avoided: github.com/fjl/memsize's linkname to runtime.stopTheWorld is rejected by the Go 1.23+ linker.
… behind the tip All three reservation loops now share one scan-range policy: the first scan starts at the reservation activation block (the action-timeout watcher no longer uses a 30-day lookback), networks without an activation entry skip the first scan, and every scan runs through fetchPastEventsInChunks and stops a few blocks behind the tip. The chunk helper hands each chunk to a callback so callers keep progress and advance their cursor chunk by chunk; a scan error no longer skips the proving or checking of already-tracked items, and the proof loop runs the re-anchor round even when the acceptance round fails. The stranding startup registration scan is chunked as well.
The LocalChain RequestReservationReanchor fake now enforces every requestReservationReanchor precondition in Solidity's order and with its revert reasons (cooldown, MovingFunds or Closing source, Live target distinct from the source, signing window, anchor floor, count cap where zero blocks all, amount cap), reserves the target's count and amount on request, and writes exactly the action fields Solidity writes. WalletReservationsCount and WalletReservationsAmount read a ledger made of the wallet's custodied reservations plus the capacity pending re-anchor requests reserved on it; new timeout and settlement helpers update it the way the contract does. Fixtures now set an action timeout and keep anchors above ReservationTxMaxFee + ReservationMinAmount, as a real request requires.
Reservation.sol accepts a permissionless re-anchor request from a MovingFunds or Closing source, and a wallet can reach Closing while it still holds reservations. Run only acted on MovingFunds wallets, so a re-anchor missed during MovingFunds, including the resume of an already authorized generation, stayed stuck and blocked wallet closing. Accept Closing on both paths; the below-dust notification stays MovingFunds only.
…producible metadata
Estimate the fee and assemble the re-anchor transaction against the live ReservationTxMaxFee, which Solidity snapshots into the new action, before sending RequestReservationReanchor, so a fee spike no longer leaves an unusable request holding the target's capacity. Any failure after the request was sent now ends the pass instead of moving on to another reservation; only pre-submit capacity reverts retry the next target. The mined-wait stops as soon as the nonce advances to another requester's action, and the not-confirmed error now wraps its cause.
…ttleable generations A TimedOut acceptance generation is evicted once its reservation exists, since the Bridge then rejects every other acceptance proof. Each pass walks a reservation's generations Pending first, then TimedOut by descending nonce, and stops after the first submission attempt, so two generations matching the same Bitcoin transaction no longer broadcast a second, reverting proof. A TimedOut re-anchor generation is evicted when the reservation is no longer Active, ActionPending or Stranded, its reservation record is read once per pass and reused for proving, and a TimedOut generation with no matching or provable transaction is re-checked on a doubling backoff capped at 32 passes, ended early when the source wallet's transaction list changes.
…ld revert Run now skips an Active reservation while block time is before its reanchorCooldownUntil or when its anchor is not above ReservationTxMaxFee + ReservationMinAmount, the two request-time gates Reservation.sol applies to permissionless callers. Previously such reservations re-ran the target search and a reverting gas estimate on every window.
…ts pending entry pollTick always recorded the deadline from the reveal event at discovery, so the chain-read fallback that recovered it for an unmemoized deposit (with its own unchunked 30-day reveal scan and sentinel error) never ran in production. The deadline now lives in the tracked entry and is passed to CheckStaleReservedDeposit; the memo, the fallback helpers and their tests are removed.
Run searched for a target per reservation and per capacity retry, each time re-running the bounded registration scan, the unbounded fallback, and GetWallet plus capacity reads for every wallet, exactly when caps are saturated. A per-pass target search now reads registrations and wallet states at most once, lazily on the first reservation that needs a target, rules out count-capped wallets for the rest of the pass, remembers the smallest anchor that fits nowhere, and skips already-read wallets in the unbounded fallback.
… test The end-to-end typed-nil test returned once the forced panic fired, before the deferred recover had called recordReservationWatcherDeath. A no-op hook now runs after that call and the test waits on it.
…y margin Add a Run test where only the amount pre-check rules out a partly filled target, with the boundary where the target lands exactly on the cap and the zero-cap-is-unlimited case, each asserting which target received the single request. Pin the Go copy of the validator's 7200-second request timeout safety margin.
The copy of [ethereum] Network into the SPV maintainer config moves into maintainerConfig, so a test can pin it: without it the network is Unknown and every reservation catch-up scan is skipped.
Correct the EthereumNetwork, ethNetwork, lookback-constant, stale-deposit and anchor-hash comments: the reservation proof loop and the stale-deposit and action-timeout watchers all start their first scan at the activation block and skip it on networks without an entry, the 30-day lookback now bounds only the stranding registration scan, and the Go anchor hash differs from the Bridge's for a reservation without an anchor. Drop history wording from the touched comments.
The watcher-death metric test left its recovered goroutines reading reservationWatcherPanicRecovered while the typed-nil test replaced it, a data race under -race. Both tests now install the hook through one helper and wait for both recover paths before returning.
Candidates now come from the wallet's ReservationAcceptanceRequested events within the on-chain action timeout plus a margin, instead of DepositRevealed events from the last 30 days. Only generations the chain confirms are a Pending Acceptance for this wallet count toward the per-run budget, and that check runs before any reveal search or Bitcoin lookup, so never-requested reveals cannot starve real requests. The reveal is located by a chunked backward scan from the request block bounded by the generation's term, so requests made long after the reveal are still proposed. Run performs the per-window setup and gauge publication once, then tries candidates in timeout order; a fee-estimate or proposal failure skips that candidate instead of aborting the window. The pre-write error wrapper is removed. Tests delegate to the strict anchor validator, which now also checks the funding transaction hash and deposit locking script. Fixtures use real funding transactions, future refund locktimes, the 2-hour deposit minimum age and a 90-day term. Scenarios that only rejected for lack of a pending action are removed, the below-minimum scenario becomes the reachable boundary case, and a MovingFunds scenario is added.
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Advanced Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
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. Comment |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
fix(reservations): address validated review findings on #4343
Context
#4343 merged into
reservations-epicbefore 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 commit76aaecdd3, 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)reservationTxMaxFeebefore 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.GetReservationReanchorRequestReceiptmethod and its receipt enum.RequestReservationReanchorreturnserroragain. 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.tbtc.Reservation.ReanchorCooldownUntilis now converted), and skip reservations whose anchor is at or belowtxMaxFee + minAmount. Both cases revert on-chain.TestReservationRequestTimeoutSafetyMarginMatchesValidatorpins 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)ReservationAcceptanceRequestedevents for the wallet. The scan covers the on-chain action timeout plus one day, in 10,000-block chunks.RevealedAtand widens the window once if needed.tbtc.FindDepositRevealedEvent.reservation_vault_fee_reserve_tbtc_base_units, because its value is in 1e18 base units.SPV maintainers (
pkg/maintainer/spv)termSecondsafter a newer generation settled.snapshotRefundDeadlinefallback is removed.cmd/maintainer.gocopies the Ethereum network into the SPV config through a tested helper.CI, harness and tooling
TBTC_V2_REFis pinned to tbtc-v2eec999aa, the merge of tbtc-v2#1161 intoreservations-upgrade. The harnessWalletProposalValidator.jsonwas re-vendored from that same commit. The bindings and the four ABI fallbacks are unchanged and verified against it.9f8f5ef1validator, which has the same ABI but different logic.verify-vendored-fallbackis now a single macro. Acheckabifailure is reported as a tool failure, not as ABI drift.checkabino longer double-spaces aninternalTypethat already contains a space.regenerate.shrequiresTBTC_V2_BUILD_DIRand compiles by relative path, so regenerating is reproducible across machines.Tests and fakes
RequestReservationReanchorfake now mirrorsReservation.sol(source and target state, cooldown, anchor floor, caps where zero blocks all, reserved target capacity). It writes only the action fields Solidity writes.EndBlock.TermSeconds,MinAmountandReanchorCooldownUntilwith non-zero values.Docs
tbtc.goheader.TermSecondsandMinAmountare set only for acceptance generations.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
MinerFeefield is removed.Not changed
StubBridge.jsonis not checked in CI; it is a test stub, not an upstream contract.Verification
gofmton every changed Go file: clean.go build ./...: pass.go veton./pkg/tbtcpg/... ./pkg/tbtc/... ./pkg/chain/ethereum/... ./pkg/maintainer/... ./cmd/... ./pkg/clientinfo/...: pass.go test -count=1on the same packages: pass.go test -raceonpkg/maintainer/spvandpkg/tbtcpg: pass.bash -eagainst a localeec999aabuild: pass.Related
reservations-upgradeaseec999aa).