From f29dfda8cdf785181040eb7137178633dae10508 Mon Sep 17 00:00:00 2001 From: Julien Lucca Date: Sat, 29 Aug 2026 21:48:53 +0200 Subject: [PATCH] fix(provenance): stamp last_tx/last_eos_account on objective and action writes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `created_tx` / `created_eos_account` are written once, on the create, and this updater deliberately never rewrites them on an edit — the create-path replay guard keys on `created_tx`, so rewriting it would make a later replay re-run the create and duplicate the row. That stays exactly as it was. The side effect was on the backend: its DbListener announces an indexed write on `user_tx:` carrying `created_tx`, and TxSignerLive confirms only on an exact hash match. For an EDIT that meant the announcement went to the original creator under the original creation hash, so the member who actually signed the edit could never see their transaction confirm — the tracker stalled at 30s even though the chain write had landed. Closing a 2021 action as a different admin hit this every time. `last_tx` / `last_eos_account` carry the writer of the current row version alongside the untouched create provenance, so the replay guard is unaffected and the backend can confirm to whoever signed. Stamped on both paths: on a create they simply equal the created_* pair. Needs the backend migration adding the two nullable columns (cambiatus/backend#434). Deploy the backend first — until the columns exist these keys would be rejected on write. Co-Authored-By: Claude Opus 5 --- src/updaters/community.js | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/updaters/community.js b/src/updaters/community.js index 52115fc..31e5782 100644 --- a/src/updaters/community.js +++ b/src/updaters/community.js @@ -322,7 +322,13 @@ async function upsertObjective (db, payload, blockInfo, _context) { let data = { community_id: payload.data.community_id, creator_id: payload.data.editor, - description: payload.data.description + description: payload.data.description, + // Who wrote THIS row version. created_* is stamped once on the create and never + // rewritten (the replay guard below keys on created_tx), so without this an edit + // was announced to the original creator under the original hash and the signer's + // tx tracker could never match it. + last_tx: payload.transactionId, + last_eos_account: payload.authorization[0].actor } if (payload.data.objective_id > 0) { @@ -424,6 +430,9 @@ function upsertAction (db, payload, blockInfo, _context) { objective_id: payload.data.objective_id, creator_id: payload.data.creator, description: payload.data.description, + // Who wrote THIS row version — see the objective updater above. + last_tx: payload.transactionId, + last_eos_account: payload.authorization[0].actor, reward: rewardAmount, verifier_reward: verifierAmount, is_completed: false,