-- -- 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 -- CREATE OR REPLACE FUNCTION exchange_do_melt( IN in_cs_rms BYTEA, IN in_amount_with_fee taler_amount, IN in_rc BYTEA, IN in_old_coin_pub BYTEA, IN in_old_coin_sig BYTEA, IN in_known_coin_id INT8, -- not used, but that's OK IN in_noreveal_index INT4, IN in_zombie_required BOOLEAN, OUT out_balance_ok BOOLEAN, OUT out_zombie_bad BOOLEAN, OUT out_noreveal_index INT4) LANGUAGE plpgsql AS $$ DECLARE denom_max INT8; BEGIN -- Shards: INSERT refresh_commitments (by rc) -- (rare:) SELECT refresh_commitments (by old_coin_pub) -- crosses shards! -- (rare:) SEELCT refresh_revealed_coins (by melt_serial_id) -- (rare:) PERFORM recoup_refresh (by rrc_serial) -- crosses shards! -- UPDATE known_coins (by coin_pub) INSERT INTO exchange.refresh_commitments (rc ,old_coin_pub ,old_coin_sig ,amount_with_fee ,noreveal_index ) VALUES (in_rc ,in_old_coin_pub ,in_old_coin_sig ,in_amount_with_fee ,in_noreveal_index) ON CONFLICT DO NOTHING; IF NOT FOUND THEN -- Idempotency check: see if an identical record exists. out_noreveal_index=-1; SELECT noreveal_index INTO out_noreveal_index FROM exchange.refresh_commitments WHERE rc=in_rc; out_balance_ok=FOUND; out_zombie_bad=FALSE; -- zombie is OK 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 rrc_serial IN (SELECT rrc_serial FROM refresh_revealed_coins WHERE melt_serial_id IN (SELECT melt_serial_id FROM refresh_commitments 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 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_old_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_noreveal_index=-1; out_balance_ok=FALSE; RETURN; END IF; -- Special actions needed for a CS melt? IF NOT NULL in_cs_rms THEN -- Get maximum denominations serial value in -- existence, this will determine how long the -- nonce will be locked. SELECT denominations_serial INTO denom_max FROM exchange.denominations ORDER BY denominations_serial DESC LIMIT 1; -- Cache CS signature to prevent replays in the future -- (and check if cached signature exists at the same time). INSERT INTO exchange.cs_nonce_locks (nonce ,max_denomination_serial ,op_hash) VALUES (cs_rms ,denom_serial ,in_rc) ON CONFLICT DO NOTHING; IF NOT FOUND THEN -- Record exists, make sure it is the same SELECT 1 FROM exchange.cs_nonce_locks WHERE nonce=cs_rms AND op_hash=in_rc; IF NOT FOUND THEN -- Nonce reuse detected out_balance_ok=FALSE; out_zombie_bad=FALSE; out_noreveal_index=42; -- FIXME: return error message more nicely! ASSERT false, 'nonce reuse attempted by client'; END IF; END IF; END IF; -- Everything fine, return success! out_balance_ok=TRUE; out_noreveal_index=in_noreveal_index; END $$;