-- -- 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_val INT8, IN in_amount_with_fee_frac INT4, 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_val ,amount_with_fee_frac ,noreveal_index ) VALUES (in_rc ,in_old_coin_pub ,in_old_coin_sig ,in_amount_with_fee_val ,in_amount_with_fee_frac ,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 exchange.recoup_refresh WHERE rrc_serial IN (SELECT rrc_serial FROM exchange.refresh_revealed_coins WHERE melt_serial_id IN (SELECT melt_serial_id FROM exchange.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 SET remaining_frac=remaining_frac-in_amount_with_fee_frac + CASE WHEN remaining_frac < in_amount_with_fee_frac THEN 100000000 ELSE 0 END, remaining_val=remaining_val-in_amount_with_fee_val - CASE WHEN remaining_frac < in_amount_with_fee_frac THEN 1 ELSE 0 END WHERE coin_pub=in_old_coin_pub AND ( (remaining_val > in_amount_with_fee_val) OR ( (remaining_frac >= in_amount_with_fee_frac) AND (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 $$;