syncdb_append_block_TR.sql (6133B)
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 append relinks the previous tail, so the client uploads the 18 -- tail's new signature (in_prev_upload_sig). That signature covers the 19 -- tail as the caller read it outside this transaction, so the whole 20 -- signed context has to be re-checked here or a concurrent write could 21 -- leave the tail with a signature over links it no longer has: the 22 -- transaction re-checks the tail against in_prev_hash and 23 -- in_prev_prev_nonce. The signed refs_hash needs no separate check, 24 -- since sync_do_update_block() is the only thing that changes it and it 25 -- always changes the block hash along with it. 26 DROP FUNCTION IF EXISTS sync_do_append_block; 27 CREATE FUNCTION sync_do_append_block ( 28 IN in_account_pub BYTEA, 29 IN in_curr_nonce BYTEA, 30 IN in_prev_nonce BYTEA, 31 IN in_data_hash BYTEA, 32 IN in_data_blob BYTEA, 33 IN ina_object_uids BYTEA[], 34 IN ina_object_incs INT2[], 35 IN in_upload_sig BYTEA, 36 IN in_refs_hash BYTEA, 37 IN in_prev_upload_sig BYTEA, 38 IN in_prev_hash BYTEA, 39 IN in_prev_prev_nonce BYTEA, 40 OUT out_account_missing BOOLEAN, 41 OUT out_outdated_append BOOLEAN, 42 OUT out_negative_refcount BOOLEAN, 43 OUT out_refs_missing BOOLEAN, 44 OUT out_inserted BOOLEAN, 45 OUT out_refs_updated BOOLEAN) 46 LANGUAGE plpgsql 47 AS $$ 48 DECLARE 49 my_last_block_nonce BYTEA; 50 my_prev_hash BYTEA; 51 my_prev_prev_nonce BYTEA; 52 BEGIN 53 out_account_missing := FALSE; 54 out_negative_refcount := FALSE; 55 out_refs_missing := FALSE; 56 out_outdated_append := FALSE; 57 out_inserted := FALSE; 58 out_refs_updated := FALSE; 59 60 -- Check if account exists and has valid payment 61 SELECT a.out_account_missing 62 INTO out_account_missing 63 FROM sync_check_account_payment(in_account_pub) AS a; 64 IF out_account_missing 65 THEN 66 RETURN; 67 END IF; 68 69 -- Get current tail of the DLL 70 SELECT last_block 71 INTO my_last_block_nonce 72 FROM accounts 73 WHERE account_pub = in_account_pub; 74 75 -- Ensure that block is being appended to the end 76 -- (or beginning if backup is empty) 77 IF NOT (in_prev_nonce IS NOT DISTINCT FROM my_last_block_nonce) 78 THEN 79 out_outdated_append := TRUE; 80 RETURN; 81 END IF; 82 83 IF in_prev_nonce IS NOT NULL 84 THEN 85 -- The tail must still look the way the relink signature was 86 -- verified against (see header comment). Its next_nonce needs no 87 -- check: it is the account's last block, so it has none, and this 88 -- transaction is what sets it. 89 SELECT block_hash 90 ,prev_nonce 91 INTO my_prev_hash 92 ,my_prev_prev_nonce 93 FROM blocks 94 WHERE account_pub = in_account_pub 95 AND nonce = in_prev_nonce; 96 97 IF NOT FOUND 98 OR my_prev_hash IS DISTINCT FROM in_prev_hash 99 OR my_prev_prev_nonce IS DISTINCT FROM in_prev_prev_nonce 100 THEN 101 out_outdated_append := TRUE; 102 RETURN; 103 END IF; 104 END IF; 105 106 -- Check referenced objects exist and no increment underflows, 107 -- before we touch anything 108 SELECT c.out_negative_refcount 109 ,c.out_refs_missing 110 INTO out_negative_refcount 111 ,out_refs_missing 112 FROM sync_check_object_refcounts_pre(in_account_pub, 113 ina_object_uids, 114 ina_object_incs) AS c; 115 IF out_negative_refcount OR out_refs_missing 116 THEN 117 RETURN; 118 END IF; 119 120 -- Try to append the new block 121 INSERT INTO blocks 122 (nonce 123 ,account_pub 124 ,block_hash 125 ,prev_nonce 126 ,data_blob 127 ,upload_sig 128 ,old_hash 129 ,refs_hash) 130 VALUES 131 (in_curr_nonce 132 ,in_account_pub 133 ,in_data_hash 134 ,in_prev_nonce 135 ,in_data_blob 136 ,in_upload_sig 137 ,decode(repeat('00', 64), 'hex') 138 ,in_refs_hash) 139 ON CONFLICT DO NOTHING; 140 141 IF NOT FOUND 142 THEN 143 out_inserted := FALSE; 144 RETURN; 145 ELSE 146 out_inserted := TRUE; 147 END IF; 148 149 -- Update the reference in the previous block; it was checked to be 150 -- this account's tail above. Store the tail's relink signature with 151 -- it; a relink never changes the data, so old_hash is set to the 152 -- current block hash. 153 IF in_prev_nonce IS NOT NULL 154 THEN 155 UPDATE blocks 156 SET next_nonce = in_curr_nonce 157 ,upload_sig = in_prev_upload_sig 158 ,old_hash = block_hash 159 WHERE account_pub = in_account_pub 160 AND nonce = in_prev_nonce; 161 162 IF NOT FOUND 163 THEN 164 RAISE EXCEPTION 'previous block % vanished for account %', 165 in_prev_nonce, in_account_pub; 166 END IF; 167 END IF; 168 169 -- Update reference to first and last block in the backup 170 UPDATE accounts 171 SET last_block = in_curr_nonce 172 ,first_block = CASE 173 -- if first block in the backup, update reference 174 WHEN in_prev_nonce IS NULL 175 THEN in_curr_nonce 176 -- otherwise, keep reference intact 177 ELSE first_block 178 END 179 WHERE account_pub = in_account_pub; 180 181 IF NOT FOUND 182 THEN 183 RAISE EXCEPTION 'account % vanished', in_account_pub; 184 END IF; 185 186 -- Update object refcounts and delete unreferenced objects; the 187 -- pre-check above means this can only fail on a concurrent change 188 SELECT u.out_negative_refcount 189 ,u.out_refs_updated 190 INTO out_negative_refcount 191 ,out_refs_updated 192 FROM sync_update_object_refcounts(in_account_pub, 193 ina_object_uids, 194 ina_object_incs) AS u; 195 IF NOT out_refs_updated 196 THEN 197 RAISE EXCEPTION 'object refcounts changed concurrently for account %', 198 in_account_pub; 199 END IF; 200 END $$;