1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
|
--
-- This file is part of TALER
-- Copyright (C) 2014--2022 Taler Systems SA
--
-- TALER is free software; you can redistribute it and/or modify it under the
-- terms of the GNU General Public License as published by the Free Software
-- Foundation; either version 3, or (at your option) any later version.
--
-- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
-- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
-- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
--
-- You should have received a copy of the GNU General Public License along with
-- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
--
CREATE OR REPLACE FUNCTION exchange_do_purse_deposit(
IN in_partner_id INT8,
IN in_purse_pub BYTEA,
IN in_amount_with_fee taler_amount,
IN in_coin_pub BYTEA,
IN in_coin_sig BYTEA,
IN in_amount_without_fee taler_amount,
IN in_reserve_expiration INT8,
IN in_now INT8,
OUT out_balance_ok BOOLEAN,
OUT out_late BOOLEAN,
OUT out_conflict BOOLEAN)
LANGUAGE plpgsql
AS $$
DECLARE
was_merged BOOLEAN;
DECLARE
psi INT8; -- partner's serial ID (set if merged)
DECLARE
my_amount taler_amount; -- total in purse
DECLARE
was_paid BOOLEAN;
DECLARE
my_in_reserve_quota BOOLEAN;
DECLARE
my_reserve_pub BYTEA;
DECLARE
rval RECORD;
BEGIN
-- Store the deposit request.
INSERT INTO purse_deposits
(partner_serial_id
,purse_pub
,coin_pub
,amount_with_fee
,coin_sig)
VALUES
(in_partner_id
,in_purse_pub
,in_coin_pub
,in_amount_with_fee
,in_coin_sig)
ON CONFLICT DO NOTHING;
IF NOT FOUND
THEN
-- Idempotency check: check if coin_sig is the same,
-- if so, success, otherwise conflict!
PERFORM
FROM purse_deposits
WHERE purse_pub = in_purse_pub
AND coin_pub = in_coin_pub
AND coin_sig = in_coin_sig;
IF NOT FOUND
THEN
-- Deposit exists, but with differences. Not allowed.
out_balance_ok=FALSE;
out_late=FALSE;
out_conflict=TRUE;
RETURN;
ELSE
-- Deposit exists, do not count for balance. Allow.
out_late=FALSE;
out_balance_ok=TRUE;
out_conflict=FALSE;
RETURN;
END IF;
END IF;
-- Check if purse was deleted, if so, abort and prevent deposit.
PERFORM
FROM exchange.purse_deletion
WHERE purse_pub = in_purse_pub;
IF FOUND
THEN
out_late=TRUE;
out_balance_ok=FALSE;
out_conflict=FALSE;
RETURN;
END IF;
-- Debit the coin
-- Check and update balance of the coin.
UPDATE known_coins kc
SET
remaining.frac=(kc.remaining).frac-in_amount_with_fee.frac
+ CASE
WHEN (kc.remaining).frac < in_amount_with_fee.frac
THEN 100000000
ELSE 0
END,
remaining.val=(kc.remaining).val-in_amount_with_fee.val
- CASE
WHEN (kc.remaining).frac < in_amount_with_fee.frac
THEN 1
ELSE 0
END
WHERE coin_pub=in_coin_pub
AND ( ((kc.remaining).val > in_amount_with_fee.val) OR
( ((kc.remaining).frac >= in_amount_with_fee.frac) AND
((kc.remaining).val >= in_amount_with_fee.val) ) );
IF NOT FOUND
THEN
-- Insufficient balance.
out_balance_ok=FALSE;
out_late=FALSE;
out_conflict=FALSE;
RETURN;
END IF;
-- Credit the purse.
UPDATE purse_requests pr
SET
balance.frac=(pr.balance).frac+in_amount_without_fee.frac
- CASE
WHEN (pr.balance).frac+in_amount_without_fee.frac >= 100000000
THEN 100000000
ELSE 0
END,
balance.val=(pr.balance).val+in_amount_without_fee.val
+ CASE
WHEN (pr.balance).frac+in_amount_without_fee.frac >= 100000000
THEN 1
ELSE 0
END
WHERE purse_pub=in_purse_pub;
out_conflict=FALSE;
out_balance_ok=TRUE;
-- See if we can finish the merge or need to update the trigger time and partner.
SELECT COALESCE(partner_serial_id,0)
,reserve_pub
INTO psi
,my_reserve_pub
FROM purse_merges
WHERE purse_pub=in_purse_pub;
IF NOT FOUND
THEN
-- Purse was not yet merged. We are done.
out_late=FALSE;
RETURN;
END IF;
SELECT
amount_with_fee
,in_reserve_quota
INTO
rval
FROM exchange.purse_requests preq
WHERE (purse_pub=in_purse_pub)
AND ( ( ( ((preq.amount_with_fee).val <= (preq.balance).val)
AND ((preq.amount_with_fee).frac <= (preq.balance).frac) )
OR ((preq.amount_with_fee).val < (preq.balance).val) ) );
IF NOT FOUND
THEN
out_late=FALSE;
RETURN;
END IF;
-- We use rval as workaround as we cannot select
-- directly into the amount due to Postgres limitations.
my_amount := rval.amount_with_fee;
my_in_reserve_quota := rval.in_reserve_quota;
-- Remember how this purse was finished.
INSERT INTO purse_decision
(purse_pub
,action_timestamp
,refunded)
VALUES
(in_purse_pub
,in_now
,FALSE)
ON CONFLICT DO NOTHING;
IF NOT FOUND
THEN
-- Purse already decided, likely expired.
out_late=TRUE;
RETURN;
END IF;
out_late=FALSE;
IF (my_in_reserve_quota)
THEN
UPDATE reserves
SET purses_active=purses_active-1
WHERE reserve_pub IN
(SELECT reserve_pub
FROM purse_merges
WHERE purse_pub=my_purse_pub
LIMIT 1);
END IF;
IF (0 != psi)
THEN
-- The taler-exchange-router will take care of this.
UPDATE purse_actions
SET action_date=0 --- "immediately"
,partner_serial_id=psi
WHERE purse_pub=in_purse_pub;
ELSE
-- This is a local reserve, update balance immediately.
INSERT INTO reserves
(reserve_pub
,current_balance
,expiration_date
,gc_date)
VALUES
(my_reserve_pub
,my_amount
,in_reserve_expiration
,in_reserve_expiration)
ON CONFLICT DO NOTHING;
IF NOT FOUND
THEN
-- Reserve existed, thus UPDATE instead of INSERT.
UPDATE reserves
SET
current_balance.frac=(current_balance).frac+my_amount.frac
- CASE
WHEN (current_balance).frac + my_amount.frac >= 100000000
THEN 100000000
ELSE 0
END
,current_balance.val=(current_balance).val+my_amount.val
+ CASE
WHEN (current_balance).frac + my_amount.frac >= 100000000
THEN 1
ELSE 0
END
,expiration_date=GREATEST(expiration_date,in_reserve_expiration)
,gc_date=GREATEST(gc_date,in_reserve_expiration)
WHERE reserve_pub=my_reserve_pub;
END IF;
END IF;
END $$;
|