-- -- This file is part of TALER -- Copyright (C) 2025 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 -- -- @author Özgür Kesim DROP FUNCTION IF EXISTS exchange_do_refresh; CREATE FUNCTION exchange_do_refresh( IN in_rc BYTEA, IN in_now INT8, IN in_refresh_seed BYTEA, IN in_planchets_h BYTEA, IN in_amount_with_fee taler_amount, IN in_blinding_seed BYTEA, IN in_cs_r_values BYTEA[], IN in_cs_r_choices INT8, IN in_selected_h BYTEA, IN in_denom_sigs BYTEA[], IN in_denom_serials INT8[], IN in_old_coin_pub BYTEA, IN in_old_coin_sig BYTEA, IN in_noreveal_index INT4, IN in_zombie_required BOOLEAN, OUT out_coin_found BOOLEAN, OUT out_balance_ok BOOLEAN, OUT out_zombie_bad BOOLEAN, OUT out_nonce_reuse BOOLEAN, OUT out_idempotent BOOLEAN, OUT out_noreveal_index INT4, OUT out_coin_balance taler_amount) LANGUAGE plpgsql AS $$ DECLARE known_coin RECORD; difference RECORD; BEGIN -- Shards: INSERT refresh (by rc) -- (rare:) SELECT refresh (by old_coin_pub) -- crosses shards! -- (rare:) SELECT refresh_revealed_coins (by refresh_id) -- (rare:) PERFORM recoup_refresh (by rrc_serial) -- crosses shards! -- UPDATE known_coins (by coin_pub) -- First, find old coin SELECT known_coin_id ,remaining INTO known_coin FROM known_coins WHERE coin_pub = in_old_coin_pub; IF NOT FOUND THEN out_coin_found = FALSE; out_balance_ok = TRUE; out_zombie_bad = FALSE; out_nonce_reuse = FALSE; out_idempotent = FALSE; out_noreveal_index = -1 ; out_coin_balance.val = 0; out_coin_balance.frac = 0; RETURN; END IF; out_coin_found = TRUE; out_coin_balance = known_coin.remaining; -- Next, check for idempotency SELECT TRUE, noreveal_index INTO out_idempotent, out_noreveal_index FROM exchange.refresh WHERE rc=in_rc; IF out_idempotent THEN -- out_idempotent is set -- out_noreveal_index is set -- out_coin_found is set -- out_coin_balance is set out_balance_ok = TRUE; out_zombie_bad = FALSE; -- zombie is OK out_nonce_reuse = FALSE; RETURN; END IF; out_idempotent = FALSE; out_noreveal_index = in_noreveal_index; -- Ensure the uniqueness of the blinding_seed IF in_blinding_seed IS NOT NULL THEN INSERT INTO unique_refresh_blinding_seed (blinding_seed) VALUES (in_blinding_seed) ON CONFLICT DO NOTHING; IF NOT FOUND THEN out_nonce_reuse = TRUE; out_balance_ok = TRUE; out_zombie_bad = FALSE; -- zombie is OK RETURN; END IF; END IF; out_nonce_reuse = FALSE; INSERT INTO exchange.refresh (rc ,execution_date ,old_coin_pub ,old_coin_sig ,planchets_h ,amount_with_fee ,noreveal_index ,refresh_seed ,blinding_seed ,cs_r_values ,cs_r_choices ,selected_h ,denom_sigs ,denom_serials ) VALUES (in_rc ,in_now ,in_old_coin_pub ,in_old_coin_sig ,in_planchets_h ,in_amount_with_fee ,in_noreveal_index ,in_refresh_seed ,in_blinding_seed ,in_cs_r_values ,in_cs_r_choices ,in_selected_h ,in_denom_sigs ,in_denom_serials ) ON CONFLICT DO NOTHING; IF NOT FOUND THEN RAISE EXCEPTION 'Conflict in refresh despite idempotency check for rc(%)!', rc; RETURN; END IF; IF in_zombie_required THEN -- Check if this coin was part of a refresh -- operation that was subsequently involved -- in a recoup operation. We begin by all -- refresh operations our coin was involved -- with, then find all associated reveal -- operations, and then see if any of these -- reveal operations was involved in a recoup. PERFORM FROM recoup_refresh WHERE refresh_id IN (SELECT refresh_id FROM refresh WHERE old_coin_pub=in_old_coin_pub); IF NOT FOUND THEN out_zombie_bad=TRUE; out_balance_ok=FALSE; RETURN; END IF; END IF; out_zombie_bad=FALSE; -- zombie is OK -- Check coin balance is sufficient. SELECT * INTO difference FROM amount_left_minus_right(out_coin_balance ,in_amount_with_fee); out_balance_ok = difference.ok; IF NOT out_balance_ok THEN RETURN; END IF; out_coin_balance = difference.diff; -- Check and update balance of the coin. UPDATE known_coins SET remaining = out_coin_balance WHERE known_coin_id = known_coin.known_coin_id; END $$;