exchange_do_deposit.sql (7864B)
1 -- 2 -- This file is part of TALER 3 -- Copyright (C) 2014--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 DROP FUNCTION IF EXISTS exchange_do_deposit; 18 CREATE FUNCTION exchange_do_deposit( 19 -- For batch_deposits 20 IN in_shard INT8, 21 IN in_merchant_pub BYTEA, 22 IN in_merchant_sig BYTEA, 23 IN in_wallet_timestamp INT8, 24 IN in_exchange_timestamp INT8, 25 IN in_refund_deadline INT8, 26 IN in_wire_deadline INT8, 27 IN in_h_contract_terms BYTEA, 28 IN in_wallet_data_hash BYTEA, -- can be NULL 29 IN in_wire_salt BYTEA, 30 IN in_wire_target_h_payto BYTEA, 31 IN in_h_normalized_payto BYTEA, 32 IN in_policy_details_serial_id INT8, -- can be NULL 33 IN in_policy_blocked BOOLEAN, 34 -- For wire_targets 35 IN in_receiver_wire_account TEXT, 36 -- For coin_deposits 37 IN ina_coin_pub BYTEA[], 38 IN ina_coin_sig BYTEA[], 39 IN ina_amount_with_fee taler_amount[], 40 IN ina_deposit_fee taler_amount[], 41 IN in_total_amount taler_amount, 42 IN in_total_without_fee taler_amount, 43 IN in_is_wallet BOOL, 44 IN in_extra_wire_subject_metadata TEXT, 45 OUT out_exchange_timestamp INT8, 46 OUT out_accumulated_total_without_fee taler_amount, 47 OUT out_insufficient_balance_coin_index INT4, -- index of coin with bad balance, NULL if none 48 OUT out_conflict BOOL 49 ) 50 LANGUAGE plpgsql 51 AS $$ 52 DECLARE 53 wtsi INT8; -- wire target serial id 54 bdsi INT8; -- batch_deposits serial id 55 i INT4; 56 my_record RECORD; 57 ini_amount_with_fee taler_amount; 58 ini_deposit_fee taler_amount; 59 ini_coin_pub BYTEA; 60 ini_coin_sig BYTEA; 61 my_update BOOL := FALSE; 62 my_total taler_amount; 63 my_total_without_fee taler_amount; 64 BEGIN 65 -- Shards: 66 -- INSERT wire_targets (by h_payto), ON CONFLICT DO NOTHING; 67 -- INSERT batch_deposits (by shard, merchant_pub), ON CONFLICT idempotency check; 68 -- INSERT[] coin_deposits (by coin_pub), ON CONFLICT idempotency check; 69 -- UPDATE[] known_coins (by coin_pub) 70 71 72 -- Make sure the kyc_target entry exists 73 INSERT INTO kyc_targets 74 (h_normalized_payto 75 ,is_wallet 76 ) VALUES ( 77 in_h_normalized_payto 78 ,in_is_wallet 79 ) 80 ON CONFLICT DO NOTHING; 81 82 -- First, get or create the 'wtsi' 83 INSERT INTO wire_targets 84 (wire_target_h_payto 85 ,h_normalized_payto 86 ,payto_uri 87 ) VALUES ( 88 in_wire_target_h_payto 89 ,in_h_normalized_payto 90 ,in_receiver_wire_account 91 ) 92 ON CONFLICT DO NOTHING -- for CONFLICT ON (wire_target_h_payto) 93 RETURNING wire_target_serial_id 94 INTO wtsi; 95 96 IF NOT FOUND 97 THEN 98 SELECT wire_target_serial_id 99 INTO wtsi 100 FROM wire_targets 101 WHERE wire_target_h_payto=in_wire_target_h_payto; 102 END IF; 103 104 105 -- Second, create the batch_deposits entry 106 INSERT INTO batch_deposits 107 (shard 108 ,merchant_pub 109 ,merchant_sig 110 ,wallet_timestamp 111 ,exchange_timestamp 112 ,refund_deadline 113 ,wire_deadline 114 ,h_contract_terms 115 ,wallet_data_hash 116 ,wire_salt 117 ,wire_target_h_payto 118 ,policy_details_serial_id 119 ,policy_blocked 120 ,total_amount 121 ,total_without_fee 122 ,extra_wire_subject_metadata 123 ) VALUES ( 124 in_shard 125 ,in_merchant_pub 126 ,in_merchant_sig 127 ,in_wallet_timestamp 128 ,in_exchange_timestamp 129 ,in_refund_deadline 130 ,in_wire_deadline 131 ,in_h_contract_terms 132 ,in_wallet_data_hash 133 ,in_wire_salt 134 ,in_wire_target_h_payto 135 ,in_policy_details_serial_id 136 ,in_policy_blocked 137 ,in_total_amount 138 ,in_total_without_fee 139 ,in_extra_wire_subject_metadata) 140 ON CONFLICT DO NOTHING -- for CONFLICT ON (merchant_pub, h_contract_terms) 141 RETURNING 142 batch_deposit_serial_id 143 INTO 144 bdsi; 145 146 IF FOUND 147 THEN 148 -- First batch deposit, we can skip all the 'my_update' logic! 149 out_accumulated_total_without_fee = in_total_without_fee; 150 ELSE 151 -- Idempotency check: see if an identical record exists. 152 -- We do select over merchant_pub, h_contract_terms and wire_target_h_payto 153 -- first to maximally increase the chance of using the existing index. 154 SELECT 155 exchange_timestamp 156 ,batch_deposit_serial_id 157 ,total_amount 158 ,total_without_fee 159 INTO 160 my_record 161 FROM batch_deposits 162 WHERE shard=in_shard 163 AND merchant_pub=in_merchant_pub 164 AND h_contract_terms=in_h_contract_terms 165 AND wire_target_h_payto=in_wire_target_h_payto 166 -- now check the rest, too 167 AND ( (wallet_data_hash=in_wallet_data_hash) OR 168 (wallet_data_hash IS NULL AND in_wallet_data_hash IS NULL) ) 169 AND wire_salt=in_wire_salt 170 AND wallet_timestamp=in_wallet_timestamp 171 AND refund_deadline=in_refund_deadline 172 AND wire_deadline=in_wire_deadline 173 AND ( (policy_details_serial_id=in_policy_details_serial_id) OR 174 (policy_details_serial_id IS NULL AND in_policy_details_serial_id IS NULL) ); 175 IF NOT FOUND 176 THEN 177 -- Deposit exists, but with *strange* differences. Not allowed. 178 out_conflict=TRUE; 179 RETURN; 180 END IF; 181 out_exchange_timestamp = my_record.exchange_timestamp; 182 bdsi = my_record.batch_deposit_serial_id; 183 my_total = my_record.total_amount; 184 my_total_without_fee = my_record.total_without_fee; 185 my_update = TRUE; 186 END IF; 187 188 out_conflict=FALSE; 189 190 -- Deposit each coin 191 192 FOR i IN 1..array_length(ina_coin_pub,1) 193 LOOP 194 ini_coin_pub = ina_coin_pub[i]; 195 ini_coin_sig = ina_coin_sig[i]; 196 ini_amount_with_fee = ina_amount_with_fee[i]; 197 ini_deposit_fee = ina_deposit_fee[i]; 198 199 INSERT INTO coin_deposits 200 (batch_deposit_serial_id 201 ,coin_pub 202 ,coin_sig 203 ,amount_with_fee 204 ) VALUES ( 205 bdsi 206 ,ini_coin_pub 207 ,ini_coin_sig 208 ,ini_amount_with_fee 209 ) 210 ON CONFLICT DO NOTHING; 211 212 IF FOUND 213 THEN 214 -- Insert did happen, update balance in known_coins! 215 UPDATE known_coins kc 216 SET 217 remaining.frac=(kc.remaining).frac-ini_amount_with_fee.frac 218 + CASE 219 WHEN (kc.remaining).frac < ini_amount_with_fee.frac 220 THEN 100000000 221 ELSE 0 222 END, 223 remaining.val=(kc.remaining).val-ini_amount_with_fee.val 224 - CASE 225 WHEN (kc.remaining).frac < ini_amount_with_fee.frac 226 THEN 1 227 ELSE 0 228 END 229 WHERE coin_pub=ini_coin_pub 230 AND ( ((kc.remaining).val > ini_amount_with_fee.val) OR 231 ( ((kc.remaining).frac >= ini_amount_with_fee.frac) AND 232 ((kc.remaining).val >= ini_amount_with_fee.val) ) ); 233 234 IF NOT FOUND 235 THEN 236 -- Insufficient balance. 237 -- Note: C arrays are 0 indexed, but i started at 1 238 out_insufficient_balance_coin_index=i-1; 239 RETURN; 240 END IF; 241 242 IF my_update 243 THEN 244 -- Prepare to update totals in batch_deposits 245 SELECT * 246 INTO my_total 247 FROM amount_add (my_total, 248 ini_amount_with_fee); 249 SELECT * 250 INTO my_total_without_fee 251 FROM amount_add (my_total_without_fee, 252 ini_amount_with_fee); 253 SELECT diff 254 INTO my_total_without_fee 255 FROM amount_left_minus_right (my_total_without_fee, 256 ini_deposit_fee); 257 END IF; 258 END IF; 259 END LOOP; -- end FOR all coins 260 261 IF my_update 262 THEN 263 UPDATE batch_deposits 264 SET total_amount = my_total 265 ,total_without_fee = my_total_without_fee 266 WHERE batch_deposit_serial_id = bdsi 267 AND merchant_pub = in_merchant_pub 268 AND h_contract_terms = in_h_contract_terms; 269 out_accumulated_total_without_fee = my_total_without_fee; 270 END IF; 271 272 END $$;