Skip to content

Extend deadlines for valid-but-unconfirmed legs#500

Merged
LandynDev merged 2 commits into
m3from
m3-d3-extensions
Jul 1, 2026
Merged

Extend deadlines for valid-but-unconfirmed legs#500
LandynDev merged 2 commits into
m3from
m3-d3-extensions

Conversation

@LandynDev

Copy link
Copy Markdown
Collaborator

What

D3 — deadline extensions. Protects honest miners from a false slash when a swap leg is broadcast-and-valid but just slow to reach its required confirmations.

The false slash this fixes

The contract slashes a miner whose leg is on-chain and matches every detail (recipient, amount, sender) but still sits below min_confirmations when the deadline passes. The validator loop never called the extension builders, so a slow source/dest confirm turned into a false slash.

Trigger — valid-but-unconfirmed only

_fetch_leg is now tri-state: ('no') absent/mismatch (slash-eligible), ('pending', info) matched-but-unconfirmed (the new extendable case), ('ok', info) confirmed, ('down') unreachable. verify_transaction(require_confirmed=False) already returned the matched info while unconfirmed; the only blocker was _fetch_leg collapsing not info.confirmed into 'no'.

decide() extends only on pending, only near the deadline ((deadline - now) <= EXTENSION_PADDING_SECONDS), only with room below the contract ceiling (max_extend_at):

  • PendingAttestation source pending → EXTEND_RESERVATION
  • Fulfilled dest pending → EXTEND_TIMEOUT (at ceiling + overdue → TIMEOUT)
  • Active never extends (no mark_fulfilled = no broadcast evidence → overdue slashes)
  • dest absent/mismatch + overdue → TIMEOUT

The contract ceiling is the only bound — no client-side extension counters. decide() returns a (decision, target_at) SwapAction so _cast_vote casts extend_reservation/extend_timeout with the computed target. Extension casts tolerate ExtensionNotLater / ExtensionExceedsCeiling / lost races as no-ops.

Seconds-based target replacing block rot

chains.py's compute_extension_target returned subtensor blocks and cited deleted ink! lib.rs lines. Replaced with compute_extension_target_secs (unix seconds): now + max(0, min_confirmations - confs) * seconds_per_block + EXTENSION_PADDING_SECONDS, bucketed on a seconds grid derived from existing constants (EXTENSION_BUCKET_BLOCKS * SUBTENSOR_BLOCK_SECONDS), clamped to max_extend_at. The block-based compute_extension_target and confirmations_to_subtensor_blocks were dead in production (tests only) and are removed.

Notes

  • Stacks on D1 (m3-d1-attestation-rate-check) — both edit decide()/SwapDecision/_cast_vote. Base is D1; retarget to m3 once D1 merges.
  • Per-chain numbers (seconds_per_block, min_confirmations) come from chains.py — no scattered literals.
  • MAX_EXTENSIONS_PER_* constants left untouched (miner cache sizing references them).
  • 546 unit tests green; ruff clean.

@LandynDev LandynDev force-pushed the m3-d3-extensions branch 2 times, most recently from 13bb488 to 2b8300e Compare June 30, 2026 22:09
@LandynDev LandynDev deleted the branch m3 July 1, 2026 03:51
@LandynDev LandynDev closed this Jul 1, 2026
@LandynDev LandynDev reopened this Jul 1, 2026
@LandynDev LandynDev changed the base branch from m3-d1-attestation-rate-check to m3 July 1, 2026 03:53
@LandynDev LandynDev merged commit 99ac9fd into m3 Jul 1, 2026
@LandynDev LandynDev deleted the m3-d3-extensions branch July 1, 2026 03:58
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