0002-recoup.sql (6705B)
1 -- 2 -- This file is part of TALER 3 -- Copyright (C) 2025 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 CREATE FUNCTION create_table_recoup( 18 IN partition_suffix TEXT DEFAULT NULL 19 ) 20 RETURNS VOID 21 LANGUAGE plpgsql 22 AS $$ 23 DECLARE 24 table_name TEXT DEFAULT 'recoup'; 25 BEGIN 26 27 PERFORM create_partitioned_table( 28 'CREATE TABLE %I' 29 '(recoup_uuid BIGINT GENERATED BY DEFAULT AS IDENTITY' 30 ',coin_pub BYTEA NOT NULL CHECK (LENGTH(coin_pub)=32)' 31 ',coin_sig BYTEA NOT NULL CHECK(LENGTH(coin_sig)=64)' 32 ',coin_blind BYTEA NOT NULL CHECK(LENGTH(coin_blind)=32)' 33 ',amount taler_amount NOT NULL' 34 ',recoup_timestamp INT8 NOT NULL' 35 ',withdraw_id INT8 NOT NULL' 36 ') %s ;' 37 ,table_name 38 ,'PARTITION BY HASH (coin_pub);' 39 ,partition_suffix 40 ); 41 PERFORM comment_partitioned_table( 42 'Information about recoups that were executed between a coin and a reserve. In this type of recoup, the amount is credited back to the reserve from which the coin originated.' 43 ,table_name 44 ,partition_suffix 45 ); 46 PERFORM comment_partitioned_column( 47 'Coin that is being debited in the recoup. Do not CASCADE ON DROP on the coin_pub, as we may keep the coin alive!' 48 ,'coin_pub' 49 ,table_name 50 ,partition_suffix 51 ); 52 PERFORM comment_partitioned_column( 53 'Identifies the h_commitment of the recouped coin and provides the link to the credited reserve.' 54 ,'withdraw_id' 55 ,table_name 56 ,partition_suffix 57 ); 58 PERFORM comment_partitioned_column( 59 'Signature by the coin affirming the recoup, of type TALER_SIGNATURE_WALLET_COIN_RECOUP' 60 ,'coin_sig' 61 ,table_name 62 ,partition_suffix 63 ); 64 PERFORM comment_partitioned_column( 65 'Denomination blinding key used when creating the blinded coin from the planchet. Secret revealed during the recoup to provide the linkage between the coin and the withdraw operation.' 66 ,'coin_blind' 67 ,table_name 68 ,partition_suffix 69 ); 70 END 71 $$; 72 73 74 CREATE FUNCTION constrain_table_recoup( 75 IN partition_suffix TEXT 76 ) 77 RETURNS VOID 78 LANGUAGE plpgsql 79 AS $$ 80 DECLARE 81 table_name TEXT DEFAULT 'recoup'; 82 BEGIN 83 table_name = concat_ws('_', table_name, partition_suffix); 84 EXECUTE FORMAT ( 85 'CREATE INDEX ' || table_name || '_by_coin_pub_index ' 86 'ON ' || table_name || ' ' 87 '(coin_pub);' 88 ); 89 EXECUTE FORMAT ( 90 'ALTER TABLE ' || table_name || 91 ' ADD CONSTRAINT ' || table_name || '_recoup_uuid_key' 92 ' UNIQUE (recoup_uuid) ' 93 ); 94 END 95 $$; 96 97 98 CREATE OR REPLACE FUNCTION foreign_table_recoup() 99 RETURNS VOID 100 LANGUAGE plpgsql 101 AS $$ 102 DECLARE 103 table_name TEXT DEFAULT 'recoup'; 104 BEGIN 105 EXECUTE FORMAT ( 106 'ALTER TABLE ' || table_name || 107 ' ADD CONSTRAINT ' || table_name || '_foreign_withdraw' 108 ' FOREIGN KEY (withdraw_id) ' 109 ' REFERENCES withdraw (withdraw_id) ON DELETE CASCADE' 110 ',ADD CONSTRAINT ' || table_name || '_foreign_coin_pub' 111 ' FOREIGN KEY (coin_pub) ' 112 ' REFERENCES known_coins (coin_pub)' 113 ); 114 END 115 $$; 116 117 118 CREATE FUNCTION create_table_recoup_by_reserve( 119 IN partition_suffix TEXT DEFAULT NULL 120 ) 121 RETURNS VOID 122 LANGUAGE plpgsql 123 AS $$ 124 DECLARE 125 table_name TEXT DEFAULT 'recoup_by_reserve'; 126 BEGIN 127 PERFORM create_partitioned_table( 128 'CREATE TABLE %I' 129 '(withdraw_id INT8 NOT NULL' -- REFERENCES withdraw (withdraw_id) ON DELETE CASCADE 130 ',coin_pub BYTEA CHECK (LENGTH(coin_pub)=32)' -- REFERENCES known_coins (coin_pub) 131 ') %s ;' 132 ,table_name 133 ,'PARTITION BY HASH (withdraw_id)' 134 ,partition_suffix 135 ); 136 PERFORM comment_partitioned_table( 137 'Information in this table is strictly redundant with that of recoup, but saved by a different primary key for fast lookups by withdraw_id.' 138 ,table_name 139 ,partition_suffix 140 ); 141 END 142 $$; 143 144 145 CREATE FUNCTION constrain_table_recoup_by_reserve( 146 IN partition_suffix TEXT DEFAULT NULL 147 ) 148 RETURNS VOID 149 LANGUAGE plpgsql 150 AS $$ 151 DECLARE 152 table_name TEXT DEFAULT 'recoup_by_reserve'; 153 BEGIN 154 table_name = concat_ws('_', table_name, partition_suffix); 155 EXECUTE FORMAT ( 156 'CREATE INDEX ' || table_name || '_main_index ' 157 'ON ' || table_name || ' ' 158 '(withdraw_id);' 159 ); 160 END 161 $$; 162 163 164 CREATE OR REPLACE FUNCTION recoup_insert_trigger() 165 RETURNS trigger 166 LANGUAGE plpgsql 167 AS $$ 168 BEGIN 169 INSERT INTO recoup_by_reserve 170 (withdraw_id 171 ,coin_pub) 172 VALUES 173 (NEW.withdraw_id 174 ,NEW.coin_pub); 175 INSERT INTO coin_history 176 (coin_pub 177 ,table_name 178 ,serial_id) 179 VALUES 180 (NEW.coin_pub 181 ,'recoup' 182 ,NEW.recoup_uuid); 183 INSERT INTO reserve_history 184 (reserve_pub 185 ,table_name 186 ,serial_id) 187 SELECT 188 res.reserve_pub 189 ,'recoup' 190 ,NEW.recoup_uuid 191 FROM withdraw wd 192 JOIN reserves res 193 USING (reserve_pub) 194 WHERE wd.withdraw_id = NEW.withdraw_id; 195 RETURN NEW; 196 END $$; 197 COMMENT ON FUNCTION recoup_insert_trigger() 198 IS 'Replicates recoup inserts into recoup_by_reserve table and updates the coin_history table.'; 199 200 201 CREATE OR REPLACE FUNCTION recoup_delete_trigger() 202 RETURNS trigger 203 LANGUAGE plpgsql 204 AS $$ 205 BEGIN 206 DELETE FROM recoup_by_reserve 207 WHERE withdraw_id = OLD.withdraw_id 208 AND coin_pub = OLD.coin_pub; 209 RETURN OLD; 210 END $$; 211 COMMENT ON FUNCTION recoup_delete_trigger() 212 IS 'Replicate recoup deletions into recoup_by_reserve table.'; 213 214 215 CREATE FUNCTION master_table_recoup() 216 RETURNS VOID 217 LANGUAGE plpgsql 218 AS $$ 219 BEGIN 220 CREATE TRIGGER recoup_on_insert 221 AFTER INSERT 222 ON recoup 223 FOR EACH ROW EXECUTE FUNCTION recoup_insert_trigger(); 224 CREATE TRIGGER recoup_on_delete 225 AFTER DELETE 226 ON recoup 227 FOR EACH ROW EXECUTE FUNCTION recoup_delete_trigger(); 228 END 229 $$; 230 231 232 INSERT INTO exchange_tables 233 (name 234 ,version 235 ,action 236 ,partitioned 237 ,by_range) 238 VALUES 239 ('recoup' 240 ,'exchange-0002' 241 ,'create' 242 ,TRUE 243 ,FALSE), 244 ('recoup' 245 ,'exchange-0002' 246 ,'constrain' 247 ,TRUE 248 ,FALSE), 249 ('recoup' 250 ,'exchange-0002' 251 ,'foreign' 252 ,TRUE 253 ,FALSE), 254 ('recoup_by_reserve' 255 ,'exchange-0002' 256 ,'create' 257 ,TRUE 258 ,FALSE), 259 ('recoup_by_reserve' 260 ,'exchange-0002' 261 ,'constrain' 262 ,TRUE 263 ,FALSE), 264 ('recoup' 265 ,'exchange-0002' 266 ,'master' 267 ,TRUE 268 ,FALSE);