summaryrefslogtreecommitdiff
path: root/src/exchangedb/procedures.sql
diff options
context:
space:
mode:
Diffstat (limited to 'src/exchangedb/procedures.sql')
-rw-r--r--src/exchangedb/procedures.sql2289
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;