11//! Typed persistence for the gateway's append-only tables.
22//!
3- //! `raw_weight_snapshot` and `epoch_bundle` are `SELECT`/`INSERT` only for the
4- //! application role, so every helper here is an insert or a read — never an
5- //! update. All uniqueness and shape invariants are enforced by the schema
6- //! (`0001_init.sql`, `0002_epoch_bundle_revision.sql`); the Rust side only
7- //! feeds them and interprets the conflicts they raise.
3+ //! `epoch_bundle` and `peer_root_statement` stay `SELECT`/`INSERT` only for
4+ //! the application role. `raw_weight_snapshot` inserts go through
5+ //! [`insert_raw_weight`] / tip supersede via the `upsert_raw_weight_tip`
6+ //! SECURITY DEFINER helper (no direct `UPDATE` grant on the table). Bundle
7+ //! reseal appends a new `epoch_bundle.revision`. Schema invariants live in
8+ //! `0001_init.sql`, `0002_epoch_bundle_revision.sql`,
9+ //! `0017_raw_weight_tip_supersede.sql`.
810
911use sqlx:: PgPool ;
1012use uuid:: Uuid ;
@@ -67,11 +69,14 @@ pub struct RawWeightRecord {
6769 pub signature : Vec < u8 > ,
6870}
6971
70- /// Append one raw-weight leaf.
72+ /// Insert or tip-supersede one raw-weight leaf.
7173///
72- /// Returns `Ok(None)` when `(challenge_id, epoch, miner_hotkey)` is already
73- /// stored — `raw_weight_snapshot_challenge_epoch_miner_unique` is what makes a
74- /// retried submission a conflict instead of a duplicate.
74+ /// Returns `Ok(Some(id))` when a row was inserted or replaced because
75+ /// `payload_digest` changed. Returns `Ok(None)` when the unique key already
76+ /// holds an identical digest (idempotent replay → HTTP 409).
77+ ///
78+ /// Tip supersede runs via `upsert_raw_weight_tip` so `base_app` never needs a
79+ /// direct `UPDATE` grant on `raw_weight_snapshot`.
7580///
7681/// # Errors
7782///
@@ -81,28 +86,28 @@ pub async fn insert_raw_weight(
8186 pool : & PgPool ,
8287 row : & NewRawWeight < ' _ > ,
8388) -> Result < Option < Uuid > , DbError > {
84- let id = sqlx:: query_scalar!(
85- r#"
86- INSERT INTO raw_weight_snapshot
87- (id, challenge_id, epoch, miner_hotkey, kind, score, absence_reason,
88- payload, payload_digest, signature, nonce)
89- VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)
90- ON CONFLICT (challenge_id, epoch, miner_hotkey) DO NOTHING
91- RETURNING id
92- "# ,
93- row. id,
94- row. challenge_id,
95- row. epoch,
96- row. miner_hotkey,
97- row. kind,
98- row. score,
99- row. absence_reason,
100- row. payload,
101- row. payload_digest,
102- row. signature,
103- row. nonce,
89+ // Runtime query: return type is `Option<Uuid>` from the tip-supersede
90+ // helper (NULL = identical digest). Avoids regenerating sqlx offline
91+ // metadata for a SECURITY DEFINER function signature.
92+ let id: Option < Uuid > = sqlx:: query_scalar (
93+ r"
94+ SELECT upsert_raw_weight_tip(
95+ $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11
96+ )
97+ " ,
10498 )
105- . fetch_optional ( pool)
99+ . bind ( row. id )
100+ . bind ( row. challenge_id )
101+ . bind ( row. epoch )
102+ . bind ( row. miner_hotkey )
103+ . bind ( row. kind )
104+ . bind ( row. score )
105+ . bind ( row. absence_reason )
106+ . bind ( row. payload )
107+ . bind ( row. payload_digest )
108+ . bind ( row. signature )
109+ . bind ( row. nonce )
110+ . fetch_one ( pool)
106111 . await ?;
107112 Ok ( id)
108113}
0 commit comments