Skip to content

chore: ignore captures taken before a market's settle time - #1441

Merged
MicBun merged 2 commits into
mainfrom
settle-on-capture-after-settle-time
Oct 8, 2026
Merged

MicBun merged 2 commits into
mainfrom
settle-on-capture-after-settle-time

Conversation

@MicBun

@MicBun MicBun commented Oct 7, 2026 •

Copy link
Copy Markdown
Contributor

Description

A market resolves against the value that stood at its settle_time. settle_market picks the earliest signed capture of the market's query (#1434), but it had no way to tell a capture taken before settle_time from one taken after. request_attestation is PUBLIC, a live market's query_components are readable, and attestations stored heights only. A capture taken before settle_time, while the value could still move, would therefore win.

This change records the block time on every capture, and settlement now uses only captures taken at or after settle_time.

  • 023-attestation-schema.sql adds attestations.created_timestamp INT8 with ALTER TABLE ... ADD COLUMN IF NOT EXISTS, the same way 028 added data_provider and stream_id.
  • 024-attestation-actions.sql: request_attestation stores @block_timestamp in the new column. The signed payload and the attestation hash do not change, so existing signatures and verifiers are unaffected. I regenerated the .prod.sql override with scripts/generate_prod_migrations.py, because request_attestation is one of the fee actions it overrides.
  • 032-order-book-actions.sql: settle_market resolves on the earliest signed capture with created_timestamp >= settle_time. A capture taken exactly at settle_time counts, which matches the check that lets a market settle at that second. When no capture qualifies, the error now names the case. Either a capture taken in time is waiting for a signature, or every capture was taken too early or has no time, or there is no capture at all. settle_market has no .prod.sql override.
  • tn_settlement: the scheduler's capture check counts only captures taken at or after the market's own settle_time. A market whose only capture is early is captured again, instead of waiting on a capture that settle_market refuses.

Rows written before this change

Existing rows get created_timestamp = NULL. Their block time cannot be recovered from attestations, so I did not backfill them. Settlement treats a capture without a time like an early one and skips it.

Only captures of markets that have not settled are ever read again. Settled markets are untouched. A market that was captured but not yet settled when the migration lands is captured again on the scheduler's next poll and settles on the new capture.

Release order

The binary works with or without the column. If attestations has no created_timestamp yet (Postgres error 42703), the scheduler counts every capture and logs this once. The settle_market deployed at that point also takes any signed capture, so the two agree. The usual order therefore works: deploy the binary first, then run migrate.sh.

The ALTER takes an AccessExclusiveLock on attestations for a moment on every migrate.sh run, as 028 already does. Since #1400, settlement's poll reads run with a 3 second lock_timeout, so a read that hits the lock fails and retries on the next poll instead of holding up block execution.

A market captured but not yet settled when the migration lands is captured again, and the books of one ladder that straddle the migration would settle on captures from different blocks. Running the migration when no market is due avoids both.

Related Problem

resolves: #1435

How Has This Been Tested?

The integration tests run against real Postgres in the test containers, with -tags kwiltest.

New unit tests in extensions/tn_settlement:

  • The capture check filters on created_timestamp >= settle_time and passes the market's settle time.
  • It falls back to counting every capture when the column is missing.
  • Any other error, such as a lock timeout, still fails the check.
  • The scheduler asks about each market at that market's own settle time.

New integration tests in TestSettlementIntegration:

  • A capture one second before settle_time (value TRUE) does not count. The capture check reports nothing captured, and settle_market refuses with the "taken before settle_time" error. A second capture exactly at settle_time (value FALSE) then settles the market FALSE, although the early capture has the lower height.
  • A capture with no time (set to NULL, as a row from before the column reads) is skipped the same way, and a timed capture then settles the market.

Updated tests: the existing tests took their captures at block time 0, before their markets' settle times. They now take them at or after the settle time, so each test still checks what it checked before. TestSettlement, TestSettlementPayouts, TestOrderEvents, TestIndexChangeSettlement, TestIndexChangePayout, TestSettlementIntegration and the tn_settlement unit tests pass.

Mutation checks: I applied each mutant below, ran the tests, then restored the files and compared them byte for byte. Every mutant fails at least one test.

  • Go: no time filter in the capture check; > instead of >=; a fallback on any Postgres error; no fallback; the scheduler passing 0 instead of the market's settle time.
  • SQL: no time filter in settle_market; > instead of >=; NULL counted as in time; request_attestation storing no time; no time filter in the "waiting for a signature" check.

Summary by CodeRabbit

  • New Features
    • Settlement uses the earliest signed capture recorded at or after a market’s settlement time. Captures without a timestamp or taken earlier do not qualify.
    • Attestations now record when their captures were created.
  • Bug Fixes
    • Markets without an eligible capture remain unresolved until one is available. Capture-status checks continue to work with older database schemas.

@MicBun MicBun self-assigned this Oct 7, 2026
@holdex

holdex Bot commented Oct 7, 2026 •

Copy link
Copy Markdown

Time Submission Status

Member # Time Running Total Status Last Update
MicBun 5h ✅ Submitted Oct 7, 2026, 12:54 PM

Submit or update total time with:

@holdex pr submit-time 2h

Add time on top of previous submission with:

@holdex pr add-time 1h30m

See available commands to help comply with our Guidelines.

@MicBun

MicBun commented Oct 7, 2026

Copy link
Copy Markdown
Contributor Author

@holdex pr submit-time 5h

@coderabbitai

coderabbitai Bot commented Oct 7, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: e831fce6-4aa1-48e1-8328-d2ac0c48df2b
📥 Commits

Reviewing files that changed from the base of the PR and between 62bc2dd and 12f094b.

📒 Files selected for processing (2)
  • extensions/tn_settlement/internal/engine_ops.go
  • extensions/tn_settlement/settlement_integration_test.go
🚧 Files skipped from review as they are similar to previous changes (2)
  • extensions/tn_settlement/internal/engine_ops.go
  • extensions/tn_settlement/settlement_integration_test.go

Included review availability: This review used your included allowance. Your plan provides up to 1 included review per hour; 0 remain after this review.


📝 Walkthrough

Walkthrough

Attestations now store their capture block timestamp. Settlement and capture-status checks use that timestamp to exclude captures taken before a market’s settle time or without a recorded timestamp.

Changes

Settlement Capture Eligibility

Layer / File(s) Summary
Store attestation capture timestamps
internal/migrations/023-attestation-schema.sql, internal/migrations/024-attestation-actions*.sql
The schema adds a nullable created_timestamp column. request_attestation stores the current block timestamp with each attestation.
Select captures eligible for settlement
internal/migrations/032-order-book-actions.sql, extensions/tn_settlement/README.md
settle_market selects the earliest signed capture taken at or after settle_time. It distinguishes eligible captures awaiting signatures from captures that are too early or lack a timestamp.
Apply settle time to capture-status checks
extensions/tn_settlement/internal/engine_ops.go, extensions/tn_settlement/internal/engine_ops_test.go, extensions/tn_settlement/scheduler/scheduler.go, extensions/tn_settlement/scheduler/scheduler_test.go
Capture-status checks accept each market’s settle time and filter by created_timestamp. They retry without the timestamp filter only when PostgreSQL reports an undefined column.
Validate timestamp-based settlement
extensions/tn_settlement/settlement_integration_test.go, tests/streams/order_book/*
Tests set capture timestamps and cover captures before, at, and without a recorded settle-time timestamp.

Priority: ➖ Normal

Estimated code review effort: 3 (Moderate) | ~25 minutes

Change: Bug fix · Severity of issue fixed: Medium

Sequence Diagram(s)

sequenceDiagram
  participant BlockContext
  participant request_attestation
  participant attestations
  participant settle_market
  BlockContext->>request_attestation: provide block timestamp and height
  request_attestation->>attestations: store capture with created_timestamp
  settle_market->>attestations: select signed capture at or after settle_time
  attestations-->>settle_market: return earliest eligible signed capture
Loading

Merge Risk: ⚪ Minimal · up to 12f09

No merge-blocking issue was identified. Run the migration when no market is due, as recommended for deployment.

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning Issue #1435 requires settlement to compare capture time with settle_time and states that existing rows need backfilling. The PR adds nullable attestations.created_timestamp, stores `@block_timesta… Add a backfill from an authoritative historical time source for existing attestations, or obtain an explicit issue-owner resolution that changes the existing-row requirement before closing #1435.
Docstring Coverage ⚠️ Warning Docstring coverage is 65.52% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 29 functions across 8 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: settlement now ignores captures taken before a market's settle time.
Out of Scope Changes check ✅ Passed The schema migration, capture timestamp storage, settlement filtering, scheduler behavior, compatibility fallback, documentation, and tests all support issue #1435. No unrelated change is evident in t…
Full details: Linked Issues check

Explanation

Issue #1435 requires settlement to compare capture time with settle_time and states that existing rows need backfilling. The PR adds nullable attestations.created_timestamp, stores @block_timestamp, filters settle_market and the scheduler, and tests the boundary. Migration 023-attestation-schema.sql leaves existing rows as NULL, and the implementation skips those rows. The PR does not meet the linked issue's existing-row requirement.

  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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.

@MicBun
MicBun marked this pull request as draft October 7, 2026 16:40
@MicBun
MicBun marked this pull request as ready for review October 8, 2026 09:55
@MicBun
MicBun force-pushed the settle-on-capture-after-settle-time branch from e400d95 to 12f094b Compare October 8, 2026 09:56
@MicBun
MicBun merged commit b64fe03 into main Oct 8, 2026
8 checks passed
@MicBun
MicBun deleted the settle-on-capture-after-settle-time branch October 8, 2026 11:04
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.

Problem: settlement can't tell a capture was taken too early

1 participant