Skip to content

fix(spv): align deposit sweep proof length with Bridge accumulated difficulty#3918

Open
lionakhnazarov wants to merge 14 commits into
threshold-network:mainfrom
lionakhnazarov:feat/spv-proof-adjustment
Open

fix(spv): align deposit sweep proof length with Bridge accumulated difficulty#3918
lionakhnazarov wants to merge 14 commits into
threshold-network:mainfrom
lionakhnazarov:feat/spv-proof-adjustment

Conversation

@lionakhnazarov
Copy link
Copy Markdown
Collaborator

Updates the SPV maintainer’s getProofInfo logic so the number of Bitcoin headers in the SPV proof matches what Bridge.evaluateProofDifficulty requires: sum of per-header difficulty ≥ requestedDifficulty × txProofDifficultyFactor, with the first header matching relay current or previous epoch difficulty. Adds relay-window checks so the proof range stays in the previous epoch, current epoch, or spans previous→current only.

This fixes failures like Insufficient accumulated difficulty in header chain on chains where per-block work varies (e.g. testnet4 min-difficulty streaks), where a fixed block count × epoch-average math was wrong.

Changes
pkg/maintainer/spv/spv.go: implement proofRangeWithinRelayWindow; rewrite getProofInfo to walk headers from proof start, sum header.Difficulty(), resolve requestedDiff from first header vs relay difficulties.
pkg/maintainer/spv/spv_test.go + bitcoin_chain_test.go: populate mock headers with realistic Bits/difficulty; fix setCurrentAndPrevEpochDifficulty argument order; update expectations.
(if included) .github/workflows/contracts-ecdsa.yml: resolve accidental merge conflict markers in on: triggers.

lionakhnazarov and others added 9 commits December 12, 2025 14:16
- Added  function to create block headers with specified difficulty.
- Introduced  method in  to add headers for a range of heights based on dynamic difficulty.
- Updated tests in  to utilize the new difficulty handling, ensuring accurate proof range calculations across epochs.
- Enhanced  function to compute required confirmations based on actual header difficulties instead of fixed assumptions.
Copy link
Copy Markdown
Member

@lrsaturnino lrsaturnino left a comment

Choose a reason for hiding this comment

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

Nice one! Just a couple of nits...

Comment thread pkg/maintainer/spv/spv.go
requestedDiff = currentEpochDifficulty
case firstHeaderDiff.Cmp(previousEpochDifficulty) == 0:
requestedDiff = previousEpochDifficulty
default:
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The default branch here silently returns false, which makes the caller log "proof goes outside the previous and current difficulty epochs" — but the real issue is the tx block was mined at min-difficulty.

On testnet4 this will cause silent retries with a misleading log. Could you add a logger.Warnf distinguishing this case? Something like "transaction block difficulty %s matches neither current (%s) nor previous (%s) epoch — may be unprovable if mined in a min-difficulty block."

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Missing a test for the scenario that motivated this whole PR — a tx block at min-difficulty where firstHeaderDiff matches neither epoch difficulty. Would be good to add a case with difficultyAtBlock(proofStartBlock) = big.NewInt(1) while epoch difficulties are much higher, expecting isProofWithinRelayRange=false.

Add the missing case for the scenario that motivated the PR: a
min-difficulty block whose first-header difficulty matches neither the
current nor the previous epoch difficulty. Bridge.evaluateProofDifficulty
would revert "Not at current or previous difficulty", so getProofInfo
must report the proof as out of range.
The default branch in getProofInfo silently returns false, after which
the caller logs "proof goes outside the previous and current difficulty
epochs". That message is misleading for the case where the first
header's difficulty is simply unknown to the relay (e.g. a testnet4
min-difficulty block). Emit a dedicated Warnf so silent retries are
diagnosable.
On chains with min-difficulty blocks (e.g. testnet4) the per-header
difficulty walk can need many more blocks than txProofDifficultyFactor
to accumulate enough work, issuing one GetBlockHeader RPC per
iteration. Log a Warnf once the walk crosses a soft threshold so an
operator can notice excessive header fetches per maintainer tick.
getProofInfo fetched the header at proofStartBlock once to resolve
requestedDifficulty, then refetched the same header on the first
iteration of the per-header difficulty walk. Seed the running sum and
block count with firstHeader and start the loop at proofStartBlock + 1
to drop the duplicate RPC.
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