sync

Backup service to store encrypted wallet databases (experimental)
Log | Files | Refs | Submodules | README | LICENSE

syncdb_append_block_TR.sql (6133B)


      1 --
      2 -- This file is part of TALER
      3 -- Copyright (C) 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 -- The append relinks the previous tail, so the client uploads the
     18 -- tail's new signature (in_prev_upload_sig).  That signature covers the
     19 -- tail as the caller read it outside this transaction, so the whole
     20 -- signed context has to be re-checked here or a concurrent write could
     21 -- leave the tail with a signature over links it no longer has: the
     22 -- transaction re-checks the tail against in_prev_hash and
     23 -- in_prev_prev_nonce.  The signed refs_hash needs no separate check,
     24 -- since sync_do_update_block() is the only thing that changes it and it
     25 -- always changes the block hash along with it.
     26 DROP FUNCTION IF EXISTS sync_do_append_block;
     27 CREATE FUNCTION sync_do_append_block (
     28   IN in_account_pub BYTEA,
     29   IN in_curr_nonce BYTEA,
     30   IN in_prev_nonce BYTEA,
     31   IN in_data_hash BYTEA,
     32   IN in_data_blob BYTEA,
     33   IN ina_object_uids BYTEA[],
     34   IN ina_object_incs INT2[],
     35   IN in_upload_sig BYTEA,
     36   IN in_refs_hash BYTEA,
     37   IN in_prev_upload_sig BYTEA,
     38   IN in_prev_hash BYTEA,
     39   IN in_prev_prev_nonce BYTEA,
     40   OUT out_account_missing BOOLEAN,
     41   OUT out_outdated_append BOOLEAN,
     42   OUT out_negative_refcount BOOLEAN,
     43   OUT out_refs_missing BOOLEAN,
     44   OUT out_inserted BOOLEAN,
     45   OUT out_refs_updated BOOLEAN)
     46 LANGUAGE plpgsql
     47 AS $$
     48 DECLARE
     49   my_last_block_nonce BYTEA;
     50   my_prev_hash BYTEA;
     51   my_prev_prev_nonce BYTEA;
     52 BEGIN
     53   out_account_missing := FALSE;
     54   out_negative_refcount := FALSE;
     55   out_refs_missing := FALSE;
     56   out_outdated_append := FALSE;
     57   out_inserted := FALSE;
     58   out_refs_updated := FALSE;
     59 
     60   -- Check if account exists and has valid payment
     61   SELECT a.out_account_missing
     62     INTO out_account_missing
     63     FROM sync_check_account_payment(in_account_pub) AS a;
     64   IF out_account_missing
     65   THEN
     66     RETURN;
     67   END IF;
     68 
     69   -- Get current tail of the DLL
     70   SELECT last_block
     71     INTO my_last_block_nonce
     72     FROM accounts
     73    WHERE account_pub = in_account_pub;
     74 
     75   -- Ensure that block is being appended to the end
     76   -- (or beginning if backup is empty)
     77   IF NOT (in_prev_nonce IS NOT DISTINCT FROM my_last_block_nonce)
     78   THEN
     79     out_outdated_append := TRUE;
     80     RETURN;
     81   END IF;
     82 
     83   IF in_prev_nonce IS NOT NULL
     84   THEN
     85     -- The tail must still look the way the relink signature was
     86     -- verified against (see header comment).  Its next_nonce needs no
     87     -- check: it is the account's last block, so it has none, and this
     88     -- transaction is what sets it.
     89     SELECT block_hash
     90           ,prev_nonce
     91       INTO my_prev_hash
     92           ,my_prev_prev_nonce
     93       FROM blocks
     94      WHERE account_pub = in_account_pub
     95        AND nonce = in_prev_nonce;
     96 
     97     IF NOT FOUND
     98        OR my_prev_hash IS DISTINCT FROM in_prev_hash
     99        OR my_prev_prev_nonce IS DISTINCT FROM in_prev_prev_nonce
    100     THEN
    101       out_outdated_append := TRUE;
    102       RETURN;
    103     END IF;
    104   END IF;
    105 
    106   -- Check referenced objects exist and no increment underflows,
    107   -- before we touch anything
    108   SELECT c.out_negative_refcount
    109         ,c.out_refs_missing
    110     INTO out_negative_refcount
    111         ,out_refs_missing
    112     FROM sync_check_object_refcounts_pre(in_account_pub,
    113                                          ina_object_uids,
    114                                          ina_object_incs) AS c;
    115   IF out_negative_refcount OR out_refs_missing
    116   THEN
    117     RETURN;
    118   END IF;
    119 
    120   -- Try to append the new block
    121   INSERT INTO blocks
    122     (nonce
    123     ,account_pub
    124     ,block_hash
    125     ,prev_nonce
    126     ,data_blob
    127     ,upload_sig
    128     ,old_hash
    129     ,refs_hash)
    130   VALUES
    131     (in_curr_nonce
    132     ,in_account_pub
    133     ,in_data_hash
    134     ,in_prev_nonce
    135     ,in_data_blob
    136     ,in_upload_sig
    137     ,decode(repeat('00', 64), 'hex')
    138     ,in_refs_hash)
    139   ON CONFLICT DO NOTHING;
    140 
    141   IF NOT FOUND
    142   THEN
    143     out_inserted := FALSE;
    144     RETURN;
    145   ELSE
    146     out_inserted := TRUE;
    147   END IF;
    148 
    149   -- Update the reference in the previous block; it was checked to be
    150   -- this account's tail above.  Store the tail's relink signature with
    151   -- it; a relink never changes the data, so old_hash is set to the
    152   -- current block hash.
    153   IF in_prev_nonce IS NOT NULL
    154   THEN
    155     UPDATE blocks
    156        SET next_nonce = in_curr_nonce
    157           ,upload_sig = in_prev_upload_sig
    158           ,old_hash = block_hash
    159      WHERE account_pub = in_account_pub
    160        AND nonce = in_prev_nonce;
    161 
    162     IF NOT FOUND
    163     THEN
    164       RAISE EXCEPTION 'previous block % vanished for account %',
    165         in_prev_nonce, in_account_pub;
    166     END IF;
    167   END IF;
    168 
    169   -- Update reference to first and last block in the backup
    170   UPDATE accounts
    171      SET last_block = in_curr_nonce
    172         ,first_block = CASE
    173            -- if first block in the backup, update reference
    174            WHEN in_prev_nonce IS NULL
    175              THEN in_curr_nonce
    176            -- otherwise, keep reference intact
    177            ELSE first_block
    178          END
    179    WHERE account_pub = in_account_pub;
    180 
    181   IF NOT FOUND
    182   THEN
    183     RAISE EXCEPTION 'account % vanished', in_account_pub;
    184   END IF;
    185 
    186   -- Update object refcounts and delete unreferenced objects; the
    187   -- pre-check above means this can only fail on a concurrent change
    188   SELECT u.out_negative_refcount
    189         ,u.out_refs_updated
    190     INTO out_negative_refcount
    191         ,out_refs_updated
    192     FROM sync_update_object_refcounts(in_account_pub,
    193                                       ina_object_uids,
    194                                       ina_object_incs) AS u;
    195   IF NOT out_refs_updated
    196   THEN
    197     RAISE EXCEPTION 'object refcounts changed concurrently for account %',
    198       in_account_pub;
    199   END IF;
    200 END $$;