exchange_do_withdraw.sql (6397B)
1 -- 2 -- This file is part of TALER 3 -- Copyright (C) 2023-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 DROP FUNCTION IF EXISTS exchange_do_withdraw; 17 18 CREATE FUNCTION exchange_do_withdraw( 19 IN in_amount_with_fee taler_amount, 20 IN in_reserve_pub BYTEA, 21 IN in_reserve_sig BYTEA, 22 IN in_now INT8, 23 IN in_min_reserve_gc INT8, 24 IN in_planchets_h BYTEA, 25 IN in_maximum_age_committed INT2, -- in years ϵ [0,1..), possibly NULL 26 IN in_noreveal_index INT2, -- possibly NULL (if not age-withdraw) 27 IN in_selected_h BYTEA, -- possibly NULL (if not age-withdraw) 28 IN in_denom_serials INT8[], 29 IN in_denom_sigs BYTEA[], 30 IN in_blinding_seed BYTEA, -- possibly NULL (if no CS denominations) 31 IN in_cs_r_values BYTEA[], -- possibly NULL (if no CS denominations) 32 IN in_cs_r_choices INT8, -- possibly NULL (if no CS denominations) 33 OUT out_reserve_found BOOLEAN, 34 OUT out_balance_ok BOOLEAN, 35 OUT out_reserve_balance taler_amount, 36 OUT out_age_ok BOOLEAN, 37 OUT out_required_age INT2, -- in years ϵ [0,1..) 38 OUT out_reserve_birthday INT4, 39 OUT out_idempotent BOOLEAN, 40 OUT out_noreveal_index INT2, -- possibly NULL (if not age-withdraw) 41 OUT out_nonce_reuse BOOLEAN) 42 LANGUAGE plpgsql 43 AS $$ 44 DECLARE 45 my_reserve RECORD; 46 my_difference RECORD; 47 my_balance taler_amount; 48 my_not_before DATE; 49 my_earliest_date DATE; 50 BEGIN 51 -- Shards: reserves by reserve_pub (SELECT) 52 -- reserves by reserve_pub (UPDATE) 53 54 -- First, find the reserve 55 SELECT current_balance 56 ,birthday 57 ,gc_date 58 INTO my_reserve 59 FROM reserves 60 WHERE reserve_pub=in_reserve_pub; 61 out_reserve_found = FOUND; 62 63 IF NOT out_reserve_found 64 THEN 65 out_age_ok = FALSE; 66 out_required_age = -1; 67 out_idempotent = FALSE; 68 out_noreveal_index = -1; 69 out_reserve_balance.val = 0; 70 out_reserve_balance.frac = 0; 71 out_balance_ok = FALSE; 72 out_nonce_reuse = FALSE; 73 out_reserve_birthday = 0; 74 RETURN; 75 END IF; 76 77 out_reserve_balance = my_reserve.current_balance; 78 out_reserve_birthday = my_reserve.birthday; 79 80 -- FIXME-performance: probably better to INSERT and on-conflict check for idempotency... 81 82 -- Next, check for idempotency of the withdraw 83 SELECT noreveal_index 84 INTO out_noreveal_index 85 FROM withdraw 86 WHERE reserve_pub = in_reserve_pub 87 AND planchets_h = in_planchets_h; 88 out_idempotent = FOUND; 89 90 IF out_idempotent 91 THEN 92 -- out_idempotent set, out_noreveal_index possibly set, report. 93 out_balance_ok = TRUE; 94 out_age_ok = TRUE; 95 out_required_age = -1; 96 out_nonce_reuse = FALSE; 97 RETURN; 98 END IF; 99 100 out_noreveal_index = -1; 101 102 -- Check age requirements 103 IF (my_reserve.birthday <> 0) 104 THEN 105 my_not_before=date '1970-01-01' + my_reserve.birthday; 106 my_earliest_date = current_date - make_interval(in_maximum_age_committed); 107 -- 108 -- 1970-01-01 + birthday == my_not_before now 109 -- | | | 110 -- <.......not allowed......>[<.....allowed range......>] 111 -- | | | 112 -- ____*_____________________*_________*________________* timeline 113 -- | 114 -- my_earliest_date == 115 -- now - maximum_age_committed*year 116 -- 117 IF ( (in_maximum_age_committed IS NULL) OR 118 (my_earliest_date < my_not_before) ) 119 THEN 120 out_required_age = extract(year FROM age(current_date, my_not_before)); 121 out_age_ok = FALSE; 122 out_balance_ok = TRUE; -- not really 123 out_nonce_reuse = FALSE; -- not really 124 RETURN; 125 END IF; 126 END IF; 127 128 out_age_ok = TRUE; 129 out_required_age = 0; 130 131 -- Check reserve balance is sufficient. 132 SELECT * 133 INTO my_difference 134 FROM amount_left_minus_right(out_reserve_balance 135 ,in_amount_with_fee); 136 137 out_balance_ok = my_difference.ok; 138 IF NOT out_balance_ok 139 THEN 140 out_nonce_reuse = FALSE; -- not yet determined 141 RETURN; 142 END IF; 143 144 my_balance = my_difference.diff; 145 146 -- Calculate new expiration dates. 147 in_min_reserve_gc=GREATEST(in_min_reserve_gc,my_reserve.gc_date); 148 149 -- Update reserve balance. 150 UPDATE reserves SET 151 gc_date=in_min_reserve_gc 152 ,current_balance=my_balance 153 WHERE 154 reserve_pub=in_reserve_pub; 155 156 -- Ensure the uniqueness of the blinding_seed 157 IF in_blinding_seed IS NOT NULL 158 THEN 159 INSERT INTO unique_withdraw_blinding_seed 160 (blinding_seed) 161 VALUES 162 (in_blinding_seed) 163 ON CONFLICT DO NOTHING; 164 165 IF NOT FOUND 166 THEN 167 out_nonce_reuse = TRUE; 168 RETURN; 169 END IF; 170 END IF; 171 172 out_nonce_reuse = FALSE; 173 174 -- Write the data into the withdraw table 175 INSERT INTO withdraw 176 (planchets_h 177 ,execution_date 178 ,max_age 179 ,amount_with_fee 180 ,reserve_pub 181 ,reserve_sig 182 ,noreveal_index 183 ,denom_serials 184 ,selected_h 185 ,blinding_seed 186 ,cs_r_values 187 ,cs_r_choices 188 ,denom_sigs) 189 VALUES 190 (in_planchets_h 191 ,in_now 192 ,in_maximum_age_committed 193 ,in_amount_with_fee 194 ,in_reserve_pub 195 ,in_reserve_sig 196 ,in_noreveal_index 197 ,in_denom_serials 198 ,in_selected_h 199 ,in_blinding_seed 200 ,in_cs_r_values 201 ,in_cs_r_choices 202 ,in_denom_sigs) 203 ON CONFLICT DO NOTHING; 204 205 IF NOT FOUND 206 THEN 207 RAISE EXCEPTION 'Conflict on insert into withdraw despite idempotency check for reserve_pub(%) and planchets_h(%)!', 208 in_reserve_pub, 209 in_planchets_h; 210 END IF; 211 212 END $$; 213 214 COMMENT ON FUNCTION exchange_do_withdraw( 215 taler_amount, 216 BYTEA, 217 BYTEA, 218 INT8, 219 INT8, 220 BYTEA, 221 INT2, 222 INT2, 223 BYTEA, 224 INT8[], 225 BYTEA[], 226 BYTEA, 227 BYTEA[], 228 INT8) 229 IS 'Checks whether the reserve has sufficient balance for an withdraw operation (or the request is repeated and was previously approved) and that age requirements are met. If so updates the database with the result. Includes storing the hashes of all blinded planchets, (separately) the hashes of the chosen planchets and denomination signatures, or signaling idempotency (and previous noreveal_index) or nonce reuse';