syncdb_delete_block_TR.sql (7170B)
1 -- 2 -- This file is part of TALER 3 -- Copyright (C) 2026 Taler Systems SA 4 -- 5 -- TALER is free software; you can redistribute it and/or modify it under the 6 -- terms of the GNU General Public License as published by the Free Software 7 -- Foundation; either version 3, or (at your option) any later version. 8 -- 9 -- TALER is distributed in the hope that it will be useful, but WITHOUT ANY 10 -- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 11 -- A PARTICULAR PURPOSE. See the GNU General Public License for more details. 12 -- 13 -- You should have received a copy of the GNU General Public License along with 14 -- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> 15 -- 16 17 -- The deletion relinks both neighbours, so the client uploads their new 18 -- signatures (in_prev_upload_sig, in_next_upload_sig). Those signatures 19 -- cover the neighbours as the caller read them outside this 20 -- transaction, so the whole signed context has to be re-checked here or 21 -- a concurrent write could leave a neighbour with a signature over links 22 -- it no longer has: the transaction re-checks them against in_prev_hash 23 -- and in_prev_prev_nonce, resp. in_next_hash and in_next_next_nonce. 24 -- The signed refs_hash needs no separate check, since 25 -- sync_do_update_block() is the only thing that changes it and it always 26 -- changes the block hash along with it. 27 DROP FUNCTION IF EXISTS sync_do_delete_block; 28 CREATE FUNCTION sync_do_delete_block ( 29 IN in_account_pub BYTEA, 30 IN in_block_nonce BYTEA, 31 IN in_prev_nonce BYTEA, 32 IN in_next_nonce BYTEA, 33 IN in_block_hash BYTEA, 34 IN ina_object_uids BYTEA[], 35 IN ina_object_incs INT2[], 36 IN in_prev_upload_sig BYTEA, 37 IN in_prev_hash BYTEA, 38 IN in_prev_prev_nonce BYTEA, 39 IN in_next_upload_sig BYTEA, 40 IN in_next_hash BYTEA, 41 IN in_next_next_nonce BYTEA, 42 OUT out_account_missing BOOLEAN, 43 OUT out_block_missing BOOLEAN, 44 OUT out_outdated_write BOOLEAN, 45 OUT out_refs_missing BOOLEAN, 46 OUT out_negative_refcount BOOLEAN, 47 OUT out_deleted BOOLEAN, 48 OUT out_refs_updated BOOLEAN) 49 LANGUAGE plpgsql 50 AS $$ 51 DECLARE 52 my_prev_nonce BYTEA; 53 my_next_nonce BYTEA; 54 my_block_hash BYTEA; 55 my_n_hash BYTEA; 56 my_n_link BYTEA; 57 BEGIN 58 out_account_missing := FALSE; 59 out_block_missing := FALSE; 60 out_outdated_write := FALSE; 61 out_refs_missing := FALSE; 62 out_negative_refcount := FALSE; 63 out_deleted := FALSE; 64 out_refs_updated := FALSE; 65 66 -- Check if account exists and has valid payment 67 SELECT a.out_account_missing 68 INTO out_account_missing 69 FROM sync_check_account_payment(in_account_pub) AS a; 70 IF out_account_missing 71 THEN 72 RETURN; 73 END IF; 74 75 SELECT prev_nonce 76 ,next_nonce 77 ,block_hash 78 INTO my_prev_nonce 79 ,my_next_nonce 80 ,my_block_hash 81 FROM blocks 82 WHERE account_pub = in_account_pub 83 AND nonce = in_block_nonce; 84 85 IF NOT FOUND 86 THEN 87 out_block_missing := TRUE; 88 RETURN; 89 END IF; 90 91 -- Does existing hash matches expected old hash? 92 -- Does previous and next nonces match expectations? 93 IF my_block_hash IS DISTINCT FROM in_block_hash OR 94 my_prev_nonce IS DISTINCT FROM in_prev_nonce OR 95 my_next_nonce IS DISTINCT FROM in_next_nonce 96 THEN 97 out_outdated_write := TRUE; 98 RETURN; 99 END IF; 100 101 -- The neighbours must still look the way the relink signatures were 102 -- verified against (see header comment). Only the link this 103 -- transaction does not rewrite is checked; the one it does rewrite is 104 -- the block being deleted, which was checked above. 105 IF in_prev_nonce IS NOT NULL 106 THEN 107 SELECT block_hash 108 ,prev_nonce 109 INTO my_n_hash 110 ,my_n_link 111 FROM blocks 112 WHERE account_pub = in_account_pub 113 AND nonce = in_prev_nonce; 114 115 IF NOT FOUND 116 OR my_n_hash IS DISTINCT FROM in_prev_hash 117 OR my_n_link IS DISTINCT FROM in_prev_prev_nonce 118 THEN 119 out_outdated_write := TRUE; 120 RETURN; 121 END IF; 122 END IF; 123 124 IF in_next_nonce IS NOT NULL 125 THEN 126 SELECT block_hash 127 ,next_nonce 128 INTO my_n_hash 129 ,my_n_link 130 FROM blocks 131 WHERE account_pub = in_account_pub 132 AND nonce = in_next_nonce; 133 134 IF NOT FOUND 135 OR my_n_hash IS DISTINCT FROM in_next_hash 136 OR my_n_link IS DISTINCT FROM in_next_next_nonce 137 THEN 138 out_outdated_write := TRUE; 139 RETURN; 140 END IF; 141 END IF; 142 143 -- Check referenced objects exist and no increment underflows, 144 -- before we touch anything 145 SELECT c.out_negative_refcount 146 ,c.out_refs_missing 147 INTO out_negative_refcount 148 ,out_refs_missing 149 FROM sync_check_object_refcounts_pre(in_account_pub, 150 ina_object_uids, 151 ina_object_incs) AS c; 152 IF out_negative_refcount OR out_refs_missing 153 THEN 154 RETURN; 155 END IF; 156 157 -- Update previous block's next reference (if necessary); the 158 -- neighbours come from the block we are deleting, so they must exist. 159 -- Store the relink signature with the neighbour; a relink never 160 -- changes the data, so old_hash is set to the current block hash. 161 IF my_prev_nonce IS NOT NULL 162 THEN 163 UPDATE blocks 164 SET next_nonce = my_next_nonce 165 ,upload_sig = in_prev_upload_sig 166 ,old_hash = block_hash 167 WHERE account_pub = in_account_pub 168 AND nonce = my_prev_nonce; 169 170 IF NOT FOUND 171 THEN 172 RAISE EXCEPTION 'previous block % vanished for account %', 173 my_prev_nonce, in_account_pub; 174 END IF; 175 END IF; 176 177 -- Update next block's previous reference (if necessary) 178 IF my_next_nonce IS NOT NULL 179 THEN 180 UPDATE blocks 181 SET prev_nonce = my_prev_nonce 182 ,upload_sig = in_next_upload_sig 183 ,old_hash = block_hash 184 WHERE account_pub = in_account_pub 185 AND nonce = my_next_nonce; 186 187 IF NOT FOUND 188 THEN 189 RAISE EXCEPTION 'next block % vanished for account %', 190 my_next_nonce, in_account_pub; 191 END IF; 192 END IF; 193 194 -- Update first and last block references (if necessary) 195 UPDATE accounts 196 SET first_block = CASE 197 WHEN my_prev_nonce IS NULL 198 THEN my_next_nonce 199 ELSE first_block 200 END 201 ,last_block = CASE 202 WHEN my_next_nonce IS NULL 203 THEN my_prev_nonce 204 ELSE last_block 205 END 206 WHERE account_pub = in_account_pub; 207 208 -- Delete block 209 DELETE FROM blocks 210 WHERE account_pub = in_account_pub 211 AND nonce = in_block_nonce; 212 213 IF NOT FOUND 214 THEN 215 RAISE EXCEPTION 'block % vanished for account %', 216 in_block_nonce, in_account_pub; 217 END IF; 218 219 -- Update object refcounts and delete unreferenced objects; the 220 -- pre-check above means this can only fail on a concurrent change 221 SELECT u.out_negative_refcount 222 ,u.out_refs_updated 223 INTO out_negative_refcount 224 ,out_refs_updated 225 FROM sync_update_object_refcounts(in_account_pub, 226 ina_object_uids, 227 ina_object_incs) AS u; 228 IF NOT out_refs_updated 229 THEN 230 RAISE EXCEPTION 'object refcounts changed concurrently for account %', 231 in_account_pub; 232 END IF; 233 234 out_deleted := TRUE; 235 END $$;