exchange

Base system with REST service to issue digital coins, run by the payment service provider
Log | Files | Refs | Submodules | README | LICENSE

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';