summaryrefslogtreecommitdiff
path: root/src/exchangedb/exchange_do_purse_deposit.sql
blob: 49d3c919b409503306ba5b8873fa98b4f81f6d73 (plain)
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 $$;