diff options
Diffstat (limited to 'src/exchangedb/procedures.sql')
-rw-r--r-- | src/exchangedb/procedures.sql | 2289 |
1 files changed, 0 insertions, 2289 deletions
diff --git a/src/exchangedb/procedures.sql b/src/exchangedb/procedures.sql deleted file mode 100644 index 8366bc811..000000000 --- a/src/exchangedb/procedures.sql +++ /dev/null @@ -1,2289 +0,0 @@ --- --- 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 <http://www.gnu.org/licenses/> --- - --- Everything in one big transaction -BEGIN; - -SET search_path TO exchange; - ---------------------------------------------------------------------------- --- Stored procedures ---------------------------------------------------------------------------- - -CREATE OR REPLACE FUNCTION exchange_do_withdraw( - IN cs_nonce BYTEA, - IN amount_val INT8, - IN amount_frac INT4, - IN h_denom_pub BYTEA, - IN rpub BYTEA, - IN reserve_sig BYTEA, - IN h_coin_envelope BYTEA, - IN denom_sig BYTEA, - IN now INT8, - IN min_reserve_gc INT8, - OUT reserve_found BOOLEAN, - OUT balance_ok BOOLEAN, - OUT nonce_ok BOOLEAN, - OUT kycok BOOLEAN, - OUT account_uuid INT8, - OUT ruuid INT8) -LANGUAGE plpgsql -AS $$ -DECLARE - reserve_gc INT8; -DECLARE - denom_serial INT8; -DECLARE - reserve_val INT8; -DECLARE - reserve_frac INT4; -BEGIN --- Shards: reserves by reserve_pub (SELECT) --- reserves_out (INSERT, with CONFLICT detection) by wih --- reserves by reserve_pub (UPDATE) --- reserves_in by reserve_pub (SELECT) --- wire_targets by wire_target_h_payto - -SELECT denominations_serial - INTO denom_serial - FROM exchange.denominations - WHERE denom_pub_hash=h_denom_pub; - -IF NOT FOUND -THEN - -- denomination unknown, should be impossible! - reserve_found=FALSE; - balance_ok=FALSE; - kycok=FALSE; - account_uuid=0; - ruuid=0; - ASSERT false, 'denomination unknown'; - RETURN; -END IF; - - -SELECT - current_balance_val - ,current_balance_frac - ,gc_date - ,reserve_uuid - INTO - reserve_val - ,reserve_frac - ,reserve_gc - ,ruuid - FROM exchange.reserves - WHERE reserves.reserve_pub=rpub; - -IF NOT FOUND -THEN - -- reserve unknown - reserve_found=FALSE; - balance_ok=FALSE; - nonce_ok=TRUE; - kycok=FALSE; - account_uuid=0; - ruuid=2; - RETURN; -END IF; - --- We optimistically insert, and then on conflict declare --- the query successful due to idempotency. -INSERT INTO exchange.reserves_out - (h_blind_ev - ,denominations_serial - ,denom_sig - ,reserve_uuid - ,reserve_sig - ,execution_date - ,amount_with_fee_val - ,amount_with_fee_frac) -VALUES - (h_coin_envelope - ,denom_serial - ,denom_sig - ,ruuid - ,reserve_sig - ,now - ,amount_val - ,amount_frac) -ON CONFLICT DO NOTHING; - -IF NOT FOUND -THEN - -- idempotent query, all constraints must be satisfied - reserve_found=TRUE; - balance_ok=TRUE; - nonce_ok=TRUE; - kycok=TRUE; - account_uuid=0; - RETURN; -END IF; - --- Check reserve balance is sufficient. -IF (reserve_val > amount_val) -THEN - IF (reserve_frac >= amount_frac) - THEN - reserve_val=reserve_val - amount_val; - reserve_frac=reserve_frac - amount_frac; - ELSE - reserve_val=reserve_val - amount_val - 1; - reserve_frac=reserve_frac + 100000000 - amount_frac; - END IF; -ELSE - IF (reserve_val = amount_val) AND (reserve_frac >= amount_frac) - THEN - reserve_val=0; - reserve_frac=reserve_frac - amount_frac; - ELSE - reserve_found=TRUE; - nonce_ok=TRUE; -- we do not really know - balance_ok=FALSE; - kycok=FALSE; -- we do not really know or care - account_uuid=0; - RETURN; - END IF; -END IF; - --- Calculate new expiration dates. -min_reserve_gc=GREATEST(min_reserve_gc,reserve_gc); - --- Update reserve balance. -UPDATE reserves SET - gc_date=min_reserve_gc - ,current_balance_val=reserve_val - ,current_balance_frac=reserve_frac -WHERE - reserves.reserve_pub=rpub; - -reserve_found=TRUE; -balance_ok=TRUE; - - - --- Special actions needed for a CS withdraw? -IF NOT NULL cs_nonce -THEN - -- 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_nonce - ,denom_serial - ,h_coin_envelope) - ON CONFLICT DO NOTHING; - - IF NOT FOUND - THEN - -- See if the existing entry is identical. - SELECT 1 - FROM exchange.cs_nonce_locks - WHERE nonce=cs_nonce - AND op_hash=h_coin_envelope; - IF NOT FOUND - THEN - reserve_found=FALSE; - balance_ok=FALSE; - kycok=FALSE; - account_uuid=0; - nonce_ok=FALSE; - RETURN; - END IF; - END IF; -ELSE - nonce_ok=TRUE; -- no nonce, hence OK! -END IF; - - - --- Obtain KYC status based on the last wire transfer into --- this reserve. FIXME: likely not adequate for reserves that got P2P transfers! --- SELECT --- kyc_ok --- ,wire_target_serial_id --- INTO --- kycok --- ,account_uuid --- FROM exchange.reserves_in --- JOIN wire_targets ON (wire_source_h_payto = wire_target_h_payto) --- WHERE reserve_pub=rpub --- LIMIT 1; -- limit 1 should not be required (without p2p transfers) - -WITH my_reserves_in AS materialized ( - SELECT wire_source_h_payto - FROM exchange.reserves_in - WHERE reserve_pub=rpub -) -SELECT - kyc_ok - ,wire_target_serial_id -INTO - kycok - ,account_uuid -FROM exchange.wire_targets - WHERE wire_target_h_payto = ( - SELECT wire_source_h_payto - FROM my_reserves_in - ); - -END $$; - -COMMENT ON FUNCTION exchange_do_withdraw(BYTEA, INT8, INT4, BYTEA, BYTEA, BYTEA, BYTEA, BYTEA, INT8, INT8) - IS 'Checks whether the reserve has sufficient balance for a withdraw operation (or the request is repeated and was previously approved) and if so updates the database with the result'; - - - - -CREATE OR REPLACE FUNCTION exchange_do_batch_withdraw( - IN amount_val INT8, - IN amount_frac INT4, - IN rpub BYTEA, - IN now INT8, - IN min_reserve_gc INT8, - OUT reserve_found BOOLEAN, - OUT balance_ok BOOLEAN, - OUT kycok BOOLEAN, - OUT account_uuid INT8, - OUT ruuid INT8) -LANGUAGE plpgsql -AS $$ -DECLARE - reserve_gc INT8; -DECLARE - reserve_val INT8; -DECLARE - reserve_frac INT4; -BEGIN --- Shards: reserves by reserve_pub (SELECT) --- reserves_out (INSERT, with CONFLICT detection) by wih --- reserves by reserve_pub (UPDATE) --- reserves_in by reserve_pub (SELECT) --- wire_targets by wire_target_h_payto - -SELECT - current_balance_val - ,current_balance_frac - ,gc_date - ,reserve_uuid - INTO - reserve_val - ,reserve_frac - ,reserve_gc - ,ruuid - FROM exchange.reserves - WHERE reserves.reserve_pub=rpub; - -IF NOT FOUND -THEN - -- reserve unknown - reserve_found=FALSE; - balance_ok=FALSE; - kycok=FALSE; - account_uuid=0; - ruuid=2; - RETURN; -END IF; - --- Check reserve balance is sufficient. -IF (reserve_val > amount_val) -THEN - IF (reserve_frac >= amount_frac) - THEN - reserve_val=reserve_val - amount_val; - reserve_frac=reserve_frac - amount_frac; - ELSE - reserve_val=reserve_val - amount_val - 1; - reserve_frac=reserve_frac + 100000000 - amount_frac; - END IF; -ELSE - IF (reserve_val = amount_val) AND (reserve_frac >= amount_frac) - THEN - reserve_val=0; - reserve_frac=reserve_frac - amount_frac; - ELSE - reserve_found=TRUE; - balance_ok=FALSE; - kycok=FALSE; -- we do not really know or care - account_uuid=0; - RETURN; - END IF; -END IF; - --- Calculate new expiration dates. -min_reserve_gc=GREATEST(min_reserve_gc,reserve_gc); - --- Update reserve balance. -UPDATE reserves SET - gc_date=min_reserve_gc - ,current_balance_val=reserve_val - ,current_balance_frac=reserve_frac -WHERE - reserves.reserve_pub=rpub; - -reserve_found=TRUE; -balance_ok=TRUE; - - --- Obtain KYC status based on the last wire transfer into --- this reserve. FIXME: likely not adequate for reserves that got P2P transfers! --- SELECT --- kyc_ok --- ,wire_target_serial_id --- INTO --- kycok --- ,account_uuid --- FROM exchange.reserves_in --- JOIN wire_targets ON (wire_source_h_payto = wire_target_h_payto) --- WHERE reserve_pub=rpub --- LIMIT 1; -- limit 1 should not be required (without p2p transfers) - -WITH my_reserves_in AS materialized ( - SELECT wire_source_h_payto - FROM exchange.reserves_in - WHERE reserve_pub=rpub -) -SELECT - kyc_ok - ,wire_target_serial_id -INTO - kycok - ,account_uuid -FROM exchange.wire_targets - WHERE wire_target_h_payto = ( - SELECT wire_source_h_payto - FROM my_reserves_in - ); - -END $$; - -COMMENT ON FUNCTION exchange_do_batch_withdraw(INT8, INT4, BYTEA, INT8, INT8) - IS 'Checks whether the reserve has sufficient balance for a withdraw operation (or the request is repeated and was previously approved) and if so updates the database with the result. Excludes storing the planchets.'; - - - - - -CREATE OR REPLACE FUNCTION exchange_do_batch_withdraw_insert( - IN cs_nonce BYTEA, - IN amount_val INT8, - IN amount_frac INT4, - IN h_denom_pub BYTEA, - IN ruuid INT8, - IN reserve_sig BYTEA, - IN h_coin_envelope BYTEA, - IN denom_sig BYTEA, - IN now INT8, - OUT out_denom_unknown BOOLEAN, - OUT out_nonce_reuse BOOLEAN, - OUT out_conflict BOOLEAN) -LANGUAGE plpgsql -AS $$ -DECLARE - denom_serial INT8; -BEGIN --- Shards: reserves by reserve_pub (SELECT) --- reserves_out (INSERT, with CONFLICT detection) by wih --- reserves by reserve_pub (UPDATE) --- reserves_in by reserve_pub (SELECT) --- wire_targets by wire_target_h_payto - -out_denom_unknown=TRUE; -out_conflict=TRUE; -out_nonce_reuse=TRUE; - -SELECT denominations_serial - INTO denom_serial - FROM exchange.denominations - WHERE denom_pub_hash=h_denom_pub; - -IF NOT FOUND -THEN - -- denomination unknown, should be impossible! - out_denom_unknown=TRUE; - ASSERT false, 'denomination unknown'; - RETURN; -END IF; -out_denom_unknown=FALSE; - -INSERT INTO exchange.reserves_out - (h_blind_ev - ,denominations_serial - ,denom_sig - ,reserve_uuid - ,reserve_sig - ,execution_date - ,amount_with_fee_val - ,amount_with_fee_frac) -VALUES - (h_coin_envelope - ,denom_serial - ,denom_sig - ,ruuid - ,reserve_sig - ,now - ,amount_val - ,amount_frac) -ON CONFLICT DO NOTHING; - -IF NOT FOUND -THEN - out_conflict=TRUE; - RETURN; -END IF; -out_conflict=FALSE; - --- Special actions needed for a CS withdraw? -out_nonce_reuse=FALSE; -IF NOT NULL cs_nonce -THEN - -- 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_nonce - ,denom_serial - ,h_coin_envelope) - ON CONFLICT DO NOTHING; - - IF NOT FOUND - THEN - -- See if the existing entry is identical. - SELECT 1 - FROM exchange.cs_nonce_locks - WHERE nonce=cs_nonce - AND op_hash=h_coin_envelope; - IF NOT FOUND - THEN - out_nonce_reuse=TRUE; - ASSERT false, 'nonce reuse attempted by client'; - RETURN; - END IF; - END IF; -END IF; - -END $$; - -COMMENT ON FUNCTION exchange_do_batch_withdraw_insert(BYTEA, INT8, INT4, BYTEA, INT8, BYTEA, BYTEA, BYTEA, INT8) - IS 'Stores information about a planchet for a batch withdraw operation. Checks if the planchet already exists, and in that case indicates a conflict'; - - - - -CREATE OR REPLACE FUNCTION exchange_do_withdraw_limit_check( - IN ruuid INT8, - IN start_time INT8, - IN upper_limit_val INT8, - IN upper_limit_frac INT4, - OUT below_limit BOOLEAN) -LANGUAGE plpgsql -AS $$ -DECLARE - total_val INT8; -DECLARE - total_frac INT8; -- INT4 could overflow during accumulation! -BEGIN --- NOTE: Read-only, but crosses shards. --- Shards: reserves by reserve_pub --- reserves_out by reserve_uuid -- crosses shards!! - - -SELECT - SUM(amount_with_fee_val) -- overflow here is not plausible - ,SUM(CAST(amount_with_fee_frac AS INT8)) -- compute using 64 bits - INTO - total_val - ,total_frac - FROM exchange.reserves_out - WHERE reserve_uuid=ruuid - AND execution_date > start_time; - --- normalize result -total_val = total_val + total_frac / 100000000; -total_frac = total_frac % 100000000; - --- compare to threshold -below_limit = (total_val < upper_limit_val) OR - ( (total_val = upper_limit_val) AND - (total_frac <= upper_limit_frac) ); -END $$; - -COMMENT ON FUNCTION exchange_do_withdraw_limit_check(INT8, INT8, INT8, INT4) - IS 'Check whether the withdrawals from the given reserve since the given time are below the given threshold'; - - --- NOTE: experiment, currently dead, see postgres_Start_deferred_wire_out; --- now done inline. FIXME: Remove code here once inline version is confirmed working nicely! -CREATE OR REPLACE PROCEDURE defer_wire_out() -LANGUAGE plpgsql -AS $$ -BEGIN - -IF EXISTS ( - SELECT 1 - FROM exchange.information_Schema.constraint_column_usage - WHERE table_name='wire_out' - AND constraint_name='wire_out_ref') -THEN - SET CONSTRAINTS wire_out_ref DEFERRED; -END IF; - -END $$; - - -CREATE OR REPLACE FUNCTION exchange_do_recoup_by_reserve( - IN res_pub BYTEA -) -RETURNS TABLE -( - denom_sig BYTEA, - denominations_serial BIGINT, - coin_pub BYTEA, - coin_sig BYTEA, - coin_blind BYTEA, - amount_val BIGINT, - amount_frac INTEGER, - recoup_timestamp BIGINT -) -LANGUAGE plpgsql -AS $$ -DECLARE - res_uuid BIGINT; - blind_ev BYTEA; - c_pub BYTEA; -BEGIN - SELECT reserve_uuid - INTO res_uuid - FROM exchange.reserves - WHERE reserves.reserve_pub = res_pub; - - FOR blind_ev IN - SELECT h_blind_ev - FROM exchange.reserves_out_by_reserve - WHERE reserves_out_by_reserve.reserve_uuid = res_uuid - LOOP - SELECT robr.coin_pub - INTO c_pub - FROM exchange.recoup_by_reserve robr - WHERE robr.reserve_out_serial_id = ( - SELECT reserves_out.reserve_out_serial_id - FROM exchange.reserves_out - WHERE reserves_out.h_blind_ev = blind_ev - ); - RETURN QUERY - SELECT kc.denom_sig, - kc.denominations_serial, - rc.coin_pub, - rc.coin_sig, - rc.coin_blind, - rc.amount_val, - rc.amount_frac, - rc.recoup_timestamp - FROM ( - SELECT * - FROM exchange.known_coins - WHERE known_coins.coin_pub = c_pub - ) kc - JOIN ( - SELECT * - FROM exchange.recoup - WHERE recoup.coin_pub = c_pub - ) rc USING (coin_pub); - END LOOP; -END; -$$; - -COMMENT ON FUNCTION exchange_do_recoup_by_reserve - IS 'Recoup by reserve as a function to make sure we hit only the needed partition and not all when joining as joins on distributed tables fetch ALL rows from the shards'; - - -CREATE OR REPLACE FUNCTION exchange_do_deposit( - IN in_amount_with_fee_val INT8, - IN in_amount_with_fee_frac INT4, - IN in_h_contract_terms BYTEA, - IN in_wire_salt BYTEA, - IN in_wallet_timestamp INT8, - IN in_exchange_timestamp INT8, - IN in_refund_deadline INT8, - IN in_wire_deadline INT8, - IN in_merchant_pub BYTEA, - IN in_receiver_wire_account VARCHAR, - IN in_h_payto BYTEA, - IN in_known_coin_id INT8, - IN in_coin_pub BYTEA, - IN in_coin_sig BYTEA, - IN in_shard INT8, - IN in_extension_blocked BOOLEAN, - IN in_extension_details VARCHAR, - OUT out_exchange_timestamp INT8, - OUT out_balance_ok BOOLEAN, - OUT out_conflict BOOLEAN) -LANGUAGE plpgsql -AS $$ -DECLARE - wtsi INT8; -- wire target serial id -DECLARE - xdi INT8; -- eXstension details serial id -BEGIN --- Shards: INSERT extension_details (by extension_details_serial_id) --- INSERT wire_targets (by h_payto), on CONFLICT DO NOTHING; --- INSERT deposits (by coin_pub, shard), ON CONFLICT DO NOTHING; --- UPDATE known_coins (by coin_pub) - -IF NOT NULL in_extension_details -THEN - INSERT INTO exchange.extension_details - (extension_options) - VALUES - (in_extension_details) - RETURNING extension_details_serial_id INTO xdi; -ELSE - xdi=NULL; -END IF; - - -INSERT INTO exchange.wire_targets - (wire_target_h_payto - ,payto_uri) - VALUES - (in_h_payto - ,in_receiver_wire_account) -ON CONFLICT DO NOTHING -- for CONFLICT ON (wire_target_h_payto) - RETURNING wire_target_serial_id INTO wtsi; - -IF NOT FOUND -THEN - SELECT wire_target_serial_id - INTO wtsi - FROM exchange.wire_targets - WHERE wire_target_h_payto=in_h_payto; -END IF; - - -INSERT INTO exchange.deposits - (shard - ,coin_pub - ,known_coin_id - ,amount_with_fee_val - ,amount_with_fee_frac - ,wallet_timestamp - ,exchange_timestamp - ,refund_deadline - ,wire_deadline - ,merchant_pub - ,h_contract_terms - ,coin_sig - ,wire_salt - ,wire_target_h_payto - ,extension_blocked - ,extension_details_serial_id - ) - VALUES - (in_shard - ,in_coin_pub - ,in_known_coin_id - ,in_amount_with_fee_val - ,in_amount_with_fee_frac - ,in_wallet_timestamp - ,in_exchange_timestamp - ,in_refund_deadline - ,in_wire_deadline - ,in_merchant_pub - ,in_h_contract_terms - ,in_coin_sig - ,in_wire_salt - ,in_h_payto - ,in_extension_blocked - ,xdi) - ON CONFLICT DO NOTHING; - -IF NOT FOUND -THEN - -- Idempotency check: see if an identical record exists. - -- Note that by checking 'coin_sig', we implicitly check - -- identity over everything that the signature covers. - -- We do select over merchant_pub and wire_target_h_payto - -- primarily here to maximally use the existing index. - SELECT - exchange_timestamp - INTO - out_exchange_timestamp - FROM exchange.deposits - WHERE shard=in_shard - AND merchant_pub=in_merchant_pub - AND wire_target_h_payto=in_h_payto - AND coin_pub=in_coin_pub - AND coin_sig=in_coin_sig; - - IF NOT FOUND - THEN - -- Deposit exists, but with differences. Not allowed. - out_balance_ok=FALSE; - out_conflict=TRUE; - RETURN; - END IF; - - -- Idempotent request known, return success. - out_balance_ok=TRUE; - out_conflict=FALSE; - - RETURN; -END IF; - - -out_exchange_timestamp=in_exchange_timestamp; - --- 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_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_balance_ok=FALSE; - out_conflict=FALSE; - RETURN; -END IF; - --- Everything fine, return success! -out_balance_ok=TRUE; -out_conflict=FALSE; - -END $$; - - - -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 $$; - - - -CREATE OR REPLACE FUNCTION exchange_do_refund( - IN in_amount_with_fee_val INT8, - IN in_amount_with_fee_frac INT4, - IN in_amount_val INT8, - IN in_amount_frac INT4, - IN in_deposit_fee_val INT8, - IN in_deposit_fee_frac INT4, - IN in_h_contract_terms BYTEA, - IN in_rtransaction_id INT8, - IN in_deposit_shard INT8, - IN in_known_coin_id INT8, - IN in_coin_pub BYTEA, - IN in_merchant_pub BYTEA, - IN in_merchant_sig BYTEA, - OUT out_not_found BOOLEAN, - OUT out_refund_ok BOOLEAN, - OUT out_gone BOOLEAN, - OUT out_conflict BOOLEAN) -LANGUAGE plpgsql -AS $$ -DECLARE - dsi INT8; -- ID of deposit being refunded -DECLARE - tmp_val INT8; -- total amount refunded -DECLARE - tmp_frac INT8; -- total amount refunded -DECLARE - deposit_val INT8; -- amount that was originally deposited -DECLARE - deposit_frac INT8; -- amount that was originally deposited -BEGIN --- Shards: SELECT deposits (coin_pub, shard, h_contract_terms, merchant_pub) --- INSERT refunds (by coin_pub, rtransaction_id) ON CONFLICT DO NOTHING --- SELECT refunds (by coin_pub) --- UPDATE known_coins (by coin_pub) - -SELECT - deposit_serial_id - ,amount_with_fee_val - ,amount_with_fee_frac - ,done -INTO - dsi - ,deposit_val - ,deposit_frac - ,out_gone -FROM exchange.deposits - WHERE coin_pub=in_coin_pub - AND shard=in_deposit_shard - AND merchant_pub=in_merchant_pub - AND h_contract_terms=in_h_contract_terms; - -IF NOT FOUND -THEN - -- No matching deposit found! - out_refund_ok=FALSE; - out_conflict=FALSE; - out_not_found=TRUE; - out_gone=FALSE; - RETURN; -END IF; - -INSERT INTO exchange.refunds - (deposit_serial_id - ,coin_pub - ,merchant_sig - ,rtransaction_id - ,amount_with_fee_val - ,amount_with_fee_frac - ) - VALUES - (dsi - ,in_coin_pub - ,in_merchant_sig - ,in_rtransaction_id - ,in_amount_with_fee_val - ,in_amount_with_fee_frac) - ON CONFLICT DO NOTHING; - -IF NOT FOUND -THEN - -- Idempotency check: see if an identical record exists. - -- Note that by checking 'coin_sig', we implicitly check - -- identity over everything that the signature covers. - -- We do select over merchant_pub and h_contract_terms - -- primarily here to maximally use the existing index. - PERFORM - FROM exchange.refunds - WHERE coin_pub=in_coin_pub - AND deposit_serial_id=dsi - AND rtransaction_id=in_rtransaction_id - AND amount_with_fee_val=in_amount_with_fee_val - AND amount_with_fee_frac=in_amount_with_fee_frac; - - IF NOT FOUND - THEN - -- Deposit exists, but have conflicting refund. - out_refund_ok=FALSE; - out_conflict=TRUE; - out_not_found=FALSE; - RETURN; - END IF; - - -- Idempotent request known, return success. - out_refund_ok=TRUE; - out_conflict=FALSE; - out_not_found=FALSE; - out_gone=FALSE; - RETURN; -END IF; - -IF out_gone -THEN - -- money already sent to the merchant. Tough luck. - out_refund_ok=FALSE; - out_conflict=FALSE; - out_not_found=FALSE; - RETURN; -END IF; - --- Check refund balance invariant. -SELECT - SUM(amount_with_fee_val) -- overflow here is not plausible - ,SUM(CAST(amount_with_fee_frac AS INT8)) -- compute using 64 bits - INTO - tmp_val - ,tmp_frac - FROM exchange.refunds - WHERE coin_pub=in_coin_pub - AND deposit_serial_id=dsi; -IF tmp_val IS NULL -THEN - RAISE NOTICE 'failed to sum up existing refunds'; - out_refund_ok=FALSE; - out_conflict=FALSE; - out_not_found=FALSE; - RETURN; -END IF; - --- Normalize result before continuing -tmp_val = tmp_val + tmp_frac / 100000000; -tmp_frac = tmp_frac % 100000000; - --- Actually check if the deposits are sufficient for the refund. Verbosely. ;-) -IF (tmp_val < deposit_val) -THEN - out_refund_ok=TRUE; -ELSE - IF (tmp_val = deposit_val) AND (tmp_frac <= deposit_frac) - THEN - out_refund_ok=TRUE; - ELSE - out_refund_ok=FALSE; - END IF; -END IF; - -IF (tmp_val = deposit_val) AND (tmp_frac = deposit_frac) -THEN - -- Refunds have reached the full value of the original - -- deposit. Also refund the deposit fee. - in_amount_frac = in_amount_frac + in_deposit_fee_frac; - in_amount_val = in_amount_val + in_deposit_fee_val; - - -- Normalize result before continuing - in_amount_val = in_amount_val + in_amount_frac / 100000000; - in_amount_frac = in_amount_frac % 100000000; -END IF; - --- Update balance of the coin. -UPDATE known_coins - SET - remaining_frac=remaining_frac+in_amount_frac - - CASE - WHEN remaining_frac+in_amount_frac >= 100000000 - THEN 100000000 - ELSE 0 - END, - remaining_val=remaining_val+in_amount_val - + CASE - WHEN remaining_frac+in_amount_frac >= 100000000 - THEN 1 - ELSE 0 - END - WHERE coin_pub=in_coin_pub; - - -out_conflict=FALSE; -out_not_found=FALSE; - -END $$; - --- COMMENT ON FUNCTION exchange_do_refund(INT8, INT4, BYTEA, BOOLEAN, BOOLEAN) --- IS 'Executes a refund operation, checking that the corresponding deposit was sufficient to cover the refunded amount'; - - - - -CREATE OR REPLACE FUNCTION exchange_do_recoup_to_reserve( - IN in_reserve_pub BYTEA, - IN in_reserve_out_serial_id INT8, - IN in_coin_blind BYTEA, - IN in_coin_pub BYTEA, - IN in_known_coin_id INT8, - IN in_coin_sig BYTEA, - IN in_reserve_gc INT8, - IN in_reserve_expiration INT8, - IN in_recoup_timestamp INT8, - OUT out_recoup_ok BOOLEAN, - OUT out_internal_failure BOOLEAN, - OUT out_recoup_timestamp INT8) -LANGUAGE plpgsql -AS $$ -DECLARE - tmp_val INT8; -- amount recouped -DECLARE - tmp_frac INT8; -- amount recouped -BEGIN --- Shards: SELECT known_coins (by coin_pub) --- SELECT recoup (by coin_pub) --- UPDATE known_coins (by coin_pub) --- UPDATE reserves (by reserve_pub) --- INSERT recoup (by coin_pub) - -out_internal_failure=FALSE; - - --- Check remaining balance of the coin. -SELECT - remaining_frac - ,remaining_val - INTO - tmp_frac - ,tmp_val -FROM exchange.known_coins - WHERE coin_pub=in_coin_pub; - -IF NOT FOUND -THEN - out_internal_failure=TRUE; - out_recoup_ok=FALSE; - RETURN; -END IF; - -IF tmp_val + tmp_frac = 0 -THEN - -- Check for idempotency - SELECT - recoup_timestamp - INTO - out_recoup_timestamp - FROM exchange.recoup - WHERE coin_pub=in_coin_pub; - - out_recoup_ok=FOUND; - RETURN; -END IF; - - --- Update balance of the coin. -UPDATE known_coins - SET - remaining_frac=0 - ,remaining_val=0 - WHERE coin_pub=in_coin_pub; - - --- Credit the reserve and update reserve timers. -UPDATE reserves - SET - current_balance_frac=current_balance_frac+tmp_frac - - CASE - WHEN current_balance_frac+tmp_frac >= 100000000 - THEN 100000000 - ELSE 0 - END, - current_balance_val=current_balance_val+tmp_val - + CASE - WHEN current_balance_frac+tmp_frac >= 100000000 - THEN 1 - ELSE 0 - END, - gc_date=GREATEST(gc_date, in_reserve_gc), - expiration_date=GREATEST(expiration_date, in_reserve_expiration) - WHERE reserve_pub=in_reserve_pub; - - -IF NOT FOUND -THEN - RAISE NOTICE 'failed to increase reserve balance from recoup'; - out_recoup_ok=TRUE; - out_internal_failure=TRUE; - RETURN; -END IF; - - -INSERT INTO exchange.recoup - (coin_pub - ,coin_sig - ,coin_blind - ,amount_val - ,amount_frac - ,recoup_timestamp - ,reserve_out_serial_id - ) -VALUES - (in_coin_pub - ,in_coin_sig - ,in_coin_blind - ,tmp_val - ,tmp_frac - ,in_recoup_timestamp - ,in_reserve_out_serial_id); - --- Normal end, everything is fine. -out_recoup_ok=TRUE; -out_recoup_timestamp=in_recoup_timestamp; - -END $$; - --- COMMENT ON FUNCTION exchange_do_recoup_to_reserve(INT8, INT4, BYTEA, BOOLEAN, BOOLEAN) --- IS 'Executes a recoup of a coin that was withdrawn from a reserve'; - - - - - - -CREATE OR REPLACE FUNCTION exchange_do_recoup_to_coin( - IN in_old_coin_pub BYTEA, - IN in_rrc_serial INT8, - IN in_coin_blind BYTEA, - IN in_coin_pub BYTEA, - IN in_known_coin_id INT8, - IN in_coin_sig BYTEA, - IN in_recoup_timestamp INT8, - OUT out_recoup_ok BOOLEAN, - OUT out_internal_failure BOOLEAN, - OUT out_recoup_timestamp INT8) -LANGUAGE plpgsql -AS $$ -DECLARE - tmp_val INT8; -- amount recouped -DECLARE - tmp_frac INT8; -- amount recouped -BEGIN - --- Shards: UPDATE known_coins (by coin_pub) --- SELECT recoup_refresh (by coin_pub) --- UPDATE known_coins (by coin_pub) --- INSERT recoup_refresh (by coin_pub) - - -out_internal_failure=FALSE; - - --- Check remaining balance of the coin. -SELECT - remaining_frac - ,remaining_val - INTO - tmp_frac - ,tmp_val -FROM exchange.known_coins - WHERE coin_pub=in_coin_pub; - -IF NOT FOUND -THEN - out_internal_failure=TRUE; - out_recoup_ok=FALSE; - RETURN; -END IF; - -IF tmp_val + tmp_frac = 0 -THEN - -- Check for idempotency - SELECT - recoup_timestamp - INTO - out_recoup_timestamp - FROM exchange.recoup_refresh - WHERE coin_pub=in_coin_pub; - out_recoup_ok=FOUND; - RETURN; -END IF; - --- Update balance of the coin. -UPDATE known_coins - SET - remaining_frac=0 - ,remaining_val=0 - WHERE coin_pub=in_coin_pub; - - --- Credit the old coin. -UPDATE known_coins - SET - remaining_frac=remaining_frac+tmp_frac - - CASE - WHEN remaining_frac+tmp_frac >= 100000000 - THEN 100000000 - ELSE 0 - END, - remaining_val=remaining_val+tmp_val - + CASE - WHEN remaining_frac+tmp_frac >= 100000000 - THEN 1 - ELSE 0 - END - WHERE coin_pub=in_old_coin_pub; - - -IF NOT FOUND -THEN - RAISE NOTICE 'failed to increase old coin balance from recoup'; - out_recoup_ok=TRUE; - out_internal_failure=TRUE; - RETURN; -END IF; - - -INSERT INTO exchange.recoup_refresh - (coin_pub - ,known_coin_id - ,coin_sig - ,coin_blind - ,amount_val - ,amount_frac - ,recoup_timestamp - ,rrc_serial - ) -VALUES - (in_coin_pub - ,in_known_coin_id - ,in_coin_sig - ,in_coin_blind - ,tmp_val - ,tmp_frac - ,in_recoup_timestamp - ,in_rrc_serial); - --- Normal end, everything is fine. -out_recoup_ok=TRUE; -out_recoup_timestamp=in_recoup_timestamp; - -END $$; - - - - --- COMMENT ON FUNCTION exchange_do_recoup_to_coin(INT8, INT4, BYTEA, BOOLEAN, BOOLEAN) --- IS 'Executes a recoup-refresh of a coin that was obtained from a refresh-reveal process'; - - - -CREATE OR REPLACE PROCEDURE exchange_do_gc( - IN in_ancient_date INT8, - IN in_now INT8) -LANGUAGE plpgsql -AS $$ -DECLARE - reserve_uuid_min INT8; -- minimum reserve UUID still alive -DECLARE - melt_min INT8; -- minimum melt still alive -DECLARE - coin_min INT8; -- minimum known_coin still alive -DECLARE - deposit_min INT8; -- minimum deposit still alive -DECLARE - reserve_out_min INT8; -- minimum reserve_out still alive -DECLARE - denom_min INT8; -- minimum denomination still alive -BEGIN - -DELETE FROM exchange.prewire - WHERE finished=TRUE; - -DELETE FROM exchange.wire_fee - WHERE end_date < in_ancient_date; - --- TODO: use closing fee as threshold? -DELETE FROM exchange.reserves - WHERE gc_date < in_now - AND current_balance_val = 0 - AND current_balance_frac = 0; - -SELECT - reserve_out_serial_id - INTO - reserve_out_min - FROM exchange.reserves_out - ORDER BY reserve_out_serial_id ASC - LIMIT 1; - -DELETE FROM exchange.recoup - WHERE reserve_out_serial_id < reserve_out_min; --- FIXME: recoup_refresh lacks GC! - -SELECT - reserve_uuid - INTO - reserve_uuid_min - FROM exchange.reserves - ORDER BY reserve_uuid ASC - LIMIT 1; - -DELETE FROM exchange.reserves_out - WHERE reserve_uuid < reserve_uuid_min; - --- FIXME: this query will be horribly slow; --- need to find another way to formulate it... -DELETE FROM exchange.denominations - WHERE expire_legal < in_now - AND denominations_serial NOT IN - (SELECT DISTINCT denominations_serial - FROM exchange.reserves_out) - AND denominations_serial NOT IN - (SELECT DISTINCT denominations_serial - FROM exchange.known_coins - WHERE coin_pub IN - (SELECT DISTINCT coin_pub - FROM exchange.recoup)) - AND denominations_serial NOT IN - (SELECT DISTINCT denominations_serial - FROM exchange.known_coins - WHERE coin_pub IN - (SELECT DISTINCT coin_pub - FROM exchange.recoup_refresh)); - -SELECT - melt_serial_id - INTO - melt_min - FROM exchange.refresh_commitments - ORDER BY melt_serial_id ASC - LIMIT 1; - -DELETE FROM exchange.refresh_revealed_coins - WHERE melt_serial_id < melt_min; - -DELETE FROM exchange.refresh_transfer_keys - WHERE melt_serial_id < melt_min; - -SELECT - known_coin_id - INTO - coin_min - FROM exchange.known_coins - ORDER BY known_coin_id ASC - LIMIT 1; - -DELETE FROM exchange.deposits - WHERE known_coin_id < coin_min; - -SELECT - deposit_serial_id - INTO - deposit_min - FROM exchange.deposits - ORDER BY deposit_serial_id ASC - LIMIT 1; - -DELETE FROM exchange.refunds - WHERE deposit_serial_id < deposit_min; - -DELETE FROM exchange.aggregation_tracking - WHERE deposit_serial_id < deposit_min; - -SELECT - denominations_serial - INTO - denom_min - FROM exchange.denominations - ORDER BY denominations_serial ASC - LIMIT 1; - -DELETE FROM exchange.cs_nonce_locks - WHERE max_denomination_serial <= denom_min; - -END $$; - - - - -CREATE OR REPLACE FUNCTION exchange_do_purse_deposit( - IN in_partner_id INT8, - IN in_purse_pub BYTEA, - IN in_amount_with_fee_val INT8, - IN in_amount_with_fee_frac INT4, - IN in_coin_pub BYTEA, - IN in_coin_sig BYTEA, - IN in_amount_without_fee_val INT8, - IN in_amount_without_fee_frac INT4, - OUT out_balance_ok BOOLEAN, - OUT out_conflict BOOLEAN) -LANGUAGE plpgsql -AS $$ -DECLARE - was_merged BOOLEAN; -DECLARE - psi INT8; -- partner's serial ID (set if merged) -DECLARE - my_amount_val INT8; -- total in purse -DECLARE - my_amount_frac INT4; -- total in purse -DECLARE - was_paid BOOLEAN; -DECLARE - my_reserve_pub BYTEA; -BEGIN - --- Store the deposit request. -INSERT INTO exchange.purse_deposits - (partner_serial_id - ,purse_pub - ,coin_pub - ,amount_with_fee_val - ,amount_with_fee_frac - ,coin_sig) - VALUES - (in_partner_id - ,in_purse_pub - ,in_coin_pub - ,in_amount_with_fee_val - ,in_amount_with_fee_frac - ,in_coin_sig) - ON CONFLICT DO NOTHING; - -IF NOT FOUND -THEN - -- Idempotency check: check if coin_sig is the same, - -- if so, success, otherwise conflict! - PERFORM - FROM exchange.purse_deposits - WHERE coin_pub = in_coin_pub - AND purse_pub = in_purse_pub - AND coin_sig = in_cion_sig; - IF NOT FOUND - THEN - -- Deposit exists, but with differences. Not allowed. - out_balance_ok=FALSE; - out_conflict=TRUE; - RETURN; - END IF; -END IF; - - --- Debit the coin --- 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_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_balance_ok=FALSE; - out_conflict=FALSE; - RETURN; -END IF; - - --- Credit the purse. -UPDATE purse_requests - SET - balance_frac=balance_frac+in_amount_without_fee_frac - - CASE - WHEN balance_frac+in_amount_without_fee_frac >= 100000000 - THEN 100000000 - ELSE 0 - END, - balance_val=balance_val+in_amount_without_fee_val - + CASE - WHEN balance_frac+in_amount_without_fee_frac >= 100000000 - THEN 1 - ELSE 0 - END - WHERE purse_pub=in_purse_pub; - -out_conflict=FALSE; -out_balance_ok=TRUE; - --- See if we can finish the merge or need to update the trigger time and partner. -SELECT partner_serial_id - ,reserve_pub - INTO psi - ,my_reserve_pub - FROM exchange.purse_merges - WHERE purse_pub=in_purse_pub; - -IF NOT FOUND -THEN - RETURN; -END IF; - -SELECT - amount_with_fee_val - ,amount_with_fee_frac - INTO - my_amount_val - ,my_amount_frac - FROM exchange.purse_requests - WHERE (purse_pub=in_purse_pub) - AND ( ( ( (amount_with_fee_val <= balance_val) - AND (amount_with_fee_frac <= balance_frac) ) - OR (amount_with_fee_val < balance_val) ) ); -IF NOT FOUND -THEN - RETURN; -END IF; - -IF (0 != psi) -THEN - -- The taler-exchange-router will take care of this. - UPDATE purse_actions - SET action_date=0 --- "immediately" - ,partner_serial_id=psi - WHERE purse_pub=in_purse_pub; -ELSE - -- This is a local reserve, update balance immediately. - UPDATE reserves - SET - current_balance_frac=current_balance_frac+my_amount_frac - - CASE - WHEN current_balance_frac + my_amount_frac >= 100000000 - THEN 100000000 - ELSE 0 - END, - current_balance_val=current_balance_val+my_amount_val - + CASE - WHEN current_balance_frac + my_amount_frac >= 100000000 - THEN 1 - ELSE 0 - END - WHERE reserve_pub=my_reserve_pub; - - -- ... and mark purse as finished. - -- FIXME: combine with UPDATE above? - UPDATE purse_requests - SET finished=true - WHERE purse_pub=in_purse_pub; -END IF; - - -END $$; - - - - -CREATE OR REPLACE FUNCTION exchange_do_purse_merge( - IN in_purse_pub BYTEA, - IN in_merge_sig BYTEA, - IN in_merge_timestamp INT8, - IN in_reserve_sig BYTEA, - IN in_partner_url VARCHAR, - IN in_reserve_pub BYTEA, - IN in_wallet_h_payto BYTEA, - IN in_require_kyc BOOLEAN, - OUT out_no_partner BOOLEAN, - OUT out_no_balance BOOLEAN, - OUT out_no_kyc BOOLEAN, - OUT out_no_reserve BOOLEAN, - OUT out_conflict BOOLEAN) -LANGUAGE plpgsql -AS $$ -DECLARE - my_amount_val INT8; -DECLARE - my_amount_frac INT4; -DECLARE - my_purse_fee_val INT8; -DECLARE - my_purse_fee_frac INT4; -DECLARE - my_partner_serial_id INT8; -DECLARE - my_finished BOOLEAN; -BEGIN - -IF in_partner_url IS NULL -THEN - my_partner_serial_id=0; -ELSE - SELECT - partner_serial_id - INTO - my_partner_serial_id - FROM exchange.partners - WHERE partner_base_url=in_partner_url - AND start_date <= in_merge_timestamp - AND end_date > in_merge_timestamp; - IF NOT FOUND - THEN - out_no_partner=TRUE; - out_conflict=FALSE; - out_no_kyc=FALSE; - out_no_reserve=FALSE; - RETURN; - END IF; -END IF; - -out_no_partner=FALSE; - - --- Check purse is 'full'. -SELECT amount_with_fee_val - ,amount_with_fee_frac - ,purse_fee_val - ,purse_fee_frac - ,finished - INTO my_amount_val - ,my_amount_frac - ,my_purse_fee_val - ,my_purse_fee_frac - ,my_finished - FROM exchange.purse_requests - WHERE purse_pub=in_purse_pub - AND balance_val >= amount_with_fee_val - AND ( (balance_frac >= amount_with_fee_frac) OR - (balance_val > amount_with_fee_val) ); -IF NOT FOUND -THEN - out_no_balance=TRUE; - out_conflict=FALSE; - out_no_kyc=FALSE; - out_no_reserve=FALSE; - RETURN; -END IF; -out_no_balance=FALSE; - --- Store purse merge signature, checks for purse_pub uniqueness -INSERT INTO exchange.purse_merges - (partner_serial_id - ,reserve_pub - ,purse_pub - ,merge_sig - ,merge_timestamp) - VALUES - (my_partner_serial_id - ,in_reserve_pub - ,in_purse_pub - ,in_merge_sig - ,in_merge_timestamp) - ON CONFLICT DO NOTHING; - -IF NOT FOUND -THEN - -- Idempotency check: see if an identical record exists. - -- Note that by checking 'merge_sig', we implicitly check - -- identity over everything that the signature covers. - PERFORM - FROM exchange.purse_merges - WHERE purse_pub=in_purse_pub - AND merge_sig=in_merge_sig; - IF NOT FOUND - THEN - -- Purse was merged, but to some other reserve. Not allowed. - out_conflict=TRUE; - out_no_kyc=FALSE; - out_no_reserve=FALSE; - RETURN; - END IF; - - -- "success" - out_conflict=FALSE; - out_no_kyc=FALSE; - out_no_reserve=FALSE; - RETURN; -END IF; -out_conflict=FALSE; - -ASSERT NOT my_finished, 'internal invariant failed'; - -IF ( (in_partner_url IS NULL) AND - (in_require_kyc) ) -THEN - -- Need to do KYC check. - SELECT NOT kyc_passed - INTO out_no_kyc - FROM exchange.reserves - WHERE reserve_pub=in_reserve_pub; - - IF NOT FOUND - THEN - out_no_kyc=TRUE; - out_no_reserve=TRUE; - RETURN; - END IF; - out_no_reserve=FALSE; - - IF (out_no_kyc) - THEN - RETURN; - END IF; -ELSE - -- KYC is not our responsibility - out_no_reserve=FALSE; - out_no_kyc=FALSE; -END IF; - - - --- Store account merge signature. -INSERT INTO exchange.account_merges - (reserve_pub - ,reserve_sig - ,purse_pub - ,wallet_h_payto) - VALUES - (in_reserve_pub - ,in_reserve_sig - ,in_purse_pub - ,in_wallet_h_payto); - --- If we need a wad transfer, mark purse ready for it. -IF (0 != my_partner_serial_id) -THEN - -- The taler-exchange-router will take care of this. - UPDATE purse_actions - SET action_date=0 --- "immediately" - ,partner_serial_id=my_partner_serial_id - WHERE purse_pub=in_purse_pub; -ELSE - -- This is a local reserve, update reserve balance immediately. - - -- Refund the purse fee, by adding it to the purse value: - my_amount_val = my_amount_val + my_purse_fee_val; - my_amount_frac = my_amount_frac + my_purse_fee_frac; - -- normalize result - my_amount_val = my_amount_val + my_amount_frac / 100000000; - my_amount_frac = my_amount_frac % 100000000; - - UPDATE reserves - SET - current_balance_frac=current_balance_frac+my_amount_frac - - CASE - WHEN current_balance_frac + my_amount_frac >= 100000000 - THEN 100000000 - ELSE 0 - END, - current_balance_val=current_balance_val+my_amount_val - + CASE - WHEN current_balance_frac + my_amount_frac >= 100000000 - THEN 1 - ELSE 0 - END - WHERE reserve_pub=in_reserve_pub; - - -- ... and mark purse as finished. - UPDATE purse_requests - SET finished=true - WHERE purse_pub=in_purse_pub; -END IF; - - -RETURN; - -END $$; - -COMMENT ON FUNCTION exchange_do_purse_merge(BYTEA, BYTEA, INT8, BYTEA, VARCHAR, BYTEA, BYTEA, BOOLEAN) - IS 'Checks that the partner exists, the purse has not been merged with a different reserve and that the purse is full. If so, persists the merge data and either merges the purse with the reserve or marks it as ready for the taler-exchange-router. Caller MUST abort the transaction on failures so as to not persist data by accident.'; - - -CREATE OR REPLACE FUNCTION exchange_do_reserve_purse( - IN in_purse_pub BYTEA, - IN in_merge_sig BYTEA, - IN in_merge_timestamp INT8, - IN in_reserve_sig BYTEA, - IN in_reserve_quota BOOLEAN, - IN in_purse_fee_val INT8, - IN in_purse_fee_frac INT4, - IN in_reserve_pub BYTEA, - IN in_wallet_h_payto BYTEA, - IN in_require_kyc BOOLEAN, - OUT out_no_funds BOOLEAN, - OUT out_no_kyc BOOLEAN, - OUT out_no_reserve BOOLEAN, - OUT out_conflict BOOLEAN) -LANGUAGE plpgsql -AS $$ -BEGIN - --- Store purse merge signature, checks for purse_pub uniqueness -INSERT INTO exchange.purse_merges - (partner_serial_id - ,reserve_pub - ,purse_pub - ,merge_sig - ,merge_timestamp) - VALUES - (0 - ,in_reserve_pub - ,in_purse_pub - ,in_merge_sig - ,in_merge_timestamp) - ON CONFLICT DO NOTHING; - -IF NOT FOUND -THEN - -- Idempotency check: see if an identical record exists. - -- Note that by checking 'merge_sig', we implicitly check - -- identity over everything that the signature covers. - PERFORM - FROM exchange.purse_merges - WHERE purse_pub=in_purse_pub - AND merge_sig=in_merge_sig; - IF NOT FOUND - THEN - -- Purse was merged, but to some other reserve. Not allowed. - out_conflict=TRUE; - out_no_kyc=FALSE; - out_no_reserve=FALSE; - out_no_funds=FALSE; - RETURN; - END IF; - - -- "success" - out_conflict=FALSE; - out_no_funds=FALSE; - out_no_kyc=FALSE; - out_no_reserve=FALSE; - RETURN; -END IF; -out_conflict=FALSE; - -SELECT NOT kyc_passed - INTO out_no_kyc - FROM exchange.reserves - WHERE reserve_pub=in_reserve_pub; - -IF NOT FOUND -THEN - out_no_kyc=TRUE; - out_no_reserve=TRUE; - out_no_funds=TRUE; - RETURN; -END IF; -out_no_reserve=FALSE; - -IF (out_no_kyc AND in_require_kyc) -THEN - out_no_funds=FALSE; - RETURN; -END IF; - -IF (in_reserve_quota) -THEN - -- Increment active purses per reserve (and check this is allowed) - UPDATE reserves - SET purses_active=purses_active+1 - ,kyc_required=TRUE - WHERE reserve_pub=in_reserve_pub - AND purses_active < purses_allowed; - IF NOT FOUND - THEN - out_no_funds=TRUE; - RETURN; - END IF; -ELSE - -- UPDATE reserves balance (and check if balance is enough to pay the fee) - UPDATE reserves - SET - current_balance_frac=current_balance_frac-in_purse_fee_frac - + CASE - WHEN current_balance_frac < in_purse_fee_frac - THEN 100000000 - ELSE 0 - END, - current_balance_val=current_balance_val-in_purse_fee_val - - CASE - WHEN current_balance_frac < in_purse_fee_frac - THEN 1 - ELSE 0 - END - ,kyc_required=TRUE - WHERE reserve_pub=in_reserve_pub - AND ( (current_balance_val > in_purse_fee_val) OR - ( (current_balance_frac >= in_purse_fee_frac) AND - (current_balance_val >= in_purse_fee_val) ) ); - IF NOT FOUND - THEN - out_no_funds=TRUE; - RETURN; - END IF; -END IF; - -out_no_funds=FALSE; - - --- Store account merge signature. -INSERT INTO exchange.account_merges - (reserve_pub - ,reserve_sig - ,purse_pub - ,wallet_h_payto) - VALUES - (in_reserve_pub - ,in_reserve_sig - ,in_purse_pub - ,in_wallet_h_payto); - -END $$; - -COMMENT ON FUNCTION exchange_do_reserve_purse(BYTEA, BYTEA, INT8, BYTEA, BOOLEAN, INT8, INT4, BYTEA, BYTEA, BOOLEAN) - IS 'Create a purse for a reserve.'; - - - - - - -CREATE OR REPLACE FUNCTION exchange_do_account_merge( - IN in_purse_pub BYTEA, - IN in_reserve_pub BYTEA, - IN in_reserve_sig BYTEA, - OUT out_balance_ok BOOLEAN, - OUT out_conflict BOOLEAN) -LANGUAGE plpgsql -AS $$ -BEGIN - -- FIXME: function/API is dead! Do DCE? -END $$; - - - -CREATE OR REPLACE FUNCTION exchange_do_expire_purse( - IN in_start_time INT8, - IN in_end_time INT8, - OUT out_found BOOLEAN) -LANGUAGE plpgsql -AS $$ -DECLARE - my_purse_pub BYTEA; -DECLARE - my_deposit record; -BEGIN - -SELECT purse_pub - INTO my_purse_pub - FROM exchange.purse_requests - WHERE (purse_expiration >= in_start_time) AND - (purse_expiration < in_end_time) AND - (NOT finished) AND - (NOT refunded) - ORDER BY purse_expiration ASC - LIMIT 1; -out_found = FOUND; -IF NOT FOUND -THEN - RETURN; -END IF; - -UPDATE purse_requests - SET refunded=TRUE, - finished=TRUE - WHERE purse_pub=my_purse_pub; - -INSERT INTO exchange.purse_refunds - (purse_pub) - VALUES - (my_purse_pub); - --- restore balance to each coin deposited into the purse -FOR my_deposit IN - SELECT coin_pub - ,amount_with_fee_val - ,amount_with_fee_frac - FROM exchange.purse_deposits - WHERE purse_pub = my_purse_pub -LOOP - UPDATE known_coins SET - remaining_frac=remaining_frac+my_deposit.amount_with_fee_frac - - CASE - WHEN remaining_frac+my_deposit.amount_with_fee_frac >= 100000000 - THEN 100000000 - ELSE 0 - END, - remaining_val=remaining_val+my_deposit.amount_with_fee_val - + CASE - WHEN remaining_frac+my_deposit.amount_with_fee_frac >= 100000000 - THEN 1 - ELSE 0 - END - WHERE coin_pub = my_deposit.coin_pub; - END LOOP; -END $$; - -COMMENT ON FUNCTION exchange_do_expire_purse(INT8,INT8) - IS 'Finds an expired purse in the given time range and refunds the coins (if any).'; - - - - -CREATE OR REPLACE FUNCTION exchange_do_history_request( - IN in_reserve_pub BYTEA, - IN in_reserve_sig BYTEA, - IN in_request_timestamp INT8, - IN in_history_fee_val INT8, - IN in_history_fee_frac INT4, - OUT out_balance_ok BOOLEAN, - OUT out_idempotent BOOLEAN) -LANGUAGE plpgsql -AS $$ -BEGIN - - -- Insert and check for idempotency. - INSERT INTO exchange.history_requests - (reserve_pub - ,request_timestamp - ,reserve_sig - ,history_fee_val - ,history_fee_frac) - VALUES - (in_reserve_pub - ,in_request_timestamp - ,in_reserve_sig - ,in_history_fee_val - ,in_history_fee_frac) - ON CONFLICT DO NOTHING; - - IF NOT FOUND - THEN - out_balance_ok=TRUE; - out_idempotent=TRUE; - RETURN; - END IF; - - out_idempotent=FALSE; - - -- Update reserve balance. - UPDATE reserves - SET - current_balance_frac=current_balance_frac-in_history_fee_frac - + CASE - WHEN current_balance_frac < in_history_fee_frac - THEN 100000000 - ELSE 0 - END, - current_balance_val=current_balance_val-in_history_fee_val - - CASE - WHEN current_balance_frac < in_history_fee_frac - THEN 1 - ELSE 0 - END - WHERE - reserve_pub=in_reserve_pub - AND ( (current_balance_val > in_history_fee_val) OR - ( (current_balance_frac >= in_history_fee_frac) AND - (current_balance_val >= in_history_fee_val) ) ); - - IF NOT FOUND - THEN - -- Either reserve does not exist, or balance insufficient. - -- Both we treat the same here as balance insufficient. - out_balance_ok=FALSE; - RETURN; - END IF; - - out_balance_ok=TRUE; -END $$; - - -CREATE OR REPLACE FUNCTION exchange_do_close_request( - IN in_reserve_pub BYTEA, - IN in_close_timestamp INT8, - IN in_reserve_sig BYTEA, - OUT out_final_balance_val INT8, - OUT out_final_balance_frac INT4, - OUT out_balance_ok BOOLEAN, - OUT out_conflict BOOLEAN) -LANGUAGE plpgsql -AS $$ -BEGIN - - SELECT - current_balance_val - ,current_balance_frac - INTO - out_final_balance_val - ,out_final_balance_frac - FROM exchange.reserves - WHERE reserve_pub=in_reserve_pub; - - IF NOT FOUND - THEN - out_final_balance_val=0; - out_final_balance_frac=0; - out_balance_ok = FALSE; - out_conflict = FALSE; - END IF; - - INSERT INTO exchange.close_requests - (reserve_pub - ,close_timestamp - ,reserve_sig - ,close_val - ,close_frac) - VALUES - (in_reserve_pub - ,in_close_timestamp - ,in_reserve_sig - ,out_final_balance_val - ,out_final_balance_frac) - ON CONFLICT DO NOTHING; - out_conflict = NOT FOUND; - - UPDATE reserves SET - current_balance_val=0 - ,current_balance_frac=0 - WHERE reserve_pub=in_reserve_pub; - out_balance_ok = TRUE; - -END $$; - - -COMMIT; |