feat(security): C-3 — confirm DeliverTx + oracle state before oracle liveness success #23

Closed
opened 2026-08-08 04:40:20 +00:00 by PlasticDigits · 19 comments
PlasticDigits commented 2026-08-08 04:40:20 +00:00 (Migrated from gitlab.com)

Summary

Close C-3 from audits/INTERNAL_KIMIK3_1786162831.md: stop treating Terra BROADCAST_MODE_SYNC (CheckTx / mempool admission) success as confirmed on-chain success for oracle liveness.

Audit ref: C-3
Related: H-3 (silence alert timing — separate issue); M-1 (local clock vs block time — may interact)


Current codebase

Component Path Behavior today
Broadcast oracle-service/src/terra_tx.rs Posts mode: BROADCAST_MODE_SYNC; returns Ok(txhash) when tx_response.code == 0
Tick oracle-service/src/main.rs run_once On broadcast Ok, calls record_successful_broadcast() and logs “submitted oracle update”
Liveness oracle-service/src/liveness.rs Silence alert keys off last record_successful_broadcast
Defaults config.rs ORACLE_MAX_SILENCE_SECS default 28800 (8h)

Problem: code == 0 under SYNC means CheckTx passed. DeliverTx failures (policy drift, paused oracle, wrong operator, wasm OOG) still look like success. Liveness stays green while on-chain rate goes stale; window rejects swaps after DEFAULT_MAX_ORACLE_AGE_SECS (6h).


Why this is needed

  1. Operators’ primary health signal can lie during the exact outages that matter (on-chain reject after mempool accept).
  2. False success delays detection up to the silence threshold, which is already longer than the window staleness budget (see H-3).
  3. Without inclusion + state confirmation, policy/ACL misconfig can persist invisibly.

Constraints / guardrails

  1. Only record liveness success after DeliverTx success (included, code == 0) and oracle State reflects the intended update (last_update_sec and/or rate changed as expected).
  2. Bound confirmation polling (timeout + max attempts); fail the tick loudly if not confirmed — do not record success.
  3. On account sequence mismatch, re-query account and retry once (or small bounded N); jitter on transient LCD errors.
  4. Do not log full LCD URLs with embedded API keys (coordinate with H-6 if touched).
  5. Keep fail-closed: uncertain inclusion ⇒ not success.
  6. No change to on-chain oracle policy in this issue.

Relevant files

  • oracle-service/src/terra_tx.rs — broadcast + new confirm/query helpers
  • oracle-service/src/main.rs — success gating before liveness record
  • oracle-service/src/liveness.rs — semantics docs / tests
  • oracle-service/src/config.rs — optional confirm timeout envs
  • README.md / docs/DEPLOYMENT.md — liveness meaning (“confirmed on-chain update”)
  • Tests under oracle-service (wiremock HTTP fixtures preferred)

  1. After SYNC broadcast returns txhash with CheckTx code == 0, poll GET /cosmos/tx/v1beta1/txs/{hash} until included or timeout.
  2. Require block/tx result code == 0.
  3. Query oracle State; verify last_update_sec advanced and rate matches proposed (or documented acceptance rule).
  4. Only then record_successful_broadcast().
  5. Sequence mismatch → refresh account → single retry; other LCD errors → bounded backoff.

Acceptance criteria

  • DeliverTx failure (simulated) does not advance liveness success timestamp.
  • CheckTx success alone does not count as success.
  • Confirmed inclusion + matching oracle state ⇒ liveness success + info log.
  • Confirmation timeout ⇒ tick error / warn; no success record.
  • Sequence mismatch path retries once with fresh account info.
  • Docs state that silence tracking means confirmed on-chain updates.

Test plan (all paths)

  1. Happy: mock SYNC ok → mock tx-by-hash included code 0 → mock State updated → liveness recorded.
  2. DeliverTx fail: SYNC ok → tx included code ≠ 0 → no liveness record; error surfaced.
  3. Never included: SYNC ok → poll timeout → no liveness record.
  4. State not updated: inclusion ok but State unchanged → no liveness record (detect wrong contract / no-op).
  5. Sequence mismatch: first broadcast fails with sequence error → re-query → retry succeeds once.
  6. Policy skip path: check_rate_update Err → no broadcast (unchanged); liveness untouched.
  7. Equal rate short-circuit: no broadcast (unchanged).

Test plan (attack / hack / abuse vectors)

  1. Fake health: LCD returns SYNC code 0 for txs that always fail DeliverTx — must not silence-alert-suppress.
  2. Stale hash replay: confirming wrong/old hash must not count (bind expected hash from broadcast response).
  3. State race: another operator updated rate differently — define fail-closed behavior (mismatch ⇒ no success for this tick).
  4. LCD delay / censorship: timeout fails closed; alert path still works via silence.
  5. Log injection / URL leak: confirmation errors must not print credentialed URLs.

Verification criteria

  • Unit/wiremock tests cover confirm success, DeliverTx fail, timeout, state mismatch, sequence retry.
  • Manual LocalTerra (optional): force on-chain reject (e.g. pause oracle) and observe no liveness success.
  • README/DEPLOYMENT wording updated; C-3 closed when merged.
## Summary Close **C-3** from [`audits/INTERNAL_KIMIK3_1786162831.md`](../audits/INTERNAL_KIMIK3_1786162831.md): stop treating Terra `BROADCAST_MODE_SYNC` (CheckTx / mempool admission) success as confirmed on-chain success for oracle liveness. **Audit ref:** C-3 **Related:** H-3 (silence alert timing — separate issue); M-1 (local clock vs block time — may interact) --- ## Current codebase | Component | Path | Behavior today | |-----------|------|----------------| | Broadcast | `oracle-service/src/terra_tx.rs` | Posts `mode: BROADCAST_MODE_SYNC`; returns `Ok(txhash)` when `tx_response.code == 0` | | Tick | `oracle-service/src/main.rs` `run_once` | On broadcast `Ok`, calls `record_successful_broadcast()` and logs “submitted oracle update” | | Liveness | `oracle-service/src/liveness.rs` | Silence alert keys off last `record_successful_broadcast` | | Defaults | `config.rs` | `ORACLE_MAX_SILENCE_SECS` default 28800 (8h) | **Problem:** `code == 0` under SYNC means CheckTx passed. DeliverTx failures (policy drift, paused oracle, wrong operator, wasm OOG) still look like success. Liveness stays green while on-chain rate goes stale; window rejects swaps after `DEFAULT_MAX_ORACLE_AGE_SECS` (6h). --- ## Why this is needed 1. Operators’ primary health signal can lie during the exact outages that matter (on-chain reject after mempool accept). 2. False success delays detection up to the silence threshold, which is already longer than the window staleness budget (see H-3). 3. Without inclusion + state confirmation, policy/ACL misconfig can persist invisibly. --- ## Constraints / guardrails 1. Only record liveness success after **DeliverTx success** (included, `code == 0`) **and** oracle `State` reflects the intended update (`last_update_sec` and/or `rate` changed as expected). 2. Bound confirmation polling (timeout + max attempts); fail the tick loudly if not confirmed — do not record success. 3. On `account sequence mismatch`, re-query account and retry **once** (or small bounded N); jitter on transient LCD errors. 4. Do not log full LCD URLs with embedded API keys (coordinate with H-6 if touched). 5. Keep fail-closed: uncertain inclusion ⇒ not success. 6. No change to on-chain oracle policy in this issue. --- ## Relevant files - `oracle-service/src/terra_tx.rs` — broadcast + new confirm/query helpers - `oracle-service/src/main.rs` — success gating before liveness record - `oracle-service/src/liveness.rs` — semantics docs / tests - `oracle-service/src/config.rs` — optional confirm timeout envs - `README.md` / `docs/DEPLOYMENT.md` — liveness meaning (“confirmed on-chain update”) - Tests under `oracle-service` (wiremock HTTP fixtures preferred) --- ## Recommended direction 1. After SYNC broadcast returns txhash with CheckTx `code == 0`, poll `GET /cosmos/tx/v1beta1/txs/{hash}` until included or timeout. 2. Require block/tx result `code == 0`. 3. Query oracle `State`; verify `last_update_sec` advanced and `rate` matches proposed (or documented acceptance rule). 4. Only then `record_successful_broadcast()`. 5. Sequence mismatch → refresh account → single retry; other LCD errors → bounded backoff. --- ## Acceptance criteria - [ ] DeliverTx failure (simulated) does **not** advance liveness success timestamp. - [ ] CheckTx success alone does **not** count as success. - [ ] Confirmed inclusion + matching oracle state ⇒ liveness success + info log. - [ ] Confirmation timeout ⇒ tick error / warn; no success record. - [ ] Sequence mismatch path retries once with fresh account info. - [ ] Docs state that silence tracking means confirmed on-chain updates. --- ## Test plan (all paths) 1. **Happy:** mock SYNC ok → mock tx-by-hash included code 0 → mock State updated → liveness recorded. 2. **DeliverTx fail:** SYNC ok → tx included code ≠ 0 → no liveness record; error surfaced. 3. **Never included:** SYNC ok → poll timeout → no liveness record. 4. **State not updated:** inclusion ok but State unchanged → no liveness record (detect wrong contract / no-op). 5. **Sequence mismatch:** first broadcast fails with sequence error → re-query → retry succeeds once. 6. **Policy skip path:** `check_rate_update` Err → no broadcast (unchanged); liveness untouched. 7. **Equal rate short-circuit:** no broadcast (unchanged). --- ## Test plan (attack / hack / abuse vectors) 1. **Fake health:** LCD returns SYNC code 0 for txs that always fail DeliverTx — must not silence-alert-suppress. 2. **Stale hash replay:** confirming wrong/old hash must not count (bind expected hash from broadcast response). 3. **State race:** another operator updated rate differently — define fail-closed behavior (mismatch ⇒ no success for this tick). 4. **LCD delay / censorship:** timeout fails closed; alert path still works via silence. 5. **Log injection / URL leak:** confirmation errors must not print credentialed URLs. --- ## Verification criteria - Unit/wiremock tests cover confirm success, DeliverTx fail, timeout, state mismatch, sequence retry. - Manual LocalTerra (optional): force on-chain reject (e.g. pause oracle) and observe no liveness success. - README/DEPLOYMENT wording updated; C-3 closed when merged.
PlasticDigits commented 2026-08-08 04:51:17 +00:00 (Migrated from gitlab.com)

mentioned in commit cbc0d88630e9384a1724edb9ebd3c0f1fb7c1ae2

mentioned in commit cbc0d88630e9384a1724edb9ebd3c0f1fb7c1ae2
PlasticDigits commented 2026-08-08 04:51:17 +00:00 (Migrated from gitlab.com)

mentioned in commit fae6e8aebe

mentioned in commit fae6e8aebec917dfd875af3eb85fa44b3287fb7c
PlasticDigits commented 2026-08-08 04:51:23 +00:00 (Migrated from gitlab.com)

mentioned in merge request !35

mentioned in merge request !35
PlasticDigits commented 2026-08-08 04:51:32 +00:00 (Migrated from gitlab.com)

mentioned in merge request !36

mentioned in merge request !36
PlasticDigits commented 2026-08-08 04:51:38 +00:00 (Migrated from gitlab.com)

Implemented in MR !35 (fix/issue-23-oracle-liveness-confirmtx).

Done (acceptance):

  • DeliverTx failure does not advance liveness
  • CheckTx alone does not count as success
  • Confirmed inclusion + matching State ⇒ liveness + info log
  • Confirmation timeout ⇒ tick error/warn; no success record
  • Sequence mismatch retries once with fresh account
  • Docs: silence = confirmed on-chain updates (README, DEPLOYMENT, INV-ORACLE-LIVENESS-001, skills/oracle-liveness-confirm)

Not in this MR (by design / optional):

  • Manual LocalTerra pause/reject smoke test
  • H-3 silence/poll timing alignment
  • M-1 block-time vs local clock for policy
  • On-chain oracle policy changes
Implemented in MR !35 (`fix/issue-23-oracle-liveness-confirmtx`). **Done (acceptance):** - [x] DeliverTx failure does not advance liveness - [x] CheckTx alone does not count as success - [x] Confirmed inclusion + matching State ⇒ liveness + info log - [x] Confirmation timeout ⇒ tick error/warn; no success record - [x] Sequence mismatch retries once with fresh account - [x] Docs: silence = confirmed on-chain updates (README, DEPLOYMENT, INV-ORACLE-LIVENESS-001, `skills/oracle-liveness-confirm`) **Not in this MR (by design / optional):** - Manual LocalTerra pause/reject smoke test - H-3 silence/poll timing alignment - M-1 block-time vs local clock for policy - On-chain oracle policy changes
PlasticDigits commented 2026-08-08 04:51:57 +00:00 (Migrated from gitlab.com)

mentioned in merge request !37

mentioned in merge request !37
PlasticDigits commented 2026-08-08 04:52:06 +00:00 (Migrated from gitlab.com)

mentioned in merge request !38

mentioned in merge request !38
PlasticDigits commented 2026-08-08 04:52:24 +00:00 (Migrated from gitlab.com)

mentioned in issue #24

mentioned in issue #24
PlasticDigits commented 2026-08-08 05:10:46 +00:00 (Migrated from gitlab.com)

MR !35 verification (C-3 / #23)

Verdict: PASS_WITH_NOTES — merging after rebase as needed.

AC

  • DeliverTx failure does not advance liveness
  • CheckTx-only success does not count
  • Confirmed inclusion + matching State → liveness + info log
  • Timeout → fail-closed, no liveness record
  • Sequence mismatch → one retry with fresh account
  • Docs updated (INV-ORACLE-LIVENESS-001)

Tests (local)

cargo test -p ust1-oracle-service — 29/29 passed
cargo clippy -p ust1-oracle-service --all-targets -- -D warnings — passed

Problems / follow-ups

  1. Optional LocalTerra DeliverTx-reject smoke test still outstanding.
  2. Policy-skip / equal-rate short-circuit paths lack dedicated regression tests (low risk; unchanged logic).
  3. Prior GitLab pipelines only ran secret_detection (no full rust job on this branch historically).
  4. Overlaps with !37 (H-3) on config.rs / liveness.rs — rebase expected when stacking merges.
## MR !35 verification (C-3 / #23) **Verdict: PASS_WITH_NOTES — merging after rebase as needed.** ### AC - [x] DeliverTx failure does not advance liveness - [x] CheckTx-only success does not count - [x] Confirmed inclusion + matching State → liveness + info log - [x] Timeout → fail-closed, no liveness record - [x] Sequence mismatch → one retry with fresh account - [x] Docs updated (INV-ORACLE-LIVENESS-001) ### Tests (local) `cargo test -p ust1-oracle-service` — **29/29 passed** `cargo clippy -p ust1-oracle-service --all-targets -- -D warnings` — **passed** ### Problems / follow-ups 1. Optional LocalTerra DeliverTx-reject smoke test still outstanding. 2. Policy-skip / equal-rate short-circuit paths lack dedicated regression tests (low risk; unchanged logic). 3. Prior GitLab pipelines only ran `secret_detection` (no full `rust` job on this branch historically). 4. Overlaps with !37 (H-3) on `config.rs` / `liveness.rs` — rebase expected when stacking merges.
PlasticDigits commented 2026-08-08 05:17:26 +00:00 (Migrated from gitlab.com)

mentioned in commit dee9d0e67b

mentioned in commit dee9d0e67ba19e5909750350fbf3186a835b6017
PlasticDigits commented 2026-08-08 05:17:46 +00:00 (Migrated from gitlab.com)

mentioned in commit 47e20e78b8

mentioned in commit 47e20e78b872a94d702c2b427184aed54c4639b7
PlasticDigits commented 2026-08-08 06:01:29 +00:00 (Migrated from gitlab.com)

mentioned in issue #27

mentioned in issue #27
PlasticDigits commented 2026-08-08 06:01:53 +00:00 (Migrated from gitlab.com)

mentioned in issue #28

mentioned in issue #28
PlasticDigits commented 2026-08-08 06:01:53 +00:00 (Migrated from gitlab.com)

marked as related to #28

marked as related to #28
PlasticDigits commented 2026-08-08 09:58:25 +00:00 (Migrated from gitlab.com)

mentioned in commit 695e577065

mentioned in commit 695e57706584abbdd69ad0fe01c934ddcfe8092c
PlasticDigits commented 2026-08-08 09:58:36 +00:00 (Migrated from gitlab.com)

mentioned in merge request !45

mentioned in merge request !45
PlasticDigits (Migrated from gitlab.com) closed this issue 2026-08-08 10:12:49 +00:00
PlasticDigits commented 2026-08-16 12:58:09 +00:00 (Migrated from gitlab.com)

mentioned in issue #32

mentioned in issue #32
PlasticDigits commented 2026-08-16 12:58:10 +00:00 (Migrated from gitlab.com)

marked as related to #32

marked as related to #32
PlasticDigits commented 2026-08-16 13:26:57 +00:00 (Migrated from gitlab.com)

mentioned in merge request !46

mentioned in merge request !46
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
code/ust1-window#23
No description provided.