exchange

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

0002-recoup.sql (6705B)


      1 --
      2 -- This file is part of TALER
      3 -- Copyright (C) 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 
     17 CREATE FUNCTION create_table_recoup(
     18   IN partition_suffix TEXT DEFAULT NULL
     19 )
     20 RETURNS VOID
     21 LANGUAGE plpgsql
     22 AS $$
     23 DECLARE
     24   table_name TEXT DEFAULT 'recoup';
     25 BEGIN
     26 
     27   PERFORM create_partitioned_table(
     28     'CREATE TABLE %I'
     29       '(recoup_uuid BIGINT GENERATED BY DEFAULT AS IDENTITY'
     30       ',coin_pub BYTEA NOT NULL CHECK (LENGTH(coin_pub)=32)'
     31       ',coin_sig BYTEA NOT NULL CHECK(LENGTH(coin_sig)=64)'
     32       ',coin_blind BYTEA NOT NULL CHECK(LENGTH(coin_blind)=32)'
     33       ',amount taler_amount NOT NULL'
     34       ',recoup_timestamp INT8 NOT NULL'
     35       ',withdraw_id INT8 NOT NULL'
     36     ') %s ;'
     37     ,table_name
     38     ,'PARTITION BY HASH (coin_pub);'
     39     ,partition_suffix
     40   );
     41   PERFORM comment_partitioned_table(
     42      'Information about recoups that were executed between a coin and a reserve. In this type of recoup, the amount is credited back to the reserve from which the coin originated.'
     43     ,table_name
     44     ,partition_suffix
     45   );
     46   PERFORM comment_partitioned_column(
     47      'Coin that is being debited in the recoup. Do not CASCADE ON DROP on the coin_pub, as we may keep the coin alive!'
     48     ,'coin_pub'
     49     ,table_name
     50     ,partition_suffix
     51   );
     52   PERFORM comment_partitioned_column(
     53      'Identifies the h_commitment of the recouped coin and provides the link to the credited reserve.'
     54     ,'withdraw_id'
     55     ,table_name
     56     ,partition_suffix
     57   );
     58   PERFORM comment_partitioned_column(
     59      'Signature by the coin affirming the recoup, of type TALER_SIGNATURE_WALLET_COIN_RECOUP'
     60     ,'coin_sig'
     61     ,table_name
     62     ,partition_suffix
     63   );
     64   PERFORM comment_partitioned_column(
     65      'Denomination blinding key used when creating the blinded coin from the planchet. Secret revealed during the recoup to provide the linkage between the coin and the withdraw operation.'
     66     ,'coin_blind'
     67     ,table_name
     68     ,partition_suffix
     69   );
     70 END
     71 $$;
     72 
     73 
     74 CREATE FUNCTION constrain_table_recoup(
     75   IN partition_suffix TEXT
     76 )
     77 RETURNS VOID
     78 LANGUAGE plpgsql
     79 AS $$
     80 DECLARE
     81   table_name TEXT DEFAULT 'recoup';
     82 BEGIN
     83   table_name = concat_ws('_', table_name, partition_suffix);
     84   EXECUTE FORMAT (
     85     'CREATE INDEX ' || table_name || '_by_coin_pub_index '
     86     'ON ' || table_name || ' '
     87     '(coin_pub);'
     88   );
     89   EXECUTE FORMAT (
     90     'ALTER TABLE ' || table_name ||
     91     ' ADD CONSTRAINT ' || table_name || '_recoup_uuid_key'
     92     ' UNIQUE (recoup_uuid) '
     93   );
     94 END
     95 $$;
     96 
     97 
     98 CREATE OR REPLACE FUNCTION foreign_table_recoup()
     99 RETURNS VOID
    100 LANGUAGE plpgsql
    101 AS $$
    102 DECLARE
    103   table_name TEXT DEFAULT 'recoup';
    104 BEGIN
    105   EXECUTE FORMAT (
    106     'ALTER TABLE ' || table_name ||
    107     ' ADD CONSTRAINT ' || table_name || '_foreign_withdraw'
    108     ' FOREIGN KEY (withdraw_id) '
    109     ' REFERENCES withdraw (withdraw_id) ON DELETE CASCADE'
    110     ',ADD CONSTRAINT ' || table_name || '_foreign_coin_pub'
    111     ' FOREIGN KEY (coin_pub) '
    112     ' REFERENCES known_coins (coin_pub)'
    113   );
    114 END
    115 $$;
    116 
    117 
    118 CREATE FUNCTION create_table_recoup_by_reserve(
    119   IN partition_suffix TEXT DEFAULT NULL
    120 )
    121 RETURNS VOID
    122 LANGUAGE plpgsql
    123 AS $$
    124 DECLARE
    125   table_name TEXT DEFAULT 'recoup_by_reserve';
    126 BEGIN
    127   PERFORM create_partitioned_table(
    128   'CREATE TABLE %I'
    129     '(withdraw_id INT8 NOT NULL' -- REFERENCES withdraw (withdraw_id) ON DELETE CASCADE
    130     ',coin_pub BYTEA CHECK (LENGTH(coin_pub)=32)' -- REFERENCES known_coins (coin_pub)
    131     ') %s ;'
    132     ,table_name
    133     ,'PARTITION BY HASH (withdraw_id)'
    134     ,partition_suffix
    135   );
    136   PERFORM comment_partitioned_table(
    137      'Information in this table is strictly redundant with that of recoup, but saved by a different primary key for fast lookups by withdraw_id.'
    138     ,table_name
    139     ,partition_suffix
    140   );
    141 END
    142 $$;
    143 
    144 
    145 CREATE FUNCTION constrain_table_recoup_by_reserve(
    146   IN partition_suffix TEXT DEFAULT NULL
    147 )
    148 RETURNS VOID
    149 LANGUAGE plpgsql
    150 AS $$
    151 DECLARE
    152   table_name TEXT DEFAULT 'recoup_by_reserve';
    153 BEGIN
    154   table_name = concat_ws('_', table_name, partition_suffix);
    155   EXECUTE FORMAT (
    156     'CREATE INDEX ' || table_name || '_main_index '
    157     'ON ' || table_name || ' '
    158     '(withdraw_id);'
    159   );
    160 END
    161 $$;
    162 
    163 
    164 CREATE OR REPLACE FUNCTION recoup_insert_trigger()
    165   RETURNS trigger
    166   LANGUAGE plpgsql
    167   AS $$
    168 BEGIN
    169   INSERT INTO recoup_by_reserve
    170     (withdraw_id
    171     ,coin_pub)
    172   VALUES
    173     (NEW.withdraw_id
    174     ,NEW.coin_pub);
    175   INSERT INTO coin_history
    176     (coin_pub
    177     ,table_name
    178     ,serial_id)
    179   VALUES
    180     (NEW.coin_pub
    181     ,'recoup'
    182     ,NEW.recoup_uuid);
    183   INSERT INTO reserve_history
    184     (reserve_pub
    185     ,table_name
    186     ,serial_id)
    187   SELECT
    188      res.reserve_pub
    189     ,'recoup'
    190     ,NEW.recoup_uuid
    191   FROM withdraw wd
    192   JOIN reserves res
    193     USING (reserve_pub)
    194     WHERE wd.withdraw_id = NEW.withdraw_id;
    195   RETURN NEW;
    196 END $$;
    197 COMMENT ON FUNCTION recoup_insert_trigger()
    198   IS 'Replicates recoup inserts into recoup_by_reserve table and updates the coin_history table.';
    199 
    200 
    201 CREATE OR REPLACE FUNCTION recoup_delete_trigger()
    202   RETURNS trigger
    203   LANGUAGE plpgsql
    204   AS $$
    205 BEGIN
    206   DELETE FROM recoup_by_reserve
    207    WHERE withdraw_id = OLD.withdraw_id
    208      AND coin_pub = OLD.coin_pub;
    209   RETURN OLD;
    210 END $$;
    211 COMMENT ON FUNCTION recoup_delete_trigger()
    212   IS 'Replicate recoup deletions into recoup_by_reserve table.';
    213 
    214 
    215 CREATE FUNCTION master_table_recoup()
    216 RETURNS VOID
    217 LANGUAGE plpgsql
    218 AS $$
    219 BEGIN
    220   CREATE TRIGGER recoup_on_insert
    221     AFTER INSERT
    222      ON recoup
    223      FOR EACH ROW EXECUTE FUNCTION recoup_insert_trigger();
    224   CREATE TRIGGER recoup_on_delete
    225     AFTER DELETE
    226       ON recoup
    227      FOR EACH ROW EXECUTE FUNCTION recoup_delete_trigger();
    228 END
    229 $$;
    230 
    231 
    232 INSERT INTO exchange_tables
    233     (name
    234     ,version
    235     ,action
    236     ,partitioned
    237     ,by_range)
    238   VALUES
    239     ('recoup'
    240     ,'exchange-0002'
    241     ,'create'
    242     ,TRUE
    243     ,FALSE),
    244     ('recoup'
    245     ,'exchange-0002'
    246     ,'constrain'
    247     ,TRUE
    248     ,FALSE),
    249     ('recoup'
    250     ,'exchange-0002'
    251     ,'foreign'
    252     ,TRUE
    253     ,FALSE),
    254     ('recoup_by_reserve'
    255     ,'exchange-0002'
    256     ,'create'
    257     ,TRUE
    258     ,FALSE),
    259     ('recoup_by_reserve'
    260     ,'exchange-0002'
    261     ,'constrain'
    262     ,TRUE
    263     ,FALSE),
    264     ('recoup'
    265     ,'exchange-0002'
    266     ,'master'
    267     ,TRUE
    268     ,FALSE);