From fc19a031e8a4e65ce6b74e7cf1e4c0a56ffd397e Mon Sep 17 00:00:00 2001 From: Julien Lucca Date: Sat, 8 Aug 2026 14:52:01 +0200 Subject: [PATCH] docs(remediation): correct why two chain claims had no DB row MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The generator blamed claimAction's (created_tx, action_id, claimer_id) dedup guard collapsing two claimactions sent in one transaction. Checked against /v1/history/get_actions: both cases (chain 19871 and 19904) are two SEPARATE transactions that landed in the SAME block, same claimer, same action, identical proof_photo — a double submit. Different created_tx, so that guard never matched them and is not the cause. The likelier mechanism is the old serial fallback: a truncated chain read made the resolver give up, the serial it fell back to collided with an id already taken, and the insert's `.catch(e => logError(...))` swallowed the failure so the row disappeared without stopping the block. Dropping the fallback closes that path. Also records that the backfilled rows leave created_block/created_tx/ created_eos_account NULL and must be filled from the history API afterwards: the GraphQL :claim type marks them non_null, so a single NULL row nullifies a validator's entire claims list in the Elm app. Hit exactly that on prod today after running the remediation; both rows have since been backfilled from /v1/history/get_actions and prod-anomaly-check.sh is clean. Co-Authored-By: Claude Opus 5 --- scripts/build-claims-id-remediation.py | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/scripts/build-claims-id-remediation.py b/scripts/build-claims-id-remediation.py index 324f679..c2f015e 100644 --- a/scripts/build-claims-id-remediation.py +++ b/scripts/build-claims-id-remediation.py @@ -167,12 +167,24 @@ def main(): if backfill: p(""" --- Chain claims that never got a DB row: two claimactions for the same --- (action, claimer) in ONE transaction, collapsed by claimAction's --- (created_tx, action_id, claimer_id) dedup guard. created_block/created_tx are --- not recoverable from table data and stay NULL. Status is the CHAIN status; --- the votes these claims received were recorded against whichever row held the --- id at the time, so no checks are synthesised here.""") +-- Chain claims that never got a DB row. Both known cases (chain 19871, 19904) +-- are the second of two claimactions the same claimer sent for the same action +-- with an identical proof_photo -- a double submit. They are NOT same-transaction +-- duplicates: each pair is two SEPARATE transactions that happened to land in one +-- block, so claimAction's (created_tx, action_id, claimer_id) dedup guard never +-- saw them as duplicates and is not what dropped them. +-- +-- What most likely dropped them: the old resolver hit a truncated chain read, +-- fell back to a DB serial, and that serial collided with an id already taken -- +-- and the insert sat behind `.catch(e => logError(...))`, so the row vanished +-- without stopping the block. Removing the serial fallback closes this. +-- +-- created_block/created_tx/created_eos_account are NOT in the chain `claim` +-- table, so this INSERT leaves them NULL and they must be backfilled from +-- /v1/history/get_actions afterwards -- the GraphQL :claim type marks them +-- non_null, and one NULL row nullifies a whole claims list in the Elm app. +-- Status is the CHAIN status; votes these claims received were recorded against +-- whichever row held the id at the time, so no checks are synthesised here.""") p('INSERT INTO claims (id, action_id, claimer_id, status, proof_photo, proof_code, created_at) VALUES') rows = [] for cid in backfill: