mte/include/sql/procedures.sql
2025-11-11 10:08:37 +01:00

5139 lines
130 KiB
PL/PgSQL

BEGIN;
SET search_path TO exchange;
CREATE OR REPLACE FUNCTION create_partitioned_table(
IN table_definition TEXT
,IN table_name TEXT
,IN main_table_partition_str TEXT
,IN partition_suffix TEXT DEFAULT NULL
)
RETURNS VOID
LANGUAGE plpgsql
AS $$
BEGIN
IF (partition_suffix IS NULL)
THEN
main_table_partition_str = '';
ELSE
IF (partition_suffix::int > 0)
THEN
table_name=table_name || '_' || partition_suffix;
END IF;
END IF;
EXECUTE FORMAT(
table_definition,
table_name,
main_table_partition_str
);
END $$;
COMMENT ON FUNCTION create_partitioned_table
IS 'Generic function to create a table that is partitioned or sharded.';
CREATE OR REPLACE FUNCTION comment_partitioned_table(
IN table_comment TEXT
,IN table_name TEXT
,IN partition_suffix TEXT DEFAULT NULL
)
RETURNS VOID
LANGUAGE plpgsql
AS $$
BEGIN
IF ( (partition_suffix IS NOT NULL) AND
(partition_suffix::int > 0) )
THEN
table_name=table_name || '_' || partition_suffix;
END IF;
EXECUTE FORMAT(
'COMMENT ON TABLE %s IS %s'
,table_name
,quote_literal(table_comment)
);
END $$;
COMMENT ON FUNCTION comment_partitioned_table
IS 'Generic function to create a comment on table that is partitioned.';
CREATE OR REPLACE FUNCTION comment_partitioned_column(
IN table_comment TEXT
,IN column_name TEXT
,IN table_name TEXT
,IN partition_suffix TEXT DEFAULT NULL
)
RETURNS VOID
LANGUAGE plpgsql
AS $$
BEGIN
IF ( (partition_suffix IS NOT NULL) AND
(partition_suffix::int > 0) )
THEN
table_name=table_name || '_' || partition_suffix;
END IF;
EXECUTE FORMAT(
'COMMENT ON COLUMN %s.%s IS %s'
,table_name
,column_name
,quote_literal(table_comment)
);
END $$;
COMMENT ON FUNCTION comment_partitioned_column
IS 'Generic function to create a comment on column of a table that is partitioned.';
CREATE OR REPLACE FUNCTION exchange_do_create_tables(
num_partitions INTEGER
)
RETURNS VOID
LANGUAGE plpgsql
AS $$
DECLARE
tc CURSOR FOR
SELECT table_serial_id
,name
,action
,partitioned
,by_range
FROM exchange.exchange_tables
WHERE NOT finished
ORDER BY table_serial_id ASC;
BEGIN
FOR rec IN tc
LOOP
CASE rec.action
WHEN 'create'
THEN
IF (rec.partitioned AND
(num_partitions IS NOT NULL))
THEN
EXECUTE FORMAT(
'SELECT exchange.create_table_%s (%s)'::text
,rec.name
,quote_literal('0')
);
IF (rec.by_range OR
(num_partitions = 0))
THEN
IF (rec.by_range)
THEN
EXECUTE FORMAT(
'CREATE TABLE exchange.%s_default'
' PARTITION OF %s'
' DEFAULT'
,rec.name
,rec.name
);
ELSE
EXECUTE FORMAT(
'CREATE TABLE exchange.%s_default'
' PARTITION OF %s'
' FOR VALUES WITH (MODULUS 1, REMAINDER 0)'
,rec.name
,rec.name
);
END IF;
ELSE
FOR i IN 1..num_partitions LOOP
EXECUTE FORMAT(
'CREATE TABLE exchange.%I'
' PARTITION OF %I'
' FOR VALUES WITH (MODULUS %s, REMAINDER %s)'
,rec.name || '_' || i
,rec.name
,num_partitions
,i-1
);
END LOOP;
END IF;
ELSE
EXECUTE FORMAT(
'SELECT exchange.create_table_%s ()'::text
,rec.name
);
END IF;
EXECUTE FORMAT(
'DROP FUNCTION exchange.create_table_%s'::text
,rec.name
);
WHEN 'alter'
THEN
EXECUTE FORMAT(
'SELECT exchange.alter_table_%s ()'::text
,rec.name
);
EXECUTE FORMAT(
'DROP FUNCTION exchange.alter_table_%s'::text
,rec.name
);
WHEN 'constrain'
THEN
ASSERT rec.partitioned, 'constrain action only applies to partitioned tables';
IF (num_partitions IS NULL)
THEN
EXECUTE FORMAT(
'SELECT exchange.constrain_table_%s (NULL)'::text
,rec.name
);
ELSE
IF ( (num_partitions = 0) OR
(rec.by_range) )
THEN
EXECUTE FORMAT(
'SELECT exchange.constrain_table_%s (%s)'::text
,rec.name
,quote_literal('default')
);
ELSE
FOR i IN 1..num_partitions LOOP
EXECUTE FORMAT(
'SELECT exchange.constrain_table_%s (%s)'::text
,rec.name
,quote_literal(i)
);
END LOOP;
END IF;
END IF;
EXECUTE FORMAT(
'DROP FUNCTION exchange.constrain_table_%s'::text
,rec.name
);
WHEN 'foreign'
THEN
IF (num_partitions IS NULL)
THEN
EXECUTE FORMAT(
'SELECT exchange.foreign_table_%s (%s)'::text
,rec.name
,NULL
);
END IF;
EXECUTE FORMAT(
'DROP FUNCTION exchange.foreign_table_%s'::text
,rec.name
);
WHEN 'master'
THEN
EXECUTE FORMAT(
'SELECT exchange.master_table_%s ()'::text
,rec.name
);
EXECUTE FORMAT(
'DROP FUNCTION exchange.master_table_%s'::text
,rec.name
);
ELSE
ASSERT FALSE, 'unsupported action type: ' || rec.action;
END CASE;
UPDATE exchange.exchange_tables
SET finished=TRUE
WHERE table_serial_id=rec.table_serial_id;
END LOOP;
END $$;
COMMENT ON FUNCTION exchange_do_create_tables
IS 'Creates all tables for the given number of partitions that need creating. Does NOT support sharding.';
CREATE OR REPLACE FUNCTION amount_normalize(
IN amount taler_amount
,OUT normalized taler_amount
)
LANGUAGE plpgsql
AS $$
BEGIN
normalized.val = amount.val + amount.frac / 100000000;
normalized.frac = amount.frac % 100000000;
END $$;
COMMENT ON FUNCTION amount_normalize
IS 'Returns the normalized amount by adding to the .val the value of (.frac / 100000000) and removing the modulus 100000000 from .frac.';
CREATE OR REPLACE FUNCTION amount_add(
IN a taler_amount
,IN b taler_amount
,OUT sum taler_amount
)
LANGUAGE plpgsql
AS $$
BEGIN
sum = (a.val + b.val, a.frac + b.frac);
CALL amount_normalize(sum ,sum);
IF (sum.val > (1<<52))
THEN
RAISE EXCEPTION 'addition overflow';
END IF;
END $$;
COMMENT ON FUNCTION amount_add
IS 'Returns the normalized sum of two amounts. It raises an exception when the resulting .val is larger than 2^52';
CREATE OR REPLACE FUNCTION amount_left_minus_right(
IN l taler_amount
,IN r taler_amount
,OUT diff taler_amount
,OUT ok BOOLEAN
)
LANGUAGE plpgsql
AS $$
BEGIN
IF (l.val > r.val)
THEN
ok = TRUE;
IF (l.frac >= r.frac)
THEN
diff.val = l.val - r.val;
diff.frac = l.frac - r.frac;
ELSE
diff.val = l.val - r.val - 1;
diff.frac = l.frac + 100000000 - r.frac;
END IF;
ELSE
IF (l.val = r.val) AND (l.frac >= r.frac)
THEN
diff.val = 0;
diff.frac = l.frac - r.frac;
ok = TRUE;
ELSE
diff = (-1, -1);
ok = FALSE;
END IF;
END IF;
RETURN;
END $$;
COMMENT ON FUNCTION amount_left_minus_right
IS 'Subtracts the right amount from the left and returns the difference and TRUE, if the left amount is larger than the right, or an invalid amount and FALSE otherwise.';
DROP FUNCTION IF EXISTS exchange_do_withdraw;
CREATE FUNCTION exchange_do_withdraw(
IN in_amount_with_fee taler_amount,
IN in_reserve_pub BYTEA,
IN in_reserve_sig BYTEA,
IN in_now INT8,
IN in_min_reserve_gc INT8,
IN in_planchets_h BYTEA,
IN in_maximum_age_committed INT2,
IN in_noreveal_index INT2,
IN in_selected_h BYTEA,
IN in_denom_serials INT8[],
IN in_denom_sigs BYTEA[],
IN in_blinding_seed BYTEA,
IN in_cs_r_values BYTEA[],
IN in_cs_r_choices INT8,
OUT out_reserve_found BOOLEAN,
OUT out_balance_ok BOOLEAN,
OUT out_reserve_balance taler_amount,
OUT out_age_ok BOOLEAN,
OUT out_required_age INT2,
OUT out_reserve_birthday INT4,
OUT out_idempotent BOOLEAN,
OUT out_noreveal_index INT2,
OUT out_nonce_reuse BOOLEAN)
LANGUAGE plpgsql
AS $$
DECLARE
my_reserve RECORD;
my_difference RECORD;
my_balance taler_amount;
my_not_before DATE;
my_earliest_date DATE;
BEGIN
SELECT current_balance
,birthday
,gc_date
INTO my_reserve
FROM reserves
WHERE reserve_pub=in_reserve_pub;
out_reserve_found = FOUND;
IF NOT out_reserve_found
THEN
out_age_ok = FALSE;
out_required_age = -1;
out_idempotent = FALSE;
out_noreveal_index = -1;
out_reserve_balance.val = 0;
out_reserve_balance.frac = 0;
out_balance_ok = FALSE;
out_nonce_reuse = FALSE;
out_reserve_birthday = 0;
RETURN;
END IF;
out_reserve_balance = my_reserve.current_balance;
out_reserve_birthday = my_reserve.birthday;
SELECT noreveal_index
INTO out_noreveal_index
FROM withdraw
WHERE reserve_pub = in_reserve_pub
AND planchets_h = in_planchets_h;
out_idempotent = FOUND;
IF out_idempotent
THEN
out_balance_ok = TRUE;
out_age_ok = TRUE;
out_required_age = -1;
out_nonce_reuse = FALSE;
RETURN;
END IF;
out_noreveal_index = -1;
IF (my_reserve.birthday <> 0)
THEN
my_not_before=date '1970-01-01' + my_reserve.birthday;
my_earliest_date = current_date - make_interval(in_maximum_age_committed);
IF ( (in_maximum_age_committed IS NULL) OR
(my_earliest_date < my_not_before) )
THEN
out_required_age = extract(year FROM age(current_date, my_not_before));
out_age_ok = FALSE;
out_balance_ok = TRUE;
out_nonce_reuse = FALSE;
RETURN;
END IF;
END IF;
out_age_ok = TRUE;
out_required_age = 0;
SELECT *
INTO my_difference
FROM amount_left_minus_right(out_reserve_balance
,in_amount_with_fee);
out_balance_ok = my_difference.ok;
IF NOT out_balance_ok
THEN
out_nonce_reuse = FALSE;
RETURN;
END IF;
my_balance = my_difference.diff;
in_min_reserve_gc=GREATEST(in_min_reserve_gc,my_reserve.gc_date);
UPDATE reserves SET
gc_date=in_min_reserve_gc
,current_balance=my_balance
WHERE
reserve_pub=in_reserve_pub;
IF in_blinding_seed IS NOT NULL
THEN
INSERT INTO unique_withdraw_blinding_seed
(blinding_seed)
VALUES
(in_blinding_seed)
ON CONFLICT DO NOTHING;
IF NOT FOUND
THEN
out_nonce_reuse = TRUE;
RETURN;
END IF;
END IF;
out_nonce_reuse = FALSE;
INSERT INTO withdraw
(planchets_h
,execution_date
,max_age
,amount_with_fee
,reserve_pub
,reserve_sig
,noreveal_index
,denom_serials
,selected_h
,blinding_seed
,cs_r_values
,cs_r_choices
,denom_sigs)
VALUES
(in_planchets_h
,in_now
,in_maximum_age_committed
,in_amount_with_fee
,in_reserve_pub
,in_reserve_sig
,in_noreveal_index
,in_denom_serials
,in_selected_h
,in_blinding_seed
,in_cs_r_values
,in_cs_r_choices
,in_denom_sigs)
ON CONFLICT DO NOTHING;
IF NOT FOUND
THEN
RAISE EXCEPTION 'Conflict on insert into withdraw despite idempotency check for reserve_pub(%) and planchets_h(%)!',
in_reserve_pub,
in_planchets_h;
END IF;
END $$;
COMMENT ON FUNCTION exchange_do_withdraw(
taler_amount,
BYTEA,
BYTEA,
INT8,
INT8,
BYTEA,
INT2,
INT2,
BYTEA,
INT8[],
BYTEA[],
BYTEA,
BYTEA[],
INT8)
IS 'Checks whether the reserve has sufficient balance for an withdraw operation (or the request is repeated and was previously approved) and that age requirements are met. If so updates the database with the result. Includes storing the hashes of all blinded planchets, (separately) the hashes of the chosen planchets and denomination signatures, or signaling idempotency (and previous noreveal_index) or nonce reuse';
DROP FUNCTION IF EXISTS exchange_do_refresh;
CREATE FUNCTION exchange_do_refresh(
IN in_rc BYTEA,
IN in_now INT8,
IN in_refresh_seed BYTEA,
IN in_planchets_h BYTEA,
IN in_amount_with_fee taler_amount,
IN in_blinding_seed BYTEA,
IN in_cs_r_values BYTEA[],
IN in_cs_r_choices INT8,
IN in_selected_h BYTEA,
IN in_denom_sigs BYTEA[],
IN in_denom_serials INT8[],
IN in_old_coin_pub BYTEA,
IN in_old_coin_sig BYTEA,
IN in_noreveal_index INT4,
IN in_zombie_required BOOLEAN,
OUT out_coin_found BOOLEAN,
OUT out_balance_ok BOOLEAN,
OUT out_zombie_bad BOOLEAN,
OUT out_nonce_reuse BOOLEAN,
OUT out_idempotent BOOLEAN,
OUT out_noreveal_index INT4,
OUT out_coin_balance taler_amount)
LANGUAGE plpgsql
AS $$
DECLARE
known_coin RECORD;
difference RECORD;
BEGIN
SELECT known_coin_id
,remaining
INTO known_coin
FROM known_coins
WHERE coin_pub = in_old_coin_pub;
IF NOT FOUND
THEN
out_coin_found = FALSE;
out_balance_ok = TRUE;
out_zombie_bad = FALSE;
out_nonce_reuse = FALSE;
out_idempotent = FALSE;
out_noreveal_index = -1 ;
out_coin_balance.val = 0;
out_coin_balance.frac = 0;
RETURN;
END IF;
out_coin_found = TRUE;
out_coin_balance = known_coin.remaining;
SELECT TRUE, noreveal_index
INTO out_idempotent, out_noreveal_index
FROM exchange.refresh
WHERE rc=in_rc;
IF out_idempotent
THEN
out_balance_ok = TRUE;
out_zombie_bad = FALSE;
out_nonce_reuse = FALSE;
RETURN;
END IF;
out_idempotent = FALSE;
out_noreveal_index = in_noreveal_index;
IF in_blinding_seed IS NOT NULL
THEN
INSERT INTO unique_refresh_blinding_seed
(blinding_seed)
VALUES
(in_blinding_seed)
ON CONFLICT DO NOTHING;
IF NOT FOUND
THEN
out_nonce_reuse = TRUE;
out_balance_ok = TRUE;
out_zombie_bad = FALSE;
RETURN;
END IF;
END IF;
out_nonce_reuse = FALSE;
INSERT INTO exchange.refresh
(rc
,execution_date
,old_coin_pub
,old_coin_sig
,planchets_h
,amount_with_fee
,noreveal_index
,refresh_seed
,blinding_seed
,cs_r_values
,cs_r_choices
,selected_h
,denom_sigs
,denom_serials
)
VALUES
(in_rc
,in_now
,in_old_coin_pub
,in_old_coin_sig
,in_planchets_h
,in_amount_with_fee
,in_noreveal_index
,in_refresh_seed
,in_blinding_seed
,in_cs_r_values
,in_cs_r_choices
,in_selected_h
,in_denom_sigs
,in_denom_serials
)
ON CONFLICT DO NOTHING;
IF NOT FOUND
THEN
RAISE EXCEPTION 'Conflict in refresh despite idempotency check for rc(%)!', rc;
RETURN;
END IF;
IF in_zombie_required
THEN
PERFORM
FROM recoup_refresh
WHERE refresh_id IN
(SELECT refresh_id
FROM refresh
WHERE old_coin_pub=in_old_coin_pub);
IF NOT FOUND
THEN
out_zombie_bad=TRUE;
out_balance_ok=FALSE;
RETURN;
END IF;
END IF;
out_zombie_bad=FALSE;
SELECT *
INTO difference
FROM amount_left_minus_right(out_coin_balance
,in_amount_with_fee);
out_balance_ok = difference.ok;
IF NOT out_balance_ok
THEN
RETURN;
END IF;
out_coin_balance = difference.diff;
UPDATE known_coins
SET
remaining = out_coin_balance
WHERE
known_coin_id = known_coin.known_coin_id;
END $$;
DROP FUNCTION IF EXISTS exchange_do_deposit;
CREATE FUNCTION exchange_do_deposit(
IN in_shard INT8,
IN in_merchant_pub BYTEA,
IN in_merchant_sig BYTEA,
IN in_wallet_timestamp INT8,
IN in_exchange_timestamp INT8,
IN in_refund_deadline INT8,
IN in_wire_deadline INT8,
IN in_h_contract_terms BYTEA,
IN in_wallet_data_hash BYTEA,
IN in_wire_salt BYTEA,
IN in_wire_target_h_payto BYTEA,
IN in_h_normalized_payto BYTEA,
IN in_policy_details_serial_id INT8,
IN in_policy_blocked BOOLEAN,
IN in_receiver_wire_account TEXT,
IN ina_coin_pub BYTEA[],
IN ina_coin_sig BYTEA[],
IN ina_amount_with_fee taler_amount[],
IN in_total_amount taler_amount,
IN in_is_wallet BOOL,
OUT out_exchange_timestamp INT8,
OUT out_insufficient_balance_coin_index INT4,
OUT out_conflict BOOL
)
LANGUAGE plpgsql
AS $$
DECLARE
wtsi INT8;
bdsi INT8;
i INT4;
ini_amount_with_fee taler_amount;
ini_coin_pub BYTEA;
ini_coin_sig BYTEA;
BEGIN
INSERT INTO kyc_targets
(h_normalized_payto
,is_wallet
) VALUES (
in_h_normalized_payto
,in_is_wallet
)
ON CONFLICT DO NOTHING;
INSERT INTO wire_targets
(wire_target_h_payto
,h_normalized_payto
,payto_uri
) VALUES (
in_wire_target_h_payto
,in_h_normalized_payto
,in_receiver_wire_account
)
ON CONFLICT DO NOTHING
RETURNING wire_target_serial_id
INTO wtsi;
IF NOT FOUND
THEN
SELECT wire_target_serial_id
INTO wtsi
FROM wire_targets
WHERE wire_target_h_payto=in_wire_target_h_payto;
END IF;
INSERT INTO batch_deposits
(shard
,merchant_pub
,merchant_sig
,wallet_timestamp
,exchange_timestamp
,refund_deadline
,wire_deadline
,h_contract_terms
,wallet_data_hash
,wire_salt
,wire_target_h_payto
,policy_details_serial_id
,policy_blocked
,total_amount
) VALUES (
in_shard
,in_merchant_pub
,in_merchant_sig
,in_wallet_timestamp
,in_exchange_timestamp
,in_refund_deadline
,in_wire_deadline
,in_h_contract_terms
,in_wallet_data_hash
,in_wire_salt
,in_wire_target_h_payto
,in_policy_details_serial_id
,in_policy_blocked
,in_total_amount)
ON CONFLICT DO NOTHING
RETURNING
batch_deposit_serial_id
INTO
bdsi;
IF NOT FOUND
THEN
SELECT
exchange_timestamp
,batch_deposit_serial_id
INTO
out_exchange_timestamp
,bdsi
FROM batch_deposits
WHERE shard=in_shard
AND merchant_pub=in_merchant_pub
AND h_contract_terms=in_h_contract_terms
AND wire_target_h_payto=in_wire_target_h_payto
AND ( (wallet_data_hash=in_wallet_data_hash) OR
(wallet_data_hash IS NULL AND in_wallet_data_hash IS NULL) )
AND wire_salt=in_wire_salt
AND wallet_timestamp=in_wallet_timestamp
AND refund_deadline=in_refund_deadline
AND wire_deadline=in_wire_deadline
AND ( (policy_details_serial_id=in_policy_details_serial_id) OR
(policy_details_serial_id IS NULL AND in_policy_details_serial_id IS NULL) );
IF NOT FOUND
THEN
out_conflict=TRUE;
RETURN;
END IF;
END IF;
out_conflict=FALSE;
FOR i IN 1..array_length(ina_coin_pub,1)
LOOP
ini_coin_pub = ina_coin_pub[i];
ini_coin_sig = ina_coin_sig[i];
ini_amount_with_fee = ina_amount_with_fee[i];
INSERT INTO coin_deposits
(batch_deposit_serial_id
,coin_pub
,coin_sig
,amount_with_fee
) VALUES (
bdsi
,ini_coin_pub
,ini_coin_sig
,ini_amount_with_fee
)
ON CONFLICT DO NOTHING;
IF FOUND
THEN
UPDATE known_coins kc
SET
remaining.frac=(kc.remaining).frac-ini_amount_with_fee.frac
+ CASE
WHEN (kc.remaining).frac < ini_amount_with_fee.frac
THEN 100000000
ELSE 0
END,
remaining.val=(kc.remaining).val-ini_amount_with_fee.val
- CASE
WHEN (kc.remaining).frac < ini_amount_with_fee.frac
THEN 1
ELSE 0
END
WHERE coin_pub=ini_coin_pub
AND ( ((kc.remaining).val > ini_amount_with_fee.val) OR
( ((kc.remaining).frac >= ini_amount_with_fee.frac) AND
((kc.remaining).val >= ini_amount_with_fee.val) ) );
IF NOT FOUND
THEN
out_insufficient_balance_coin_index=i-1;
RETURN;
END IF;
END IF;
END LOOP;
END $$;
DROP FUNCTION IF EXISTS exchange_do_check_deposit_idempotent;
CREATE FUNCTION exchange_do_check_deposit_idempotent(
IN in_shard INT8,
IN in_merchant_pub BYTEA,
IN in_wallet_timestamp INT8,
IN in_exchange_timestamp INT8,
IN in_refund_deadline INT8,
IN in_wire_deadline INT8,
IN in_h_contract_terms BYTEA,
IN in_wallet_data_hash BYTEA,
IN in_wire_salt BYTEA,
IN in_wire_target_h_payto BYTEA,
IN in_policy_details_serial_id INT8,
IN in_policy_blocked BOOLEAN,
IN ina_coin_pub BYTEA[],
IN ina_coin_sig BYTEA[],
IN ina_amount_with_fee taler_amount[],
OUT out_exchange_timestamp INT8,
OUT out_is_idempotent BOOL
)
LANGUAGE plpgsql
AS $$
DECLARE
wtsi INT8;
bdsi INT8;
i INT4;
ini_amount_with_fee taler_amount;
ini_coin_pub BYTEA;
ini_coin_sig BYTEA;
BEGIN
out_exchange_timestamp = in_exchange_timestamp;
SELECT wire_target_serial_id
INTO wtsi
FROM wire_targets
WHERE wire_target_h_payto=in_wire_target_h_payto;
IF NOT FOUND
THEN
out_is_idempotent = FALSE;
RETURN;
END IF;
SELECT
exchange_timestamp
,batch_deposit_serial_id
INTO
out_exchange_timestamp
,bdsi
FROM batch_deposits
WHERE shard=in_shard
AND merchant_pub=in_merchant_pub
AND h_contract_terms=in_h_contract_terms
AND wire_target_h_payto=in_wire_target_h_payto
AND ( (wallet_data_hash=in_wallet_data_hash) OR
(wallet_data_hash IS NULL AND in_wallet_data_hash IS NULL) )
AND wire_salt=in_wire_salt
AND wallet_timestamp=in_wallet_timestamp
AND refund_deadline=in_refund_deadline
AND wire_deadline=in_wire_deadline
AND ( (policy_details_serial_id=in_policy_details_serial_id) OR
(policy_details_serial_id IS NULL AND in_policy_details_serial_id IS NULL) );
IF NOT FOUND
THEN
out_is_idempotent=FALSE;
RETURN;
END IF;
FOR i IN 1..array_length(ina_coin_pub,1)
LOOP
ini_coin_pub = ina_coin_pub[i];
ini_coin_sig = ina_coin_sig[i];
ini_amount_with_fee = ina_amount_with_fee[i];
PERFORM FROM coin_deposits
WHERE batch_deposit_serial_id=bdsi
AND coin_pub=ini_coin_pub
AND coin_sig=ini_coin_sig
AND amount_with_fee=ini_amount_with_fee;
IF NOT FOUND
THEN
out_is_idempotent=FALSE;
RETURN;
END IF;
END LOOP;
out_is_idempotent=TRUE;
END $$;
CREATE OR REPLACE FUNCTION exchange_do_melt(
IN in_cs_rms BYTEA,
IN in_amount_with_fee taler_amount,
IN in_rc BYTEA,
IN in_old_coin_pub BYTEA,
IN in_old_coin_sig BYTEA,
IN in_known_coin_id INT8,
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
INSERT INTO exchange.refresh_commitments
(rc
,old_coin_pub
,old_coin_sig
,amount_with_fee
,noreveal_index
)
VALUES
(in_rc
,in_old_coin_pub
,in_old_coin_sig
,in_amount_with_fee
,in_noreveal_index)
ON CONFLICT DO NOTHING;
IF NOT FOUND
THEN
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;
RETURN;
END IF;
IF in_zombie_required
THEN
PERFORM
FROM recoup_refresh
WHERE rrc_serial IN
(SELECT rrc_serial
FROM refresh_revealed_coins
WHERE melt_serial_id IN
(SELECT melt_serial_id
FROM refresh_commitments
WHERE old_coin_pub=in_old_coin_pub));
IF NOT FOUND
THEN
out_zombie_bad=TRUE;
out_balance_ok=FALSE;
RETURN;
END IF;
END IF;
out_zombie_bad=FALSE;
UPDATE known_coins kc
SET
remaining.frac=(kc.remaining).frac-in_amount_with_fee.frac
+ CASE
WHEN (kc.remaining).frac < in_amount_with_fee.frac
THEN 100000000
ELSE 0
END,
remaining.val=(kc.remaining).val-in_amount_with_fee.val
- CASE
WHEN (kc.remaining).frac < in_amount_with_fee.frac
THEN 1
ELSE 0
END
WHERE coin_pub=in_old_coin_pub
AND ( ((kc.remaining).val > in_amount_with_fee.val) OR
( ((kc.remaining).frac >= in_amount_with_fee.frac) AND
((kc.remaining).val >= in_amount_with_fee.val) ) );
IF NOT FOUND
THEN
out_noreveal_index=-1;
out_balance_ok=FALSE;
RETURN;
END IF;
IF in_cs_rms IS NOT NULL
THEN
SELECT
denominations_serial
INTO
denom_max
FROM exchange.denominations
ORDER BY denominations_serial DESC
LIMIT 1;
INSERT INTO exchange.cs_nonce_locks
(nonce
,max_denomination_serial
,op_hash)
VALUES
(in_cs_rms
,denom_max
,in_rc)
ON CONFLICT DO NOTHING;
IF NOT FOUND
THEN
SELECT 1
FROM exchange.cs_nonce_locks
WHERE nonce=in_cs_rms
AND op_hash=in_rc;
IF NOT FOUND
THEN
out_balance_ok=FALSE;
out_zombie_bad=FALSE;
out_noreveal_index=42;
ASSERT false, 'nonce reuse attempted by client';
END IF;
END IF;
END IF;
out_balance_ok=TRUE;
out_noreveal_index=in_noreveal_index;
END $$;
CREATE OR REPLACE FUNCTION exchange_do_select_deposits_missing_wire(
IN in_min_serial_id INT8)
RETURNS SETOF exchange_do_select_deposits_missing_wire_return_type
LANGUAGE plpgsql
AS $$
DECLARE
missing CURSOR
FOR
SELECT
batch_deposit_serial_id
,wire_target_h_payto
,wire_deadline
FROM batch_deposits
WHERE batch_deposit_serial_id > in_min_serial_id
ORDER BY batch_deposit_serial_id ASC;
DECLARE
my_total_val INT8;
DECLARE
my_total_frac INT8;
DECLARE
my_total taler_amount;
DECLARE
my_batch_record RECORD;
DECLARE
i RECORD;
BEGIN
OPEN missing;
LOOP
FETCH NEXT FROM missing INTO i;
EXIT WHEN NOT FOUND;
SELECT
SUM((cdep.amount_with_fee).val) AS total_val
,SUM((cdep.amount_with_fee).frac::INT8) AS total_frac
INTO
my_batch_record
FROM coin_deposits cdep
WHERE cdep.batch_deposit_serial_id = i.batch_deposit_serial_id;
my_total_val=my_batch_record.total_val;
my_total_frac=my_batch_record.total_frac;
my_total.val = my_total_val + my_total_frac / 100000000;
my_total.frac = my_total_frac % 100000000;
RETURN NEXT (
i.batch_deposit_serial_id
,my_total
,i.wire_target_h_payto
,i.wire_deadline);
END LOOP;
CLOSE missing;
RETURN;
END $$;
CREATE OR REPLACE FUNCTION exchange_do_select_justification_missing_wire(
IN in_wire_target_h_payto BYTEA,
IN in_current_time INT8,
OUT out_payto_uri TEXT,
OUT out_kyc_pending TEXT,
OUT out_aml_status INT4,
OUT out_aml_limit taler_amount)
LANGUAGE plpgsql
AS $$
DECLARE
my_required_checks TEXT[];
DECLARE
my_aml_data RECORD;
DECLARE
satisfied CURSOR FOR
SELECT satisfied_checks
FROM kyc_attributes
WHERE h_payto=in_wire_target_h_payto
AND expiration_time < in_current_time;
DECLARE
i RECORD;
BEGIN
out_payto_uri = NULL;
SELECT payto_uri
INTO out_payto_uri
FROM wire_targets
WHERE wire_target_h_payto=my_wire_target_h_payto;
my_required_checks = NULL;
SELECT string_to_array (required_checks, ' ')
INTO my_required_checks
FROM legitimization_requirements
WHERE h_payto=my_wire_target_h_payto;
SELECT
new_threshold
,kyc_requirements
,new_status
INTO
my_aml_data
FROM aml_history
WHERE h_payto=in_wire_target_h_payto
ORDER BY aml_history_serial_id
DESC LIMIT 1;
IF FOUND
THEN
out_aml_limit=my_aml_data.new_threshold;
out_aml_status=my_aml_data.kyc_status;
my_required_checks
= array_cat (my_required_checks,
my_aml_data.kyc_requirements);
ELSE
out_aml_limit=NULL;
out_aml_status=0;
END IF;
OPEN satisfied;
LOOP
FETCH NEXT FROM satisfied INTO i;
EXIT WHEN NOT FOUND;
FOR i in 1..array_length(i.satisfied_checks)
LOOP
my_required_checks
= array_remove (my_required_checks,
i.satisfied_checks[i]);
END LOOP;
END LOOP;
IF ( (my_required_checks IS NOT NULL) AND
(0 < array_length(my_satisfied_checks)) )
THEN
out_kyc_pending
= array_to_string (my_required_checks, ' ');
END IF;
RETURN;
END $$;
CREATE OR REPLACE FUNCTION exchange_do_refund(
IN in_amount_with_fee taler_amount,
IN in_amount taler_amount,
IN in_deposit_fee taler_amount,
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
bdsi INT8;
DECLARE
tmp_val INT8;
DECLARE
tmp_frac INT8;
DECLARE
tmp taler_amount;
DECLARE
deposit taler_amount;
BEGIN
SELECT
bdep.batch_deposit_serial_id
,(cdep.amount_with_fee).val
,(cdep.amount_with_fee).frac
,bdep.done
INTO
bdsi
,deposit.val
,deposit.frac
,out_gone
FROM batch_deposits bdep
JOIN coin_deposits cdep
USING (batch_deposit_serial_id)
WHERE cdep.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
out_refund_ok=FALSE;
out_conflict=FALSE;
out_not_found=TRUE;
out_gone=FALSE;
RETURN;
END IF;
INSERT INTO refunds
(batch_deposit_serial_id
,coin_pub
,merchant_sig
,rtransaction_id
,amount_with_fee
)
VALUES
(bdsi
,in_coin_pub
,in_merchant_sig
,in_rtransaction_id
,in_amount_with_fee
)
ON CONFLICT DO NOTHING;
IF NOT FOUND
THEN
PERFORM
FROM exchange.refunds
WHERE coin_pub=in_coin_pub
AND batch_deposit_serial_id=bdsi
AND rtransaction_id=in_rtransaction_id
AND amount_with_fee=in_amount_with_fee;
IF NOT FOUND
THEN
out_refund_ok=FALSE;
out_conflict=TRUE;
out_not_found=FALSE;
RETURN;
END IF;
out_refund_ok=TRUE;
out_conflict=FALSE;
out_not_found=FALSE;
out_gone=FALSE;
RETURN;
END IF;
IF out_gone
THEN
out_refund_ok=FALSE;
out_conflict=FALSE;
out_not_found=FALSE;
RETURN;
END IF;
SELECT
SUM((refs.amount_with_fee).val)
,SUM(CAST((refs.amount_with_fee).frac AS INT8))
INTO
tmp_val
,tmp_frac
FROM refunds refs
WHERE coin_pub=in_coin_pub
AND batch_deposit_serial_id=bdsi;
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;
tmp.val = tmp_val + tmp_frac / 100000000;
tmp.frac = tmp_frac % 100000000;
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
in_amount.frac = in_amount.frac + in_deposit_fee.frac;
in_amount.val = in_amount.val + in_deposit_fee.val;
in_amount.val = in_amount.val + in_amount.frac / 100000000;
in_amount.frac = in_amount.frac % 100000000;
END IF;
UPDATE known_coins kc
SET
remaining.frac=(kc.remaining).frac+in_amount.frac
- CASE
WHEN (kc.remaining).frac+in_amount.frac >= 100000000
THEN 100000000
ELSE 0
END,
remaining.val=(kc.remaining).val+in_amount.val
+ CASE
WHEN (kc.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(taler_amount, taler_amount, taler_amount, BYTEA, INT8, INT8, INT8, BYTEA, BYTEA, BYTEA)
IS 'Executes a refund operation, checking that the corresponding deposit was sufficient to cover the refunded amount';
DROP FUNCTION IF EXISTS exchange_do_recoup_to_reserve;
CREATE FUNCTION exchange_do_recoup_to_reserve(
IN in_reserve_pub BYTEA,
IN in_withdraw_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 taler_amount;
balance taler_amount;
new_balance taler_amount;
reserve RECORD;
rval RECORD;
BEGIN
out_internal_failure=FALSE;
SELECT
remaining
INTO
rval
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;
tmp := rval.remaining;
IF tmp.val + tmp.frac = 0
THEN
SELECT
recoup_timestamp
INTO
out_recoup_timestamp
FROM exchange.recoup
WHERE coin_pub=in_coin_pub;
out_recoup_ok=FOUND;
RETURN;
END IF;
UPDATE known_coins
SET
remaining.val = 0
,remaining.frac = 0
WHERE coin_pub=in_coin_pub;
SELECT current_balance
INTO reserve
FROM reserves
WHERE reserve_pub=in_reserve_pub;
balance = reserve.current_balance;
new_balance.frac=balance.frac+tmp.frac
- CASE
WHEN balance.frac+tmp.frac >= 100000000
THEN 100000000
ELSE 0
END;
new_balance.val=balance.val+tmp.val
+ CASE
WHEN balance.frac+tmp.frac >= 100000000
THEN 1
ELSE 0
END;
UPDATE reserves
SET
current_balance = new_balance,
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
,recoup_timestamp
,withdraw_id
)
VALUES
(in_coin_pub
,in_coin_sig
,in_coin_blind
,tmp
,in_recoup_timestamp
,in_withdraw_id);
out_recoup_ok=TRUE;
out_recoup_timestamp=in_recoup_timestamp;
END $$;
DROP FUNCTION IF EXISTS exchange_do_recoup_to_coin;
CREATE FUNCTION exchange_do_recoup_to_coin(
IN in_old_coin_pub BYTEA,
IN in_refresh_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_recoup_timestamp INT8,
OUT out_recoup_ok BOOLEAN,
OUT out_internal_failure BOOLEAN,
OUT out_recoup_timestamp INT8)
LANGUAGE plpgsql
AS $$
DECLARE
rval RECORD;
DECLARE
tmp taler_amount;
BEGIN
out_internal_failure=FALSE;
SELECT
remaining
INTO
rval
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;
tmp := rval.remaining;
IF tmp.val + tmp.frac = 0
THEN
SELECT
recoup_timestamp
INTO
out_recoup_timestamp
FROM recoup_refresh
WHERE coin_pub=in_coin_pub;
out_recoup_ok=FOUND;
RETURN;
END IF;
UPDATE known_coins
SET
remaining.val = 0
,remaining.frac = 0
WHERE coin_pub=in_coin_pub;
UPDATE known_coins kc
SET
remaining.frac=(kc.remaining).frac+tmp.frac
- CASE
WHEN (kc.remaining).frac+tmp.frac >= 100000000
THEN 100000000
ELSE 0
END,
remaining.val=(kc.remaining).val+tmp.val
+ CASE
WHEN (kc.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 recoup_refresh
(coin_pub
,known_coin_id
,coin_sig
,coin_blind
,amount
,recoup_timestamp
,refresh_id
)
VALUES
(in_coin_pub
,in_known_coin_id
,in_coin_sig
,in_coin_blind
,tmp
,in_recoup_timestamp
,in_refresh_id);
out_recoup_ok=TRUE;
out_recoup_timestamp=in_recoup_timestamp;
END $$;
CREATE OR REPLACE PROCEDURE exchange_do_main_gc(
IN in_ancient_date INT8,
IN in_now INT8)
LANGUAGE plpgsql
AS $$
DECLARE
coin_min INT8;
batch_deposit_min INT8;
withdraw_min INT8;
denom_min INT8;
BEGIN
DELETE FROM prewire
WHERE finished=TRUE;
DELETE FROM wire_fee
WHERE end_date < in_ancient_date;
DELETE FROM refresh
WHERE execution_date < in_ancient_date;
DELETE FROM kycauths_in
WHERE execution_date < in_ancient_date;
DELETE FROM reserves_in
WHERE execution_date < in_ancient_date;
DELETE FROM batch_deposits
WHERE wire_deadline < in_ancient_date;
DELETE FROM withdraw
WHERE reserve_pub IN (
SELECT reserve_pub
FROM reserves
WHERE gc_date < in_now
AND current_balance = (0, 0));
DELETE FROM reserves_close
WHERE reserve_pub IN (
SELECT reserve_pub
FROM reserves
WHERE gc_date < in_now
AND current_balance = (0, 0));
SELECT withdraw_id
INTO withdraw_min
FROM withdraw
ORDER BY withdraw_id ASC
LIMIT 1;
DELETE FROM recoup
WHERE withdraw_id < withdraw_min;
DELETE FROM reserves
WHERE gc_date < in_now
AND current_balance = (0, 0);
DELETE FROM denominations
WHERE expire_legal < in_now
AND denominations_serial NOT IN
(SELECT DISTINCT UNNEST(denom_serials)
FROM withdraw)
AND denominations_serial NOT IN
(SELECT DISTINCT denominations_serial
FROM known_coins
WHERE coin_pub IN
(SELECT DISTINCT coin_pub
FROM recoup))
AND denominations_serial NOT IN
(SELECT DISTINCT denominations_serial
FROM known_coins
WHERE coin_pub IN
(SELECT DISTINCT coin_pub
FROM recoup_refresh));
DELETE FROM recoup_refresh
WHERE known_coin_id < coin_min;
SELECT known_coin_id
INTO coin_min
FROM known_coins
ORDER BY known_coin_id ASC
LIMIT 1;
SELECT batch_deposit_serial_id
INTO batch_deposit_min
FROM coin_deposits
ORDER BY batch_deposit_serial_id ASC
LIMIT 1;
DELETE FROM refunds
WHERE batch_deposit_serial_id < batch_deposit_min;
DELETE FROM aggregation_tracking
WHERE batch_deposit_serial_id < batch_deposit_min;
DELETE FROM coin_deposits
WHERE batch_deposit_serial_id < batch_deposit_min;
SELECT denominations_serial
INTO denom_min
FROM denominations
ORDER BY denominations_serial ASC
LIMIT 1;
DELETE FROM cs_nonce_locks
WHERE max_denomination_serial <= denom_min;
END $$;
CREATE OR REPLACE FUNCTION exchange_do_purse_delete(
IN in_purse_pub BYTEA,
IN in_purse_sig BYTEA,
IN in_now INT8,
OUT out_decided BOOLEAN,
OUT out_found BOOLEAN)
LANGUAGE plpgsql
AS $$
DECLARE
my_deposit record;
DECLARE
my_in_reserve_quota BOOLEAN;
BEGIN
PERFORM refunded FROM purse_decision
WHERE purse_pub=in_purse_pub;
IF FOUND
THEN
out_found=TRUE;
out_decided=TRUE;
RETURN;
END IF;
out_decided=FALSE;
SELECT in_reserve_quota
INTO my_in_reserve_quota
FROM exchange.purse_requests
WHERE purse_pub=in_purse_pub;
out_found=FOUND;
IF NOT FOUND
THEN
RETURN;
END IF;
INSERT INTO exchange.purse_deletion
(purse_pub
,purse_sig)
VALUES
(in_purse_pub
,in_purse_sig)
ON CONFLICT DO NOTHING;
IF NOT FOUND
THEN
RETURN;
END IF;
DELETE FROM contracts
WHERE purse_pub=in_purse_pub;
INSERT INTO purse_decision
(purse_pub
,action_timestamp
,refunded)
VALUES
(in_purse_pub
,in_now
,TRUE);
IF (my_in_reserve_quota)
THEN
UPDATE reserves
SET purses_active=purses_active-1
WHERE reserve_pub IN
(SELECT reserve_pub
FROM exchange.purse_merges
WHERE purse_pub=in_purse_pub
LIMIT 1);
END IF;
FOR my_deposit IN
SELECT coin_pub
,amount_with_fee
FROM exchange.purse_deposits
WHERE purse_pub = in_purse_pub
LOOP
UPDATE known_coins kc SET
remaining.frac=(kc.remaining).frac+(my_deposit.amount_with_fee).frac
- CASE
WHEN (kc.remaining).frac+(my_deposit.amount_with_fee).frac >= 100000000
THEN 100000000
ELSE 0
END,
remaining.val=(kc.remaining).val+(my_deposit.amount_with_fee).val
+ CASE
WHEN (kc.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_purse_delete(BYTEA,BYTEA,INT8)
IS 'Delete a previously undecided purse and refund the coins (if any).';
CREATE OR REPLACE FUNCTION exchange_do_purse_deposit(
IN in_partner_id INT8,
IN in_purse_pub BYTEA,
IN in_amount_with_fee taler_amount,
IN in_coin_pub BYTEA,
IN in_coin_sig BYTEA,
IN in_amount_without_fee taler_amount,
IN in_reserve_expiration INT8,
IN in_now INT8,
OUT out_balance_ok BOOLEAN,
OUT out_late BOOLEAN,
OUT out_conflict BOOLEAN)
LANGUAGE plpgsql
AS $$
DECLARE
was_merged BOOLEAN;
DECLARE
psi INT8;
DECLARE
my_amount taler_amount;
DECLARE
was_paid BOOLEAN;
DECLARE
my_in_reserve_quota BOOLEAN;
DECLARE
my_reserve_pub BYTEA;
DECLARE
rval RECORD;
BEGIN
INSERT INTO purse_deposits
(partner_serial_id
,purse_pub
,coin_pub
,amount_with_fee
,coin_sig)
VALUES
(in_partner_id
,in_purse_pub
,in_coin_pub
,in_amount_with_fee
,in_coin_sig)
ON CONFLICT DO NOTHING;
IF NOT FOUND
THEN
PERFORM
FROM purse_deposits
WHERE purse_pub = in_purse_pub
AND coin_pub = in_coin_pub
AND coin_sig = in_coin_sig;
IF NOT FOUND
THEN
out_balance_ok=FALSE;
out_late=FALSE;
out_conflict=TRUE;
RETURN;
ELSE
out_late=FALSE;
out_balance_ok=TRUE;
out_conflict=FALSE;
RETURN;
END IF;
END IF;
PERFORM
FROM exchange.purse_deletion
WHERE purse_pub = in_purse_pub;
IF FOUND
THEN
out_late=TRUE;
out_balance_ok=FALSE;
out_conflict=FALSE;
RETURN;
END IF;
UPDATE known_coins kc
SET
remaining.frac=(kc.remaining).frac-in_amount_with_fee.frac
+ CASE
WHEN (kc.remaining).frac < in_amount_with_fee.frac
THEN 100000000
ELSE 0
END,
remaining.val=(kc.remaining).val-in_amount_with_fee.val
- CASE
WHEN (kc.remaining).frac < in_amount_with_fee.frac
THEN 1
ELSE 0
END
WHERE coin_pub=in_coin_pub
AND ( ((kc.remaining).val > in_amount_with_fee.val) OR
( ((kc.remaining).frac >= in_amount_with_fee.frac) AND
((kc.remaining).val >= in_amount_with_fee.val) ) );
IF NOT FOUND
THEN
out_balance_ok=FALSE;
out_late=FALSE;
out_conflict=FALSE;
RETURN;
END IF;
UPDATE purse_requests pr
SET
balance.frac=(pr.balance).frac+in_amount_without_fee.frac
- CASE
WHEN (pr.balance).frac+in_amount_without_fee.frac >= 100000000
THEN 100000000
ELSE 0
END,
balance.val=(pr.balance).val+in_amount_without_fee.val
+ CASE
WHEN (pr.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;
SELECT COALESCE(partner_serial_id,0)
,reserve_pub
INTO psi
,my_reserve_pub
FROM purse_merges
WHERE purse_pub=in_purse_pub;
IF NOT FOUND
THEN
out_late=FALSE;
RETURN;
END IF;
SELECT
amount_with_fee
,in_reserve_quota
INTO
rval
FROM exchange.purse_requests preq
WHERE (purse_pub=in_purse_pub)
AND ( ( ( ((preq.amount_with_fee).val <= (preq.balance).val)
AND ((preq.amount_with_fee).frac <= (preq.balance).frac) )
OR ((preq.amount_with_fee).val < (preq.balance).val) ) );
IF NOT FOUND
THEN
out_late=FALSE;
RETURN;
END IF;
my_amount := rval.amount_with_fee;
my_in_reserve_quota := rval.in_reserve_quota;
INSERT INTO purse_decision
(purse_pub
,action_timestamp
,refunded)
VALUES
(in_purse_pub
,in_now
,FALSE)
ON CONFLICT DO NOTHING;
IF NOT FOUND
THEN
out_late=TRUE;
RETURN;
END IF;
out_late=FALSE;
IF (my_in_reserve_quota)
THEN
UPDATE reserves
SET purses_active=purses_active-1
WHERE reserve_pub IN
(SELECT reserve_pub
FROM purse_merges
WHERE purse_pub=my_purse_pub
LIMIT 1);
END IF;
IF (0 != psi)
THEN
UPDATE purse_actions
SET action_date=0
,partner_serial_id=psi
WHERE purse_pub=in_purse_pub;
ELSE
INSERT INTO reserves
(reserve_pub
,current_balance
,expiration_date
,gc_date)
VALUES
(my_reserve_pub
,my_amount
,in_reserve_expiration
,in_reserve_expiration)
ON CONFLICT DO NOTHING;
IF NOT FOUND
THEN
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
,expiration_date=GREATEST(expiration_date,in_reserve_expiration)
,gc_date=GREATEST(gc_date,in_reserve_expiration)
WHERE reserve_pub=my_reserve_pub;
END IF;
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 TEXT,
IN in_reserve_pub BYTEA,
IN in_wallet_h_payto BYTEA,
IN in_expiration_date INT8,
OUT out_no_partner BOOLEAN,
OUT out_no_balance BOOLEAN,
OUT out_conflict BOOLEAN)
LANGUAGE plpgsql
AS $$
DECLARE
my_amount taler_amount;
DECLARE
my_purse_fee taler_amount;
DECLARE
my_partner_serial_id INT8;
DECLARE
my_in_reserve_quota BOOLEAN;
DECLARE
rval RECORD;
DECLARE
reserve_bal RECORD;
DECLARE
balance taler_amount;
BEGIN
INSERT INTO reserves
(reserve_pub
,expiration_date
,gc_date)
VALUES
(in_reserve_pub
,in_expiration_date
,in_expiration_date)
ON CONFLICT DO NOTHING;
IF in_partner_url IS NULL
THEN
my_partner_serial_id=NULL;
ELSE
SELECT
partner_serial_id
INTO
my_partner_serial_id
FROM 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;
RETURN;
END IF;
END IF;
out_no_partner=FALSE;
SELECT amount_with_fee
,purse_fee
,in_reserve_quota
INTO rval
FROM purse_requests pr
WHERE purse_pub=in_purse_pub
AND (pr.balance).val >= (pr.amount_with_fee).val
AND ( (pr.balance).frac >= (pr.amount_with_fee).frac OR
(pr.balance).val > (pr.amount_with_fee).val );
IF NOT FOUND
THEN
out_no_balance=TRUE;
out_conflict=FALSE;
RETURN;
END IF;
my_amount := rval.amount_with_fee;
my_purse_fee := rval.purse_fee;
my_in_reserve_quota := rval.in_reserve_quota;
out_no_balance=FALSE;
INSERT INTO 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
PERFORM
FROM purse_merges
WHERE purse_pub=in_purse_pub
AND merge_sig=in_merge_sig;
IF NOT FOUND
THEN
out_conflict=TRUE;
RETURN;
END IF;
out_conflict=FALSE;
RETURN;
END IF;
INSERT INTO purse_decision
(purse_pub
,action_timestamp
,refunded)
VALUES
(in_purse_pub
,in_merge_timestamp
,FALSE)
ON CONFLICT DO NOTHING;
IF NOT FOUND
THEN
out_conflict=TRUE;
RETURN;
END IF;
out_conflict=FALSE;
IF (my_in_reserve_quota)
THEN
UPDATE reserves
SET purses_active=purses_active-1
WHERE reserve_pub IN
(SELECT reserve_pub
FROM purse_merges
WHERE purse_pub=my_purse_pub
LIMIT 1);
END IF;
INSERT INTO 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 (0 != my_partner_serial_id)
THEN
UPDATE purse_actions
SET action_date=0
,partner_serial_id=my_partner_serial_id
WHERE purse_pub=in_purse_pub;
ELSE
my_amount.val = my_amount.val + my_purse_fee.val;
my_amount.frac = my_amount.frac + my_purse_fee.frac;
my_amount.val = my_amount.val + my_amount.frac / 100000000;
my_amount.frac = my_amount.frac % 100000000;
SELECT current_balance
INTO reserve_bal
FROM reserves
WHERE reserve_pub=in_reserve_pub;
balance = reserve_bal.current_balance;
balance.val=balance.val+my_amount.val
+ CASE
WHEN balance.frac + my_amount.frac >= 100000000
THEN 1
ELSE 0
END;
balance.frac=balance.frac+my_amount.frac
- CASE
WHEN balance.frac + my_amount.frac >= 100000000
THEN 100000000
ELSE 0
END;
UPDATE reserves
SET current_balance=balance
WHERE reserve_pub=in_reserve_pub;
END IF;
RETURN;
END $$;
COMMENT ON FUNCTION exchange_do_purse_merge(BYTEA, BYTEA, INT8, BYTEA, TEXT, BYTEA, BYTEA, INT8)
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_expiration INT8,
IN in_reserve_gc INT8,
IN in_reserve_sig BYTEA,
IN in_reserve_quota BOOLEAN,
IN in_purse_fee taler_amount,
IN in_reserve_pub BYTEA,
IN in_wallet_h_payto BYTEA,
OUT out_no_funds BOOLEAN,
OUT out_no_reserve BOOLEAN,
OUT out_conflict BOOLEAN)
LANGUAGE plpgsql
AS $$
BEGIN
INSERT INTO purse_merges
(partner_serial_id
,reserve_pub
,purse_pub
,merge_sig
,merge_timestamp)
VALUES
(NULL
,in_reserve_pub
,in_purse_pub
,in_merge_sig
,in_merge_timestamp)
ON CONFLICT DO NOTHING;
IF NOT FOUND
THEN
PERFORM
FROM purse_merges
WHERE purse_pub=in_purse_pub
AND merge_sig=in_merge_sig;
IF NOT FOUND
THEN
out_conflict=TRUE;
out_no_reserve=FALSE;
out_no_funds=FALSE;
RETURN;
END IF;
out_conflict=FALSE;
out_no_funds=FALSE;
out_no_reserve=FALSE;
RETURN;
END IF;
out_conflict=FALSE;
PERFORM
FROM exchange.reserves
WHERE reserve_pub=in_reserve_pub;
out_no_reserve = NOT FOUND;
IF (in_reserve_quota)
THEN
IF (out_no_reserve)
THEN
out_no_funds=TRUE;
RETURN;
END IF;
UPDATE exchange.reserves
SET purses_active=purses_active+1
WHERE reserve_pub=in_reserve_pub
AND purses_active < purses_allowed;
IF NOT FOUND
THEN
out_no_funds=TRUE;
RETURN;
END IF;
ELSE
IF (out_no_reserve)
THEN
IF ( (0 != in_purse_fee.val) OR
(0 != in_purse_fee.frac) )
THEN
out_no_funds=TRUE;
RETURN;
END IF;
INSERT INTO exchange.reserves
(reserve_pub
,expiration_date
,gc_date)
VALUES
(in_reserve_pub
,in_reserve_expiration
,in_reserve_gc);
ELSE
UPDATE exchange.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
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;
END IF;
out_no_funds=FALSE;
INSERT INTO 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, INT8, INT8, BYTEA, BOOLEAN, taler_amount, BYTEA, BYTEA)
IS 'Create a purse for a reserve.';
CREATE OR REPLACE FUNCTION exchange_do_expire_purse(
IN in_start_time INT8,
IN in_end_time INT8,
IN in_now INT8,
OUT out_found BOOLEAN)
LANGUAGE plpgsql
AS $$
DECLARE
my_purse_pub BYTEA;
DECLARE
my_deposit record;
DECLARE
my_in_reserve_quota BOOLEAN;
BEGIN
SELECT purse_pub
,in_reserve_quota
INTO my_purse_pub
,my_in_reserve_quota
FROM purse_requests
WHERE (purse_expiration >= in_start_time) AND
(purse_expiration < in_end_time) AND
NOT was_decided
ORDER BY purse_expiration ASC
LIMIT 1;
out_found = FOUND;
IF NOT FOUND
THEN
RETURN;
END IF;
INSERT INTO purse_decision
(purse_pub
,action_timestamp
,refunded)
VALUES
(my_purse_pub
,in_now
,TRUE);
NOTIFY X8DJSPNYJMNZDAP7GN6YQ4EZVSQXMF3HRP4VAR347WP9SZYP1C200;
IF (my_in_reserve_quota)
THEN
UPDATE reserves
SET purses_active=purses_active-1
WHERE reserve_pub IN
(SELECT reserve_pub
FROM exchange.purse_merges
WHERE purse_pub=my_purse_pub
LIMIT 1);
END IF;
FOR my_deposit IN
SELECT coin_pub
,amount_with_fee
FROM purse_deposits
WHERE purse_pub = my_purse_pub
LOOP
UPDATE known_coins kc SET
remaining.frac=(kc.remaining).frac+(my_deposit.amount_with_fee).frac
- CASE
WHEN (kc.remaining).frac+(my_deposit.amount_with_fee).frac >= 100000000
THEN 100000000
ELSE 0
END,
remaining.val=(kc.remaining).val+(my_deposit.amount_with_fee).val
+ CASE
WHEN (kc.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,INT8)
IS 'Finds an expired purse in the given time range and refunds the coins (if any).';
CREATE OR REPLACE FUNCTION exchange_do_reserve_open_deposit(
IN in_coin_pub BYTEA,
IN in_known_coin_id INT8,
IN in_coin_sig BYTEA,
IN in_reserve_sig BYTEA,
IN in_reserve_pub BYTEA,
IN in_coin_total taler_amount,
OUT out_insufficient_funds BOOLEAN)
LANGUAGE plpgsql
AS $$
BEGIN
INSERT INTO exchange.reserves_open_deposits
(reserve_sig
,reserve_pub
,coin_pub
,coin_sig
,contribution
)
VALUES
(in_reserve_sig
,in_reserve_pub
,in_coin_pub
,in_coin_sig
,in_coin_total
)
ON CONFLICT DO NOTHING;
IF NOT FOUND
THEN
out_insufficient_funds=FALSE;
RETURN;
END IF;
UPDATE exchange.known_coins kc
SET
remaining.frac=(kc.remaining).frac-in_coin_total.frac
+ CASE
WHEN (kc.remaining).frac < in_coin_total.frac
THEN 100000000
ELSE 0
END,
remaining.val=(kc.remaining).val-in_coin_total.val
- CASE
WHEN (kc.remaining).frac < in_coin_total.frac
THEN 1
ELSE 0
END
WHERE coin_pub=in_coin_pub
AND ( ((kc.remaining).val > in_coin_total.val) OR
( ((kc.remaining).frac >= in_coin_total.frac) AND
((kc.remaining).val >= in_coin_total.val) ) );
IF NOT FOUND
THEN
out_insufficient_funds=TRUE;
RETURN;
END IF;
out_insufficient_funds=FALSE;
END $$;
CREATE OR REPLACE FUNCTION exchange_do_reserve_open(
IN in_reserve_pub BYTEA,
IN in_total_paid taler_amount,
IN in_reserve_payment taler_amount,
IN in_min_purse_limit INT4,
IN in_default_purse_limit INT4,
IN in_reserve_sig BYTEA,
IN in_desired_expiration INT8,
IN in_reserve_gc_delay INT8,
IN in_now INT8,
IN in_open_fee taler_amount,
OUT out_open_cost taler_amount,
OUT out_final_expiration INT8,
OUT out_no_reserve BOOLEAN,
OUT out_no_funds BOOLEAN,
OUT out_reserve_balance taler_amount)
LANGUAGE plpgsql
AS $$
DECLARE
my_balance taler_amount;
my_cost taler_amount;
my_cost_tmp INT8;
my_years_tmp INT4;
my_years INT4;
my_needs_update BOOL;
my_expiration_date INT8;
reserve RECORD;
BEGIN
SELECT current_balance
,expiration_date
,purses_allowed
INTO reserve
FROM reserves
WHERE reserve_pub=in_reserve_pub;
IF NOT FOUND
THEN
RAISE NOTICE 'reserve not found';
out_no_reserve = TRUE;
out_no_funds = TRUE;
out_reserve_balance.val = 0;
out_reserve_balance.frac = 0;
out_open_cost.val = 0;
out_open_cost.frac = 0;
out_final_expiration = 0;
RETURN;
END IF;
out_no_reserve = FALSE;
out_reserve_balance = reserve.current_balance;
IF (reserve.expiration_date < in_now)
THEN
my_expiration_date = in_now;
ELSE
my_expiration_date = reserve.expiration_date;
END IF;
my_cost.val = 0;
my_cost.frac = 0;
my_needs_update = FALSE;
my_years = 0;
IF (my_expiration_date < in_desired_expiration)
THEN
my_years = (31535999999999 + in_desired_expiration - my_expiration_date) / 31536000000000;
reserve.purses_allowed = in_default_purse_limit;
my_expiration_date = my_expiration_date + 31536000000000 * my_years;
END IF;
IF (reserve.purses_allowed < in_min_purse_limit)
THEN
my_years = (31535999999999 + in_desired_expiration - in_now) / 31536000000000;
my_expiration_date = in_now + 31536000000000 * my_years;
my_years_tmp = (in_min_purse_limit + in_default_purse_limit - reserve.purses_allowed - 1) / in_default_purse_limit;
my_years = my_years + my_years_tmp;
reserve.purses_allowed = reserve.purses_allowed + (in_default_purse_limit * my_years_tmp);
END IF;
IF (my_years > 0)
THEN
my_cost.val = my_years * in_open_fee.val;
my_cost_tmp = my_years * in_open_fee.frac / 100000000;
IF (CAST (my_cost.val + my_cost_tmp AS INT8) < my_cost.val)
THEN
out_open_cost.val=9223372036854775807;
out_open_cost.frac=2147483647;
out_final_expiration=my_expiration_date;
out_no_funds=FALSE;
RAISE NOTICE 'arithmetic issue computing amount';
RETURN;
END IF;
my_cost.val = CAST (my_cost.val + my_cost_tmp AS INT8);
my_cost.frac = my_years * in_open_fee.frac % 100000000;
my_needs_update = TRUE;
END IF;
IF NOT my_needs_update
THEN
out_final_expiration = reserve.expiration_date;
out_open_cost.val = 0;
out_open_cost.frac = 0;
out_no_funds=FALSE;
RAISE NOTICE 'no change required';
RETURN;
END IF;
IF ( (in_total_paid.val < my_cost.val) OR
( (in_total_paid.val = my_cost.val) AND
(in_total_paid.frac < my_cost.frac) ) )
THEN
out_open_cost.val = my_cost.val;
out_open_cost.frac = my_cost.frac;
out_no_funds=FALSE;
IF (reserve.expiration_date >= in_desired_expiration)
THEN
RAISE NOTICE 'forcing low expiration time';
out_final_expiration = 0;
ELSE
out_final_expiration = reserve.expiration_date;
END IF;
RAISE NOTICE 'amount paid too low';
RETURN;
END IF;
IF (out_reserve_balance.val > in_reserve_payment.val)
THEN
IF (out_reserve_balance.frac >= in_reserve_payment.frac)
THEN
my_balance.val=out_reserve_balance.val - in_reserve_payment.val;
my_balance.frac=out_reserve_balance.frac - in_reserve_payment.frac;
ELSE
my_balance.val=out_reserve_balance.val - in_reserve_payment.val - 1;
my_balance.frac=out_reserve_balance.frac + 100000000 - in_reserve_payment.frac;
END IF;
ELSE
IF (out_reserve_balance.val = in_reserve_payment.val) AND (out_reserve_balance.frac >= in_reserve_payment.frac)
THEN
my_balance.val=0;
my_balance.frac=out_reserve_balance.frac - in_reserve_payment.frac;
ELSE
out_final_expiration = reserve.expiration_date;
out_open_cost.val = my_cost.val;
out_open_cost.frac = my_cost.frac;
out_no_funds=TRUE;
RAISE NOTICE 'reserve balance too low';
RETURN;
END IF;
END IF;
UPDATE reserves SET
current_balance=my_balance
,gc_date=reserve.expiration_date + in_reserve_gc_delay
,expiration_date=my_expiration_date
,purses_allowed=reserve.purses_allowed
WHERE
reserve_pub=in_reserve_pub;
out_final_expiration=my_expiration_date;
out_open_cost = my_cost;
out_no_funds=FALSE;
RETURN;
END $$;
CREATE OR REPLACE FUNCTION exchange_do_insert_or_update_policy_details(
IN in_policy_hash_code BYTEA,
IN in_policy_json TEXT,
IN in_deadline INT8,
IN in_commitment taler_amount,
IN in_accumulated_total taler_amount,
IN in_fee taler_amount,
IN in_transferable taler_amount,
IN in_fulfillment_state SMALLINT,
OUT out_policy_details_serial_id INT8,
OUT out_accumulated_total taler_amount,
OUT out_fulfillment_state SMALLINT)
LANGUAGE plpgsql
AS $$
DECLARE
cur_commitment taler_amount;
DECLARE
cur_accumulated_total taler_amount;
DECLARE
rval RECORD;
BEGIN
INSERT INTO policy_details
(policy_hash_code,
policy_json,
deadline,
commitment,
accumulated_total,
fee,
transferable,
fulfillment_state)
VALUES (in_policy_hash_code,
in_policy_json,
in_deadline,
in_commitment,
in_accumulated_total,
in_fee,
in_transferable,
in_fulfillment_state)
ON CONFLICT (policy_hash_code) DO NOTHING
RETURNING policy_details_serial_id INTO out_policy_details_serial_id;
IF FOUND THEN
out_accumulated_total = in_accumulated_total;
out_fulfillment_state = in_fulfillment_state;
RETURN;
END IF;
SELECT policy_details_serial_id
,commitment
,accumulated_total
INTO rval
FROM policy_details
WHERE policy_hash_code = in_policy_hash_code;
out_policy_details_serial_id := rval.policy_details_serial_id;
cur_commitment := rval.commitment;
cur_accumulated_total := rval.accumulated_total;
out_accumulated_total.val = cur_accumulated_total.val + in_accumulated_total.val;
out_accumulated_total.frac = cur_accumulated_total.frac + in_accumulated_total.frac;
out_accumulated_total.val = out_accumulated_total.val + out_accumulated_total.frac / 100000000;
out_accumulated_total.frac = out_accumulated_total.frac % 100000000;
IF (out_accumulated_total.val > (1 << 52))
THEN
RAISE EXCEPTION 'accumulation overflow';
END IF;
IF (out_fullfillment_state = 2)
THEN
IF (out_accumulated_total.val >= cur_commitment.val OR
(out_accumulated_total.val = cur_commitment.val AND
out_accumulated_total.frac >= cur_commitment.frac))
THEN
out_fulfillment_state = 3;
END IF;
END IF;
UPDATE exchange.policy_details
SET
accumulated = out_accumulated_total,
fulfillment_state = out_fulfillment_state
WHERE
policy_details_serial_id = out_policy_details_serial_id;
END $$;
DROP FUNCTION IF EXISTS exchange_do_insert_aml_decision;
CREATE FUNCTION exchange_do_insert_aml_decision(
IN in_payto_uri TEXT,
IN in_h_normalized_payto BYTEA,
IN in_h_full_payto BYTEA,
IN in_decision_time INT8,
IN in_expiration_time INT8,
IN in_properties JSONB,
IN in_kyc_attributes_enc BYTEA,
IN in_kyc_attributes_hash BYTEA,
IN in_kyc_attributes_expiration INT8,
IN in_new_rules JSONB,
IN in_to_investigate BOOLEAN,
IN in_new_measure_name TEXT,
IN in_jmeasures JSONB,
IN in_justification TEXT,
IN in_decider_pub BYTEA,
IN in_decider_sig BYTEA,
IN in_notify_s TEXT,
IN ina_events TEXT[],
IN in_form_name TEXT,
OUT out_invalid_officer BOOLEAN,
OUT out_account_unknown BOOLEAN,
OUT out_last_date INT8,
OUT out_legitimization_measure_serial_id INT8,
OUT out_is_wallet BOOL)
LANGUAGE plpgsql
AS $$
DECLARE
my_outcome_serial_id INT8;
my_legitimization_process_serial_id INT8;
my_kyc_attributes_serial_id INT8;
my_rec RECORD;
my_access_token BYTEA;
my_i INT4;
ini_event TEXT;
BEGIN
out_account_unknown=FALSE;
out_legitimization_measure_serial_id=0;
IF in_decider_pub IS NOT NULL
THEN
IF in_justification IS NULL OR in_decider_sig IS NULL
THEN
RAISE EXCEPTION 'Got in_decider_sig without justification or signature.';
END IF;
PERFORM
FROM aml_staff
WHERE decider_pub=in_decider_pub
AND is_active
AND NOT read_only;
IF NOT FOUND
THEN
out_invalid_officer=TRUE;
out_last_date=0;
RETURN;
END IF;
END IF;
out_invalid_officer=FALSE;
SELECT decision_time
INTO out_last_date
FROM legitimization_outcomes
WHERE h_payto=in_h_normalized_payto
AND is_active
ORDER BY decision_time DESC, outcome_serial_id DESC;
IF FOUND
THEN
IF in_decider_pub IS NOT NULL AND out_last_date > in_decision_time
THEN
RETURN;
END IF;
UPDATE legitimization_outcomes
SET is_active=FALSE
WHERE h_payto=in_h_normalized_payto
AND is_active;
ELSE
out_last_date = 0;
END IF;
SELECT access_token
,is_wallet
INTO my_rec
FROM kyc_targets
WHERE h_normalized_payto=in_h_normalized_payto;
IF NOT FOUND
THEN
IF in_payto_uri IS NULL
THEN
out_account_unknown=TRUE;
RETURN;
END IF;
out_is_wallet
= (LOWER (SUBSTRING (in_payto_uri, 22)) =
'payto://taler-reserve/') OR
(LOWER (SUBSTRING (in_payto_uri, 27)) =
'payto://taler-reserve-http/');
INSERT INTO kyc_targets
(h_normalized_payto
,is_wallet
) VALUES (
in_h_normalized_payto
,out_is_wallet
)
RETURNING access_token
INTO my_access_token;
INSERT INTO wire_targets
(wire_target_h_payto
,h_normalized_payto
,payto_uri
) VALUES (
in_h_full_payto
,in_h_normalized_payto
,in_payto_uri
)
ON CONFLICT DO NOTHING;
ELSE
my_access_token = my_rec.access_token;
out_is_wallet = my_rec.is_wallet;
END IF;
IF in_jmeasures IS NOT NULL
THEN
SELECT legitimization_measure_serial_id
INTO out_legitimization_measure_serial_id
FROM legitimization_measures
WHERE access_token=my_access_token
AND jmeasures=in_jmeasures
AND NOT is_finished;
IF NOT FOUND
THEN
INSERT INTO legitimization_measures
(access_token
,start_time
,jmeasures
,display_priority
) VALUES (
my_access_token
,in_decision_time
,in_jmeasures
,1)
RETURNING
legitimization_measure_serial_id
INTO
out_legitimization_measure_serial_id;
END IF;
END IF;
RAISE NOTICE 'marking legi measures of % as finished except for %', my_access_token, out_legitimization_measure_serial_id;
UPDATE legitimization_measures
SET is_finished=TRUE
WHERE access_token=my_access_token
AND NOT is_finished
AND legitimization_measure_serial_id != out_legitimization_measure_serial_id;
UPDATE legitimization_outcomes
SET is_active=FALSE
WHERE h_payto=in_h_normalized_payto
AND expiration_time >= in_decision_time;
INSERT INTO legitimization_outcomes
(h_payto
,decision_time
,expiration_time
,jproperties
,new_measure_name
,to_investigate
,jnew_rules
) VALUES (
in_h_normalized_payto
,in_decision_time
,in_expiration_time
,in_properties
,in_new_measure_name
,in_to_investigate
,in_new_rules
)
RETURNING outcome_serial_id
INTO my_outcome_serial_id;
IF in_kyc_attributes_enc IS NOT NULL
THEN
IF in_kyc_attributes_hash IS NULL OR in_kyc_attributes_expiration IS NULL
THEN
RAISE EXCEPTION 'Got in_kyc_attributes_hash without hash or expiration.';
END IF;
IF in_decider_pub IS NULL
THEN
RAISE EXCEPTION 'Got in_kyc_attributes_hash without in_decider_pub.';
END IF;
INSERT INTO legitimization_processes
(h_payto
,start_time
,expiration_time
,provider_name
,provider_user_id
,finished
) VALUES (
in_h_normalized_payto
,in_decision_time
,in_decision_time
,'aml-officer'
,in_decider_pub
,TRUE
)
RETURNING legitimization_process_serial_id
INTO my_legitimization_process_serial_id;
INSERT INTO kyc_attributes
(h_payto
,collection_time
,expiration_time
,form_name
,by_aml_officer
,encrypted_attributes
,legitimization_serial
) VALUES (
in_h_normalized_payto
,in_decision_time
,in_kyc_attributes_expiration
,in_form_name
,TRUE
,in_kyc_attributes_enc
,my_legitimization_process_serial_id
)
RETURNING kyc_attributes_serial_id
INTO my_kyc_attributes_serial_id;
NOTIFY XSX9Z5XGWWYFKXTAYCES63B62527JKNX9XD0131Z08THVV8YW5BZG;
END IF;
IF in_decider_pub IS NOT NULL
THEN
INSERT INTO aml_history
(h_payto
,outcome_serial_id
,justification
,decider_pub
,decider_sig
,kyc_attributes_hash
,kyc_attributes_serial_id
) VALUES (
in_h_normalized_payto
,my_outcome_serial_id
,in_justification
,in_decider_pub
,in_decider_sig
,in_kyc_attributes_hash
,my_kyc_attributes_serial_id
);
END IF;
FOR i IN 1..COALESCE(array_length(ina_events,1),0)
LOOP
ini_event = ina_events[i];
INSERT INTO kyc_events
(event_timestamp
,event_type
) VALUES (
in_decision_time
,ini_event);
IF (ini-event = 'ACCOUNT_OPEN')
THEN
UPDATE kyc_targets
SET open_time=in_decision_time
,close_time=NULL
WHERE h_normalized_payto=in_h_normalized_payto;
END IF;
IF (ini-event = 'ACCOUNT_IDLE')
THEN
UPDATE kyc_targets
SET close_time=in_decision_time
WHERE h_normalized_payto=in_h_normalized_payto;
END IF;
END LOOP;
INSERT INTO kyc_alerts
(h_payto
,trigger_type
) VALUES (
in_h_normalized_payto
,1
)
ON CONFLICT DO NOTHING;
EXECUTE FORMAT (
'NOTIFY %s'
,in_notify_s);
END $$;
COMMENT ON FUNCTION exchange_do_insert_aml_decision(TEXT, BYTEA, BYTEA, INT8, INT8, JSONB, BYTEA, BYTEA, INT8, JSONB, BOOLEAN, TEXT, JSONB, TEXT, BYTEA, BYTEA, TEXT, TEXT[], TEXT)
IS 'Checks whether the AML officer is eligible to make AML decisions and if so inserts the decision into the table';
DROP FUNCTION IF EXISTS exchange_do_insert_successor_measure;
CREATE FUNCTION exchange_do_insert_successor_measure(
IN in_h_normalized_payto BYTEA,
IN in_decision_time INT8,
IN in_expiration_time INT8,
IN in_new_measure_name TEXT,
IN in_jmeasures JSONB,
OUT out_last_date INT8,
OUT out_account_unknown BOOLEAN,
OUT out_legitimization_measure_serial_id INT8
)
LANGUAGE plpgsql
AS $$
DECLARE
my_outcome_serial_id INT8;
my_access_token BYTEA;
my_is_wallet BOOL;
BEGIN
out_account_unknown=FALSE;
out_legitimization_measure_serial_id=0;
SELECT decision_time
INTO out_last_date
FROM legitimization_outcomes
WHERE h_payto=in_h_normalized_payto
AND is_active
ORDER BY decision_time DESC, outcome_serial_id DESC;
IF FOUND
THEN
IF out_last_date > in_decision_time
THEN
RETURN;
END IF;
UPDATE legitimization_outcomes
SET is_active=FALSE
WHERE h_payto=in_h_normalized_payto
AND is_active;
ELSE
out_last_date = 0;
END IF;
SELECT access_token
INTO my_access_token
FROM kyc_targets
WHERE h_normalized_payto=in_h_normalized_payto;
IF NOT FOUND
THEN
IF in_payto_uri IS NULL
THEN
out_account_unknown=TRUE;
RETURN;
END IF;
my_is_wallet
= (LOWER (SUBSTRING (in_payto_uri, 22)) =
'payto://taler-reserve/') OR
(LOWER (SUBSTRING (in_payto_uri, 27)) =
'payto://taler-reserve-http/');
INSERT INTO kyc_targets
(h_normalized_payto
,is_wallet)
VALUES
(in_h_normalized_payto
,my_is_wallet)
RETURNING access_token
INTO my_access_token;
INSERT INTO wire_targets
(wire_target_h_payto
,h_normalized_payto
,payto_uri)
VALUES
(in_h_full_payto
,in_h_normalized_payto
,in_payto_uri)
ON CONFLICT DO NOTHING;
END IF;
SELECT legitimization_measure_serial_id
INTO out_legitimization_measure_serial_id
FROM legitimization_measures
WHERE access_token=my_access_token
AND jmeasures=in_jmeasures
AND NOT is_finished;
IF NOT FOUND
THEN
INSERT INTO legitimization_measures
(access_token
,start_time
,jmeasures
,display_priority)
VALUES
(my_access_token
,in_decision_time
,in_jmeasures
,1)
RETURNING
legitimization_measure_serial_id
INTO
out_legitimization_measure_serial_id;
END IF;
UPDATE legitimization_measures
SET is_finished=TRUE
WHERE access_token=my_access_token
AND NOT is_finished
AND legitimization_measure_serial_id != out_legitimization_measure_serial_id;
UPDATE legitimization_outcomes
SET is_active=FALSE
WHERE h_payto=in_h_normalized_payto
AND expiration_time >= in_decision_time;
INSERT INTO legitimization_outcomes
(h_payto
,decision_time
,expiration_time
,jproperties
,new_measure_name
,to_investigate
,jnew_rules
)
VALUES
(in_h_normalized_payto
,in_decision_time
,in_expiration_time
,'{}'::JSONB
,in_new_measure_name
,FALSE
,NULL
)
RETURNING
outcome_serial_id
INTO
my_outcome_serial_id;
END $$;
COMMENT ON FUNCTION exchange_do_insert_successor_measure(BYTEA, INT8, INT8, TEXT, JSONB)
IS 'Checks whether the AML officer is eligible to make AML decisions and if so inserts the decision into the table';
CREATE OR REPLACE FUNCTION exchange_do_insert_aml_officer(
IN in_decider_pub BYTEA,
IN in_master_sig BYTEA,
IN in_decider_name TEXT,
IN in_is_active BOOLEAN,
IN in_read_only BOOLEAN,
IN in_last_change INT8,
OUT out_last_change INT8)
LANGUAGE plpgsql
AS $$
BEGIN
INSERT INTO exchange.aml_staff
(decider_pub
,master_sig
,decider_name
,is_active
,read_only
,last_change
) VALUES
(in_decider_pub
,in_master_sig
,in_decider_name
,in_is_active
,in_read_only
,in_last_change)
ON CONFLICT DO NOTHING;
IF FOUND
THEN
out_last_change=0;
RETURN;
END IF;
SELECT last_change
INTO out_last_change
FROM exchange.aml_staff
WHERE decider_pub=in_decider_pub;
ASSERT FOUND, 'cannot have INSERT conflict but no AML staff record';
IF out_last_change >= in_last_change
THEN
RETURN;
END IF;
UPDATE exchange.aml_staff
SET master_sig=in_master_sig
,decider_name=in_decider_name
,is_active=in_is_active
,read_only=in_read_only
,last_change=in_last_change
WHERE decider_pub=in_decider_pub;
END $$;
COMMENT ON FUNCTION exchange_do_insert_aml_officer(BYTEA, BYTEA, TEXT, BOOL, BOOL, INT8)
IS 'Inserts or updates AML staff record, making sure the update is more recent than the previous change';
DROP FUNCTION IF EXISTS exchange_do_array_reserves_insert;
CREATE FUNCTION exchange_do_array_reserves_insert(
IN in_gc_date INT8,
IN in_reserve_expiration INT8,
IN ina_reserve_pub BYTEA[],
IN ina_wire_ref INT8[],
IN ina_credit taler_amount[],
IN ina_exchange_account_name TEXT[],
IN ina_execution_date INT8[],
IN ina_wire_source_h_payto BYTEA[],
IN ina_h_normalized_payto BYTEA[],
IN ina_payto_uri TEXT[],
IN ina_notify TEXT[])
RETURNS SETOF exchange_do_array_reserve_insert_return_type
LANGUAGE plpgsql
AS $$
DECLARE
conflict BOOL;
dup BOOL;
uuid INT8;
i INT4;
my_is_wallet BOOL;
ini_reserve_pub BYTEA;
ini_wire_ref INT8;
ini_credit taler_amount;
ini_exchange_account_name TEXT;
ini_execution_date INT8;
ini_wire_source_h_payto BYTEA;
ini_h_normalized_payto BYTEA;
ini_payto_uri TEXT;
ini_notify TEXT;
BEGIN
FOR i IN 1..array_length(ina_reserve_pub,1)
LOOP
ini_reserve_pub = ina_reserve_pub[i];
ini_wire_ref = ina_wire_ref[i];
ini_credit = ina_credit[i];
ini_exchange_account_name = ina_exchange_account_name[i];
ini_execution_date = ina_execution_date[i];
ini_wire_source_h_payto = ina_wire_source_h_payto[i];
ini_h_normalized_payto = ina_h_normalized_payto[i];
ini_payto_uri = ina_payto_uri[i];
ini_notify = ina_notify[i];
my_is_wallet
= (LOWER (SUBSTRING (ini_payto_uri, 22)) =
'payto://taler-reserve/') OR
(LOWER (SUBSTRING (ini_payto_uri, 27)) =
'payto://taler-reserve-http/');
INSERT INTO kyc_targets
(h_normalized_payto
,is_wallet
) VALUES (
ini_h_normalized_payto
,my_is_wallet
)
ON CONFLICT DO NOTHING;
INSERT INTO wire_targets
(wire_target_h_payto
,h_normalized_payto
,payto_uri
) VALUES (
ini_wire_source_h_payto
,ini_h_normalized_payto
,ini_payto_uri
)
ON CONFLICT DO NOTHING;
INSERT INTO reserves
(reserve_pub
,current_balance
,expiration_date
,gc_date
) VALUES (
ini_reserve_pub
,ini_credit
,in_reserve_expiration
,in_gc_date
)
ON CONFLICT DO NOTHING
RETURNING reserve_uuid
INTO uuid;
conflict = NOT FOUND;
INSERT INTO reserves_in
(reserve_pub
,wire_reference
,credit
,exchange_account_section
,wire_source_h_payto
,execution_date
) VALUES (
ini_reserve_pub
,ini_wire_ref
,ini_credit
,ini_exchange_account_name
,ini_wire_source_h_payto
,ini_execution_date
)
ON CONFLICT DO NOTHING;
IF NOT FOUND
THEN
IF conflict
THEN
dup = TRUE;
else
dup = FALSE;
END IF;
ELSE
IF NOT conflict
THEN
EXECUTE FORMAT (
'NOTIFY %s'
,ini_notify);
END IF;
dup = FALSE;
END IF;
RETURN NEXT (dup,uuid);
END LOOP;
RETURN;
END $$;
CREATE OR REPLACE FUNCTION exchange_do_batch_reserves_update(
IN in_reserve_pub BYTEA,
IN in_expiration_date INT8,
IN in_wire_ref INT8,
IN in_credit taler_amount,
IN in_exchange_account_name TEXT,
IN in_wire_source_h_payto BYTEA,
IN in_notify text,
OUT out_duplicate BOOLEAN)
LANGUAGE plpgsql
AS $$
BEGIN
INSERT INTO reserves_in
(reserve_pub
,wire_reference
,credit
,exchange_account_section
,wire_source_h_payto
,execution_date)
VALUES
(in_reserve_pub
,in_wire_ref
,in_credit
,in_exchange_account_name
,in_wire_source_h_payto
,in_expiration_date)
ON CONFLICT DO NOTHING;
IF FOUND
THEN
out_duplicate = FALSE;
UPDATE reserves rs
SET
current_balance.frac = (rs.current_balance).frac+in_credit.frac
- CASE
WHEN (rs.current_balance).frac + in_credit.frac >= 100000000
THEN 100000000
ELSE 1
END
,current_balance.val = (rs.current_balance).val+in_credit.val
+ CASE
WHEN (rs.current_balance).frac + in_credit.frac >= 100000000
THEN 1
ELSE 0
END
,expiration_date=GREATEST(expiration_date,in_expiration_date)
,gc_date=GREATEST(gc_date,in_expiration_date)
WHERE reserve_pub=in_reserve_pub;
EXECUTE FORMAT (
'NOTIFY %s'
,in_notify);
ELSE
out_duplicate = TRUE;
END IF;
RETURN;
END $$;
CREATE OR REPLACE FUNCTION exchange_do_get_link_data(
IN in_coin_pub BYTEA
)
RETURNS SETOF record
LANGUAGE plpgsql
AS $$
DECLARE
curs CURSOR
FOR
SELECT
melt_serial_id
FROM refresh_commitments
WHERE old_coin_pub=in_coin_pub;
DECLARE
i RECORD;
BEGIN
OPEN curs;
LOOP
FETCH NEXT FROM curs INTO i;
EXIT WHEN NOT FOUND;
RETURN QUERY
SELECT
tp.transfer_pub
,denoms.denom_pub
,rrc.ev_sig
,rrc.ewv
,rrc.link_sig
,rrc.freshcoin_index
,rrc.coin_ev
FROM refresh_revealed_coins rrc
JOIN refresh_transfer_keys tp
ON (tp.melt_serial_id=rrc.melt_serial_id)
JOIN denominations denoms
ON (rrc.denominations_serial=denoms.denominations_serial)
WHERE rrc.melt_serial_id =i.melt_serial_id
ORDER BY tp.transfer_pub,
rrc.freshcoin_index ASC
;
END LOOP;
CLOSE curs;
END $$;
CREATE OR REPLACE FUNCTION exchange_do_batch4_known_coin(
IN in_coin_pub1 BYTEA,
IN in_denom_pub_hash1 BYTEA,
IN in_h_age_commitment1 BYTEA,
IN in_denom_sig1 BYTEA,
IN in_coin_pub2 BYTEA,
IN in_denom_pub_hash2 BYTEA,
IN in_h_age_commitment2 BYTEA,
IN in_denom_sig2 BYTEA,
IN in_coin_pub3 BYTEA,
IN in_denom_pub_hash3 BYTEA,
IN in_h_age_commitment3 BYTEA,
IN in_denom_sig3 BYTEA,
IN in_coin_pub4 BYTEA,
IN in_denom_pub_hash4 BYTEA,
IN in_h_age_commitment4 BYTEA,
IN in_denom_sig4 BYTEA,
OUT existed1 BOOLEAN,
OUT existed2 BOOLEAN,
OUT existed3 BOOLEAN,
OUT existed4 BOOLEAN,
OUT known_coin_id1 INT8,
OUT known_coin_id2 INT8,
OUT known_coin_id3 INT8,
OUT known_coin_id4 INT8,
OUT denom_pub_hash1 BYTEA,
OUT denom_pub_hash2 BYTEA,
OUT denom_pub_hash3 BYTEA,
OUT denom_pub_hash4 BYTEA,
OUT age_commitment_hash1 BYTEA,
OUT age_commitment_hash2 BYTEA,
OUT age_commitment_hash3 BYTEA,
OUT age_commitment_hash4 BYTEA)
LANGUAGE plpgsql
AS $$
BEGIN
WITH dd AS (
SELECT
denominations_serial,
coin
FROM denominations
WHERE denom_pub_hash
IN
(in_denom_pub_hash1,
in_denom_pub_hash2,
in_denom_pub_hash3,
in_denom_pub_hash4)
),
input_rows AS (
VALUES
(in_coin_pub1,
in_denom_pub_hash1,
in_h_age_commitment1,
in_denom_sig1),
(in_coin_pub2,
in_denom_pub_hash2,
in_h_age_commitment2,
in_denom_sig2),
(in_coin_pub3,
in_denom_pub_hash3,
in_h_age_commitment3,
in_denom_sig3),
(in_coin_pub4,
in_denom_pub_hash4,
in_h_age_commitment4,
in_denom_sig4)
),
ins AS (
INSERT INTO known_coins (
coin_pub,
denominations_serial,
age_commitment_hash,
denom_sig,
remaining
)
SELECT
ir.coin_pub,
dd.denominations_serial,
ir.age_commitment_hash,
ir.denom_sig,
dd.coin
FROM input_rows ir
JOIN dd
ON dd.denom_pub_hash = ir.denom_pub_hash
ON CONFLICT DO NOTHING
RETURNING known_coin_id
),
exists AS (
SELECT
CASE
WHEN
ins.known_coin_id IS NOT NULL
THEN
FALSE
ELSE
TRUE
END AS existed,
ins.known_coin_id,
dd.denom_pub_hash,
kc.age_commitment_hash
FROM input_rows ir
LEFT JOIN ins
ON ins.coin_pub = ir.coin_pub
LEFT JOIN known_coins kc
ON kc.coin_pub = ir.coin_pub
LEFT JOIN dd
ON dd.denom_pub_hash = ir.denom_pub_hash
)
SELECT
exists.existed AS existed1,
exists.known_coin_id AS known_coin_id1,
exists.denom_pub_hash AS denom_pub_hash1,
exists.age_commitment_hash AS age_commitment_hash1,
(
SELECT exists.existed
FROM exists
WHERE exists.denom_pub_hash = in_denom_pub_hash2
) AS existed2,
(
SELECT exists.known_coin_id
FROM exists
WHERE exists.denom_pub_hash = in_denom_pub_hash2
) AS known_coin_id2,
(
SELECT exists.denom_pub_hash
FROM exists
WHERE exists.denom_pub_hash = in_denom_pub_hash2
) AS denom_pub_hash2,
(
SELECT exists.age_commitment_hash
FROM exists
WHERE exists.denom_pub_hash = in_denom_pub_hash2
)AS age_commitment_hash2,
(
SELECT exists.existed
FROM exists
WHERE exists.denom_pub_hash = in_denom_pub_hash3
) AS existed3,
(
SELECT exists.known_coin_id
FROM exists
WHERE exists.denom_pub_hash = in_denom_pub_hash3
) AS known_coin_id3,
(
SELECT exists.denom_pub_hash
FROM exists
WHERE exists.denom_pub_hash = in_denom_pub_hash3
) AS denom_pub_hash3,
(
SELECT exists.age_commitment_hash
FROM exists
WHERE exists.denom_pub_hash = in_denom_pub_hash3
)AS age_commitment_hash3,
(
SELECT exists.existed
FROM exists
WHERE exists.denom_pub_hash = in_denom_pub_hash4
) AS existed4,
(
SELECT exists.known_coin_id
FROM exists
WHERE exists.denom_pub_hash = in_denom_pub_hash4
) AS known_coin_id4,
(
SELECT exists.denom_pub_hash
FROM exists
WHERE exists.denom_pub_hash = in_denom_pub_hash4
) AS denom_pub_hash4,
(
SELECT exists.age_commitment_hash
FROM exists
WHERE exists.denom_pub_hash = in_denom_pub_hash4
)AS age_commitment_hash4
FROM exists;
RETURN;
END $$;
CREATE OR REPLACE FUNCTION exchange_do_batch2_known_coin(
IN in_coin_pub1 BYTEA,
IN in_denom_pub_hash1 BYTEA,
IN in_h_age_commitment1 BYTEA,
IN in_denom_sig1 BYTEA,
IN in_coin_pub2 BYTEA,
IN in_denom_pub_hash2 BYTEA,
IN in_h_age_commitment2 BYTEA,
IN in_denom_sig2 BYTEA,
OUT existed1 BOOLEAN,
OUT existed2 BOOLEAN,
OUT known_coin_id1 INT8,
OUT known_coin_id2 INT8,
OUT denom_pub_hash1 BYTEA,
OUT denom_pub_hash2 BYTEA,
OUT age_commitment_hash1 BYTEA,
OUT age_commitment_hash2 BYTEA)
LANGUAGE plpgsql
AS $$
BEGIN
WITH dd AS (
SELECT
denominations_serial,
coin
FROM denominations
WHERE denom_pub_hash
IN
(in_denom_pub_hash1,
in_denom_pub_hash2)
),
input_rows AS (
VALUES
(in_coin_pub1,
in_denom_pub_hash1,
in_h_age_commitment1,
in_denom_sig1),
(in_coin_pub2,
in_denom_pub_hash2,
in_h_age_commitment2,
in_denom_sig2)
),
ins AS (
INSERT INTO known_coins (
coin_pub,
denominations_serial,
age_commitment_hash,
denom_sig,
remaining
)
SELECT
ir.coin_pub,
dd.denominations_serial,
ir.age_commitment_hash,
ir.denom_sig,
dd.coin
FROM input_rows ir
JOIN dd
ON dd.denom_pub_hash = ir.denom_pub_hash
ON CONFLICT DO NOTHING
RETURNING known_coin_id
),
exists AS (
SELECT
CASE
WHEN ins.known_coin_id IS NOT NULL
THEN
FALSE
ELSE
TRUE
END AS existed,
ins.known_coin_id,
dd.denom_pub_hash,
kc.age_commitment_hash
FROM input_rows ir
LEFT JOIN ins
ON ins.coin_pub = ir.coin_pub
LEFT JOIN known_coins kc
ON kc.coin_pub = ir.coin_pub
LEFT JOIN dd
ON dd.denom_pub_hash = ir.denom_pub_hash
)
SELECT
exists.existed AS existed1,
exists.known_coin_id AS known_coin_id1,
exists.denom_pub_hash AS denom_pub_hash1,
exists.age_commitment_hash AS age_commitment_hash1,
(
SELECT exists.existed
FROM exists
WHERE exists.denom_pub_hash = in_denom_pub_hash2
) AS existed2,
(
SELECT exists.known_coin_id
FROM exists
WHERE exists.denom_pub_hash = in_denom_pub_hash2
) AS known_coin_id2,
(
SELECT exists.denom_pub_hash
FROM exists
WHERE exists.denom_pub_hash = in_denom_pub_hash2
) AS denom_pub_hash2,
(
SELECT exists.age_commitment_hash
FROM exists
WHERE exists.denom_pub_hash = in_denom_pub_hash2
)AS age_commitment_hash2
FROM exists;
RETURN;
END $$;
CREATE OR REPLACE FUNCTION exchange_do_batch1_known_coin(
IN in_coin_pub1 BYTEA,
IN in_denom_pub_hash1 BYTEA,
IN in_h_age_commitment1 BYTEA,
IN in_denom_sig1 BYTEA,
OUT existed1 BOOLEAN,
OUT known_coin_id1 INT8,
OUT denom_pub_hash1 BYTEA,
OUT age_commitment_hash1 BYTEA)
LANGUAGE plpgsql
AS $$
BEGIN
WITH dd AS (
SELECT
denominations_serial,
coin
FROM denominations
WHERE denom_pub_hash
IN
(in_denom_pub_hash1,
in_denom_pub_hash2)
),
input_rows AS (
VALUES
(in_coin_pub1,
in_denom_pub_hash1,
in_h_age_commitment1,
in_denom_sig1)
),
ins AS (
INSERT INTO known_coins (
coin_pub,
denominations_serial,
age_commitment_hash,
denom_sig,
remaining
)
SELECT
ir.coin_pub,
dd.denominations_serial,
ir.age_commitment_hash,
ir.denom_sig,
dd.coin
FROM input_rows ir
JOIN dd
ON dd.denom_pub_hash = ir.denom_pub_hash
ON CONFLICT DO NOTHING
RETURNING known_coin_id
),
exists AS (
SELECT
CASE
WHEN ins.known_coin_id IS NOT NULL
THEN
FALSE
ELSE
TRUE
END AS existed,
ins.known_coin_id,
dd.denom_pub_hash,
kc.age_commitment_hash
FROM input_rows ir
LEFT JOIN ins
ON ins.coin_pub = ir.coin_pub
LEFT JOIN known_coins kc
ON kc.coin_pub = ir.coin_pub
LEFT JOIN dd
ON dd.denom_pub_hash = ir.denom_pub_hash
)
SELECT
exists.existed AS existed1,
exists.known_coin_id AS known_coin_id1,
exists.denom_pub_hash AS denom_pub_hash1,
exists.age_commitment_hash AS age_commitment_hash1
FROM exists;
RETURN;
END $$;
DROP PROCEDURE IF EXISTS exchange_do_kycauth_in_insert;
CREATE PROCEDURE exchange_do_kycauth_in_insert(
IN in_account_pub BYTEA,
IN in_wire_reference INT8,
IN in_credit taler_amount,
IN in_wire_source_h_payto BYTEA,
IN in_h_normalized_payto BYTEA,
IN in_payto_uri TEXT,
IN in_exchange_account_name TEXT,
IN in_execution_date INT8,
IN in_notify_s TEXT)
LANGUAGE plpgsql
AS $$
DECLARE
my_is_wallet BOOL;
BEGIN
INSERT INTO kycauths_in
(account_pub
,wire_reference
,credit
,wire_source_h_payto
,exchange_account_section
,execution_date
) VALUES (
in_account_pub
,in_wire_reference
,in_credit
,in_wire_source_h_payto
,in_exchange_account_name
,in_execution_date
)
ON CONFLICT DO NOTHING;
IF NOT FOUND
THEN
RETURN;
END IF;
UPDATE kyc_targets
SET target_pub=in_account_pub
WHERE h_normalized_payto=in_h_normalized_payto;
IF NOT FOUND
THEN
my_is_wallet
= (LOWER (SUBSTRING (in_payto_uri, 22)) =
'payto://taler-reserve/') OR
(LOWER (SUBSTRING (in_payto_uri, 27)) =
'payto://taler-reserve-http/');
INSERT INTO kyc_targets
(h_normalized_payto
,is_wallet
,target_pub
) VALUES (
in_h_normalized_payto
,my_is_wallet
,in_account_pub);
INSERT INTO wire_targets
(wire_target_h_payto
,h_normalized_payto
,payto_uri
) VALUES (
in_wire_source_h_payto
,in_h_normalized_payto
,in_payto_uri);
END IF;
EXECUTE FORMAT (
'NOTIFY %s'
,in_notify_s);
END $$;
DROP FUNCTION IF EXISTS exchange_do_trigger_kyc_rule_for_account;
CREATE FUNCTION exchange_do_trigger_kyc_rule_for_account(
IN in_h_normalized_payto BYTEA,
IN in_account_pub BYTEA,
IN in_merchant_pub BYTEA,
IN in_payto_uri TEXT,
IN in_h_full_payto BYTEA,
IN in_now INT8,
IN in_jmeasures JSONB,
IN in_display_priority INT4,
IN in_notify_s TEXT,
OUT out_legitimization_measure_serial_id INT8,
OUT out_bad_kyc_auth BOOL)
LANGUAGE plpgsql
AS $$
DECLARE
my_rec RECORD;
my_is_wallet BOOL;
my_access_token BYTEA;
my_account_pub BYTEA;
my_reserve_pub BYTEA;
BEGIN
SELECT access_token
,target_pub
INTO my_rec
FROM kyc_targets
WHERE h_normalized_payto=in_h_normalized_payto;
IF FOUND
THEN
my_access_token = my_rec.access_token;
my_account_pub = my_rec.target_pub;
out_bad_kyc_auth = COALESCE ((my_account_pub != in_merchant_pub), TRUE);
ELSE
my_is_wallet
= (LOWER (SUBSTRING (in_payto_uri, 22)) =
'payto://taler-reserve/') OR
(LOWER (SUBSTRING (in_payto_uri, 27)) =
'payto://taler-reserve-http/');
INSERT INTO kyc_targets
(h_normalized_payto
,is_wallet
,target_pub
) VALUES (
in_h_normalized_payto
,my_is_wallet
,in_account_pub
)
RETURNING access_token
INTO my_access_token;
INSERT INTO wire_targets
(payto_uri
,wire_target_h_payto
,h_normalized_payto
) VALUES (
in_payto_uri
,in_h_full_payto
,in_h_normalized_payto
);
out_bad_kyc_auth=TRUE;
END IF;
IF out_bad_kyc_auth
THEN
PERFORM FROM reserves_in
WHERE wire_source_h_payto IN (
SELECT wire_target_h_payto
FROM wire_targets
WHERE h_normalized_payto=in_h_normalized_payto
)
AND reserve_pub = in_merchant_pub
ORDER BY execution_date DESC;
IF FOUND
THEN
out_bad_kyc_auth = FALSE;
END IF;
END IF;
UPDATE legitimization_measures
SET display_priority=GREATEST(in_display_priority,display_priority)
WHERE access_token=my_access_token
AND jmeasures=in_jmeasures
AND NOT is_finished
RETURNING legitimization_measure_serial_id
INTO out_legitimization_measure_serial_id;
IF NOT FOUND
THEN
INSERT INTO legitimization_measures
(access_token
,start_time
,jmeasures
,display_priority
) VALUES (
my_access_token
,in_now
,in_jmeasures
,in_display_priority)
RETURNING legitimization_measure_serial_id
INTO out_legitimization_measure_serial_id;
UPDATE legitimization_measures
SET is_finished=TRUE
WHERE access_token=my_access_token
AND NOT is_finished
AND legitimization_measure_serial_id != out_legitimization_measure_serial_id;
END IF;
EXECUTE FORMAT (
'NOTIFY %s'
,in_notify_s);
END $$;
DROP FUNCTION IF EXISTS exchange_do_lookup_kyc_requirement_by_row;
CREATE FUNCTION exchange_do_lookup_kyc_requirement_by_row(
IN in_h_normalized_payto BYTEA,
IN in_account_pub BYTEA,
OUT out_account_pub BYTEA,
OUT out_reserve_pub BYTEA,
OUT out_access_token BYTEA,
OUT out_jrules JSONB,
OUT out_is_wallet BOOLEAN,
OUT out_not_found BOOLEAN,
OUT out_rule_gen INT8,
OUT out_aml_review BOOLEAN,
OUT out_kyc_required BOOLEAN)
LANGUAGE plpgsql
AS $$
DECLARE
my_wtrec RECORD;
my_lorec RECORD;
my_ok BOOL;
BEGIN
SELECT access_token
,target_pub
,is_wallet
INTO my_wtrec
FROM kyc_targets
WHERE h_normalized_payto=in_h_normalized_payto;
IF NOT FOUND
THEN
out_not_found = TRUE;
out_kyc_required = FALSE;
RETURN;
END IF;
my_ok = (in_account_pub IS NOT NULL) AND
(my_wtrec.target_pub = in_account_pub);
IF ( (NOT my_ok) AND
(in_account_pub IS NOT NULL) )
THEN
PERFORM
FROM reserves_in
WHERE reserve_pub=in_account_pub
AND wire_source_h_payto IN
(SELECT wire_target_h_payto
FROM wire_targets
WHERE h_normalized_payto=in_h_normalized_payto);
IF FOUND
THEN
my_wtrec.target_pub = in_account_pub;
my_ok = TRUE;
END IF;
END IF;
IF (NOT my_ok AND
( (in_account_pub IS NOT NULL) OR
(my_wtrec.target_pub IS NULL) ) )
THEN
out_not_found = TRUE;
out_kyc_required = FALSE;
RETURN;
END IF;
out_not_found = FALSE;
out_is_wallet = my_wtrec.is_wallet;
out_account_pub = my_wtrec.target_pub;
out_access_token = my_wtrec.access_token;
PERFORM
FROM legitimization_measures
WHERE access_token=out_access_token
AND NOT is_finished
LIMIT 1;
out_kyc_required = FOUND;
SELECT jnew_rules
,to_investigate
,outcome_serial_id
INTO my_lorec
FROM legitimization_outcomes
WHERE h_payto=in_h_normalized_payto
AND is_active;
IF FOUND
THEN
out_jrules=my_lorec.jnew_rules;
out_aml_review=my_lorec.to_investigate;
out_rule_gen=my_lorec.outcome_serial_id;
END IF;
SELECT reserve_pub
INTO out_reserve_pub
FROM reserves_in
WHERE wire_source_h_payto
IN (SELECT wt.wire_target_h_payto
FROM wire_targets wt
WHERE h_normalized_payto=in_h_normalized_payto)
ORDER BY execution_date DESC, reserve_in_serial_id DESC
LIMIT 1;
END $$;
DROP FUNCTION IF EXISTS exchange_do_insert_active_legitimization_measure;
CREATE FUNCTION exchange_do_insert_active_legitimization_measure(
IN in_access_token BYTEA,
IN in_start_time INT8,
IN in_jmeasures JSONB,
OUT out_legitimization_measure_serial_id INT8)
LANGUAGE plpgsql
AS $$
BEGIN
UPDATE legitimization_measures
SET is_finished=TRUE
WHERE access_token=in_access_token
AND NOT is_finished;
INSERT INTO legitimization_measures
(access_token
,start_time
,jmeasures
,display_priority)
VALUES
(in_access_token
,in_start_time
,in_jmeasures
,1)
RETURNING
legitimization_measure_serial_id
INTO
out_legitimization_measure_serial_id;
END $$;
COMMENT ON FUNCTION exchange_do_insert_active_legitimization_measure(BYTEA, INT8, JSONB)
IS 'Inserts legitimization measure for an account and marks all existing such measures as inactive';
CREATE OR REPLACE FUNCTION exchange_do_select_aggregations_above_serial(
IN in_min_serial_id INT8)
RETURNS SETOF exchange_do_select_aggregations_above_serial_return_type
LANGUAGE plpgsql
AS $$
DECLARE
aggregation CURSOR
FOR
SELECT
batch_deposit_serial_id
,aggregation_serial_id
FROM aggregation_tracking
WHERE aggregation_serial_id >= in_min_serial_id
ORDER BY aggregation_serial_id ASC;
DECLARE
my_total_val INT8;
DECLARE
my_total_frac INT8;
DECLARE
my_total taler_amount;
DECLARE
my_batch_record RECORD;
DECLARE
i RECORD;
BEGIN
OPEN aggregation;
LOOP
FETCH NEXT FROM aggregation INTO i;
EXIT WHEN NOT FOUND;
SELECT
SUM((cdep.amount_with_fee).val) AS total_val
,SUM((cdep.amount_with_fee).frac::INT8) AS total_frac
INTO
my_batch_record
FROM coin_deposits cdep
WHERE cdep.batch_deposit_serial_id = i.batch_deposit_serial_id;
my_total_val=my_batch_record.total_val;
my_total_frac=my_batch_record.total_frac;
my_total.val = my_total_val + my_total_frac / 100000000;
my_total.frac = my_total_frac % 100000000;
RETURN NEXT (
i.batch_deposit_serial_id
,i.aggregation_serial_id
,my_total
);
END LOOP;
CLOSE aggregation;
RETURN;
END $$;
DROP FUNCTION IF EXISTS exchange_do_persist_kyc_attributes;
CREATE FUNCTION exchange_do_persist_kyc_attributes(
IN in_process_row INT8,
IN in_h_payto BYTEA,
IN in_birthday INT4,
IN in_provider_name TEXT,
IN in_provider_account_id TEXT,
IN in_provider_legitimization_id TEXT,
IN in_collection_time_ts INT8,
IN in_expiration_time INT8,
IN in_expiration_time_ts INT8,
IN in_enc_attributes BYTEA,
IN in_kyc_completed_notify_s TEXT,
IN in_form_name TEXT,
OUT out_ok BOOLEAN)
LANGUAGE plpgsql
AS $$
BEGIN
INSERT INTO kyc_attributes
(h_payto
,collection_time
,expiration_time
,form_name
,by_aml_officer
,encrypted_attributes
,legitimization_serial
) VALUES
(in_h_payto
,in_collection_time_ts
,in_expiration_time_ts
,in_form_name
,FALSE
,in_enc_attributes
,in_process_row);
NOTIFY XSX9Z5XGWWYFKXTAYCES63B62527JKNX9XD0131Z08THVV8YW5BZG;
UPDATE legitimization_processes
SET provider_user_id=in_provider_account_id
,provider_legitimization_id=in_provider_legitimization_id
,expiration_time=GREATEST(expiration_time,in_expiration_time)
,finished=TRUE
WHERE h_payto=in_h_payto
AND legitimization_process_serial_id=in_process_row
AND provider_name=in_provider_name;
out_ok=FOUND;
UPDATE reserves
SET birthday=in_birthday
WHERE (reserve_pub IN
(SELECT reserve_pub
FROM reserves_in
WHERE wire_source_h_payto IN
(SELECT wire_source_h_payto
FROM wire_targets
WHERE h_normalized_payto=in_h_payto) ) )
AND ( ((current_balance).frac > 0) OR
((current_balance).val > 0 ) )
AND (expiration_date > in_collection_time_ts);
EXECUTE FORMAT (
'NOTIFY %s'
,in_kyc_completed_notify_s);
INSERT INTO kyc_alerts
(h_payto
,trigger_type)
VALUES
(in_h_payto,1)
ON CONFLICT DO NOTHING;
END $$;
COMMENT ON FUNCTION exchange_do_persist_kyc_attributes(INT8, BYTEA, INT4, TEXT, TEXT, TEXT, INT8, INT8, INT8, BYTEA, TEXT, TEXT)
IS 'Inserts new KYC attributes and updates the status of the legitimization process';
DROP FUNCTION IF EXISTS exchange_do_insert_aml_program_failure;
CREATE FUNCTION exchange_do_insert_aml_program_failure (
IN in_legitimization_process_serial_id INT8,
IN in_h_payto BYTEA,
IN in_now INT8,
IN in_error_code INT4,
IN in_error_message TEXT,
IN in_kyc_completed_notify_s TEXT,
OUT out_update BOOLEAN)
LANGUAGE plpgsql
AS $$
BEGIN
UPDATE legitimization_processes
SET finished=TRUE
,error_code=in_error_code
,error_message=in_error_message
WHERE h_payto=in_h_payto
AND legitimization_process_serial_id=in_legitimization_process_serial_id;
out_update = FOUND;
IF NOT FOUND
THEN
INSERT INTO legitimization_processes
(finished
,error_code
,error_message
,h_payto
,start_time
,provider_section
) VALUES (
TRUE
,in_error_code
,in_error_message
,in_h_payto
,in_now
,'skip'
);
END IF;
EXECUTE FORMAT (
'NOTIFY %s'
,in_kyc_completed_notify_s);
INSERT INTO kyc_alerts
(h_payto
,trigger_type)
VALUES
(in_h_payto,1)
ON CONFLICT DO NOTHING;
END $$;
COMMENT ON FUNCTION exchange_do_insert_aml_program_failure(INT8, BYTEA, INT8, INT4, TEXT, TEXT)
IS 'Stores information about an AML program run that failed into the legitimization_processes table. Either updates a row of an existing legitimization process, or creates a new entry.';
DROP FUNCTION IF EXISTS exchange_do_set_aml_lock;
CREATE FUNCTION exchange_do_set_aml_lock (
IN in_h_payto BYTEA,
IN in_now INT8,
IN in_expiration INT8,
OUT out_aml_program_lock_timeout INT8)
LANGUAGE plpgsql
AS $$
BEGIN
UPDATE kyc_targets
SET aml_program_lock_timeout=in_expiration
WHERE h_normalized_payto=in_h_payto
AND ( (aml_program_lock_timeout IS NULL)
OR (aml_program_lock_timeout < in_now) );
IF NOT FOUND
THEN
SELECT aml_program_lock_timeout
INTO out_aml_program_lock_timeout
FROM kyc_targets
WHERE h_normalized_payto=in_h_payto;
ELSE
out_aml_program_lock_timeout = 0;
END IF;
END $$;
COMMENT ON FUNCTION exchange_do_set_aml_lock(BYTEA, INT8, INT8)
IS 'Tries to lock an account for running an AML program. Returns the timeout of the existing lock, 0 if there is no existing lock, and NULL if we do not know the account.';
DROP FUNCTION IF EXISTS exchange_do_insert_sanction_list_hit;
CREATE FUNCTION exchange_do_insert_sanction_list_hit(
IN in_h_normalized_payto BYTEA,
IN in_decision_time INT8,
IN in_expiration_time INT8,
IN in_properties JSONB,
IN in_new_rules JSONB,
IN in_to_investigate BOOLEAN,
IN in_notify_s TEXT,
IN ina_events TEXT[],
OUT out_outcome_serial_id INT8)
LANGUAGE plpgsql
AS $$
DECLARE
my_i INT4;
ini_event TEXT;
BEGIN
UPDATE legitimization_outcomes
SET is_active=FALSE
WHERE h_payto=in_h_normalized_payto;
INSERT INTO legitimization_outcomes
(h_payto
,decision_time
,expiration_time
,jproperties
,to_investigate
,jnew_rules
)
VALUES
(in_h_normalized_payto
,in_decision_time
,in_expiration_time
,in_properties
,in_to_investigate
,in_new_rules
)
RETURNING
outcome_serial_id
INTO
out_outcome_serial_id;
FOR i IN 1..COALESCE(array_length(ina_events,1),0)
LOOP
ini_event = ina_events[i];
INSERT INTO kyc_events
(event_timestamp
,event_type)
VALUES
(in_decision_time
,ini_event);
END LOOP;
EXECUTE FORMAT (
'NOTIFY %s'
,in_notify_s);
END $$;
COMMENT ON FUNCTION exchange_do_insert_sanction_list_hit(BYTEA, INT8, INT8, JSONB, JSONB, BOOLEAN, TEXT, TEXT[])
IS 'Insert result from sanction list check into the table';
SET search_path TO exchange;
DROP FUNCTION IF EXISTS interval_to_start;
CREATE OR REPLACE FUNCTION interval_to_start (
IN in_timestamp TIMESTAMP,
IN in_range statistic_range,
OUT out_bucket_start INT8
)
LANGUAGE plpgsql
AS $$
BEGIN
out_bucket_start = EXTRACT(EPOCH FROM DATE_TRUNC(in_range::text, in_timestamp));
END $$;
COMMENT ON FUNCTION interval_to_start
IS 'computes the start time of the bucket for an event at the current time given the desired bucket range';
DROP PROCEDURE IF EXISTS exchange_do_bump_number_bucket_stat;
CREATE OR REPLACE PROCEDURE exchange_do_bump_number_bucket_stat(
in_slug TEXT,
in_h_payto BYTEA,
in_timestamp TIMESTAMP,
in_delta INT8
)
LANGUAGE plpgsql
AS $$
DECLARE
my_meta INT8;
my_range statistic_range;
my_bucket_start INT8;
my_curs CURSOR (arg_slug TEXT)
FOR SELECT UNNEST(ranges)
FROM exchange_statistic_bucket_meta
WHERE slug=arg_slug;
BEGIN
SELECT bmeta_serial_id
INTO my_meta
FROM exchange_statistic_bucket_meta
WHERE slug=in_slug
AND stype='number';
IF NOT FOUND
THEN
RETURN;
END IF;
OPEN my_curs (arg_slug:=in_slug);
LOOP
FETCH NEXT
FROM my_curs
INTO my_range;
EXIT WHEN NOT FOUND;
SELECT *
INTO my_bucket_start
FROM interval_to_start (in_timestamp, my_range);
UPDATE exchange_statistic_bucket_counter
SET cumulative_number = cumulative_number + in_delta
WHERE bmeta_serial_id=my_meta
AND h_payto=in_h_payto
AND bucket_start=my_bucket_start
AND bucket_range=my_range;
IF NOT FOUND
THEN
INSERT INTO exchange_statistic_bucket_counter
(bmeta_serial_id
,h_payto
,bucket_start
,bucket_range
,cumulative_number
) VALUES (
my_meta
,in_h_payto
,my_bucket_start
,my_range
,in_delta);
END IF;
END LOOP;
CLOSE my_curs;
END $$;
DROP PROCEDURE IF EXISTS exchange_do_bump_amount_bucket_stat;
CREATE OR REPLACE PROCEDURE exchange_do_bump_amount_bucket_stat(
in_slug TEXT,
in_h_payto BYTEA,
in_timestamp TIMESTAMP,
in_delta taler_amount
)
LANGUAGE plpgsql
AS $$
DECLARE
my_meta INT8;
my_range statistic_range;
my_bucket_start INT8;
my_curs CURSOR (arg_slug TEXT)
FOR SELECT UNNEST(ranges)
FROM exchange_statistic_bucket_meta
WHERE slug=arg_slug;
BEGIN
SELECT bmeta_serial_id
INTO my_meta
FROM exchange_statistic_bucket_meta
WHERE slug=in_slug
AND stype='amount';
IF NOT FOUND
THEN
RETURN;
END IF;
OPEN my_curs (arg_slug:=in_slug);
LOOP
FETCH NEXT
FROM my_curs
INTO my_range;
EXIT WHEN NOT FOUND;
SELECT *
INTO my_bucket_start
FROM interval_to_start (in_timestamp, my_range);
UPDATE exchange_statistic_bucket_amount
SET
cumulative_value.val = (cumulative_value).val + (in_delta).val
+ CASE
WHEN (in_delta).frac + (cumulative_value).frac >= 100000000
THEN 1
ELSE 0
END,
cumulative_value.frac = (cumulative_value).frac + (in_delta).frac
- CASE
WHEN (in_delta).frac + (cumulative_value).frac >= 100000000
THEN 100000000
ELSE 0
END
WHERE bmeta_serial_id=my_meta
AND h_payto=in_h_payto
AND bucket_start=my_bucket_start
AND bucket_range=my_range;
IF NOT FOUND
THEN
INSERT INTO exchange_statistic_bucket_amount
(bmeta_serial_id
,h_payto
,bucket_start
,bucket_range
,cumulative_value
) VALUES (
my_meta
,in_h_payto
,my_bucket_start
,my_range
,in_delta);
END IF;
END LOOP;
CLOSE my_curs;
END $$;
COMMENT ON PROCEDURE exchange_do_bump_amount_bucket_stat
IS 'Updates an amount statistic tracked over buckets';
DROP PROCEDURE IF EXISTS exchange_do_bump_number_interval_stat;
CREATE OR REPLACE PROCEDURE exchange_do_bump_number_interval_stat(
in_slug TEXT,
in_h_payto BYTEA,
in_timestamp TIMESTAMP,
in_delta INT8
)
LANGUAGE plpgsql
AS $$
DECLARE
my_now INT8;
my_record RECORD;
my_meta INT8;
my_ranges INT8[];
my_precisions INT8[];
my_rangex INT8;
my_precisionx INT8;
my_start INT8;
my_event INT8;
BEGIN
my_now = ROUND(EXTRACT(epoch FROM CURRENT_TIMESTAMP(0)::TIMESTAMP) * 1000000)::INT8 / 1000 / 1000;
SELECT imeta_serial_id
,ranges AS ranges
,precisions AS precisions
INTO my_record
FROM exchange_statistic_interval_meta
WHERE slug=in_slug
AND stype='number';
IF NOT FOUND
THEN
RETURN;
END IF;
my_start = ROUND(EXTRACT(epoch FROM in_timestamp) * 1000000)::INT8 / 1000 / 1000;
my_precisions = my_record.precisions;
my_ranges = my_record.ranges;
my_rangex = NULL;
FOR my_x IN 1..COALESCE(array_length(my_ranges,1),0)
LOOP
IF my_now - my_ranges[my_x] < my_start
THEN
my_rangex = my_ranges[my_x];
my_precisionx = my_precisions[my_x];
EXIT;
END IF;
END LOOP;
IF my_rangex IS NULL
THEN
RETURN;
END IF;
my_meta = my_record.imeta_serial_id;
my_start = my_start - my_start % my_precisionx;
INSERT INTO exchange_statistic_counter_event AS msce
(imeta_serial_id
,h_payto
,slot
,delta)
VALUES
(my_meta
,in_h_payto
,my_start
,in_delta)
ON CONFLICT (imeta_serial_id, h_payto, slot)
DO UPDATE SET
delta = msce.delta + in_delta
RETURNING nevent_serial_id
INTO my_event;
UPDATE exchange_statistic_interval_counter
SET cumulative_number = cumulative_number + in_delta
WHERE imeta_serial_id = my_meta
AND h_payto = in_h_payto
AND range=my_rangex;
IF NOT FOUND
THEN
INSERT INTO exchange_statistic_interval_counter
(imeta_serial_id
,h_payto
,range
,event_delimiter
,cumulative_number
) VALUES (
my_meta
,in_h_payto
,my_rangex
,my_event
,in_delta);
END IF;
END $$;
COMMENT ON PROCEDURE exchange_do_bump_number_interval_stat
IS 'Updates a numeric statistic tracked over an interval';
DROP PROCEDURE IF EXISTS exchange_do_bump_amount_interval_stat;
CREATE OR REPLACE PROCEDURE exchange_do_bump_amount_interval_stat(
in_slug TEXT,
in_h_payto BYTEA,
in_timestamp TIMESTAMP,
in_delta taler_amount
)
LANGUAGE plpgsql
AS $$
DECLARE
my_now INT8;
my_record RECORD;
my_meta INT8;
my_ranges INT8[];
my_precisions INT8[];
my_x INT;
my_rangex INT8;
my_precisionx INT8;
my_start INT8;
my_event INT8;
BEGIN
my_now = ROUND(EXTRACT(epoch FROM CURRENT_TIMESTAMP(0)::TIMESTAMP) * 1000000)::INT8 / 1000 / 1000;
SELECT imeta_serial_id
,ranges
,precisions
INTO my_record
FROM exchange_statistic_interval_meta
WHERE slug=in_slug
AND stype='amount';
IF NOT FOUND
THEN
RETURN;
END IF;
my_start = ROUND(EXTRACT(epoch FROM in_timestamp) * 1000000)::INT8 / 1000 / 1000;
my_precisions = my_record.precisions;
my_ranges = my_record.ranges;
my_rangex = NULL;
FOR my_x IN 1..COALESCE(array_length(my_ranges,1),0)
LOOP
IF my_now - my_ranges[my_x] < my_start
THEN
my_rangex = my_ranges[my_x];
my_precisionx = my_precisions[my_x];
EXIT;
END IF;
END LOOP;
IF my_rangex IS NULL
THEN
RETURN;
END IF;
my_start = my_start - my_start % my_precisionx;
my_meta = my_record.imeta_serial_id;
INSERT INTO exchange_statistic_amount_event AS msae
(imeta_serial_id
,h_payto
,slot
,delta
) VALUES (
my_meta
,in_h_payto
,my_start
,in_delta
)
ON CONFLICT (imeta_serial_id, h_payto, slot)
DO UPDATE SET
delta.val = (msae.delta).val + (in_delta).val
+ CASE
WHEN (in_delta).frac + (msae.delta).frac >= 100000000
THEN 1
ELSE 0
END,
delta.frac = (msae.delta).frac + (in_delta).frac
- CASE
WHEN (in_delta).frac + (msae.delta).frac >= 100000000
THEN 100000000
ELSE 0
END
RETURNING aevent_serial_id
INTO my_event;
UPDATE exchange_statistic_interval_amount
SET
cumulative_value.val = (cumulative_value).val + (in_delta).val
+ CASE
WHEN (in_delta).frac + (cumulative_value).frac >= 100000000
THEN 1
ELSE 0
END,
cumulative_value.frac = (cumulative_value).frac + (in_delta).frac
- CASE
WHEN (in_delta).frac + (cumulative_value).frac >= 100000000
THEN 100000000
ELSE 0
END
WHERE imeta_serial_id=my_meta
AND h_payto=in_h_payto
AND range=my_rangex;
IF NOT FOUND
THEN
INSERT INTO exchange_statistic_interval_amount
(imeta_serial_id
,h_payto
,range
,event_delimiter
,cumulative_value
) VALUES (
my_meta
,in_h_payto
,my_rangex
,my_event
,in_delta);
END IF;
END $$;
COMMENT ON PROCEDURE exchange_do_bump_amount_interval_stat
IS 'Updates an amount statistic tracked over an interval';
DROP PROCEDURE IF EXISTS exchange_do_bump_number_stat;
CREATE OR REPLACE PROCEDURE exchange_do_bump_number_stat(
in_slug TEXT,
in_h_payto BYTEA,
in_timestamp TIMESTAMP,
in_delta INT8
)
LANGUAGE plpgsql
AS $$
BEGIN
CALL exchange_do_bump_number_bucket_stat (in_slug, in_h_payto, in_timestamp, in_delta);
CALL exchange_do_bump_number_interval_stat (in_slug, in_h_payto, in_timestamp, in_delta);
END $$;
COMMENT ON PROCEDURE exchange_do_bump_number_stat
IS 'Updates a numeric statistic (bucket or interval)';
DROP PROCEDURE IF EXISTS exchange_do_bump_amount_stat;
CREATE OR REPLACE PROCEDURE exchange_do_bump_amount_stat(
in_slug TEXT,
in_h_payto BYTEA,
in_timestamp TIMESTAMP,
in_delta taler_amount
)
LANGUAGE plpgsql
AS $$
BEGIN
CALL exchange_do_bump_amount_bucket_stat (in_slug, in_h_payto, in_timestamp, in_delta);
CALL exchange_do_bump_amount_interval_stat (in_slug, in_h_payto, in_timestamp, in_delta);
END $$;
COMMENT ON PROCEDURE exchange_do_bump_amount_stat
IS 'Updates an amount statistic (bucket or interval)';
DROP FUNCTION IF EXISTS exchange_statistic_interval_number_get;
CREATE OR REPLACE FUNCTION exchange_statistic_interval_number_get (
IN in_slug TEXT,
IN in_h_payto BYTEA
)
RETURNS SETOF exchange_statistic_interval_number_get_return_value
LANGUAGE plpgsql
AS $$
DECLARE
my_time INT8 DEFAULT ROUND(EXTRACT(epoch FROM CURRENT_TIMESTAMP(0)::TIMESTAMP) * 1000000)::INT8 / 1000 / 1000;
my_ranges INT8[];
my_range INT8;
my_delta INT8;
my_meta INT8;
my_next_max_serial INT8;
my_rec RECORD;
my_irec RECORD;
my_i INT;
my_min_serial INT8 DEFAULT NULL;
my_rval exchange_statistic_interval_number_get_return_value;
BEGIN
SELECT imeta_serial_id
,ranges
,precisions
INTO my_rec
FROM exchange_statistic_interval_meta
WHERE slug=in_slug;
IF NOT FOUND
THEN
RETURN;
END IF;
my_rval.rvalue = 0;
my_ranges = my_rec.ranges;
my_meta = my_rec.imeta_serial_id;
FOR my_i IN 1..COALESCE(array_length(my_ranges,1),0)
LOOP
my_range = my_ranges[my_i];
SELECT event_delimiter
,cumulative_number
INTO my_irec
FROM exchange_statistic_interval_counter
WHERE imeta_serial_id = my_meta
AND range = my_range
AND h_payto = in_h_payto;
IF FOUND
THEN
my_min_serial = my_irec.event_delimiter;
my_rval.rvalue = my_rval.rvalue + my_irec.cumulative_number;
SELECT SUM(delta) AS delta_sum
INTO my_irec
FROM exchange_statistic_counter_event
WHERE imeta_serial_id = my_meta
AND h_payto = in_h_payto
AND slot < my_time - my_range
AND nevent_serial_id >= my_min_serial;
IF FOUND AND my_irec.delta_sum IS NOT NULL
THEN
my_delta = my_irec.delta_sum;
my_rval.rvalue = my_rval.rvalue - my_delta;
SELECT nevent_serial_id
INTO my_next_max_serial
FROM exchange_statistic_counter_event
WHERE imeta_serial_id = my_meta
AND h_payto = in_h_payto
AND slot >= my_time - my_range
AND nevent_serial_id >= my_min_serial
ORDER BY slot ASC
LIMIT 1;
IF FOUND
THEN
UPDATE exchange_statistic_interval_counter
SET cumulative_number = cumulative_number - my_delta,
event_delimiter = my_next_max_serial
WHERE imeta_serial_id = my_meta
AND h_payto = in_h_payto
AND range = my_range;
ELSE
DELETE FROM exchange_statistic_interval_counter
WHERE imeta_serial_id = my_meta
AND h_payto = in_h_payto
AND range = my_range;
END IF;
IF (my_i < array_length(my_ranges,1))
THEN
UPDATE exchange_statistic_interval_counter AS usic SET
cumulative_number = cumulative_number + my_delta,
event_delimiter = LEAST(usic.event_delimiter,my_min_serial)
WHERE imeta_serial_id = my_meta
AND h_payto = in_h_payto
AND range=my_ranges[my_i+1];
IF NOT FOUND
THEN
INSERT INTO exchange_statistic_interval_counter
(imeta_serial_id
,h_payto
,range
,event_delimiter
,cumulative_number
) VALUES (
my_meta
,in_h_payto
,my_ranges[my_i+1]
,my_min_serial
,my_delta);
END IF;
ELSE
DELETE FROM exchange_statistic_counter_event
WHERE imeta_serial_id = my_meta
AND h_payto = in_h_payto
AND slot < my_time - my_range;
END IF;
END IF;
my_rval.range = my_range;
RETURN NEXT my_rval;
END IF;
END LOOP;
END $$;
COMMENT ON FUNCTION exchange_statistic_interval_number_get
IS 'Returns deposit statistic tracking deposited amounts over certain time intervals; we first trim the stored data to only track what is still in-range, and then return the remaining value for each range';
DROP FUNCTION IF EXISTS exchange_statistic_interval_amount_get;
CREATE OR REPLACE FUNCTION exchange_statistic_interval_amount_get (
IN in_slug TEXT,
IN in_h_payto BYTEA
)
RETURNS SETOF exchange_statistic_interval_amount_get_return_value
LANGUAGE plpgsql
AS $$
DECLARE
my_time INT8 DEFAULT ROUND(EXTRACT(epoch FROM CURRENT_TIMESTAMP(0)::TIMESTAMP) * 1000000)::INT8 / 1000 / 1000;
my_ranges INT8[];
my_range INT8;
my_delta_value INT8;
my_delta_frac INT8;
my_delta taler_amount;
my_meta INT8;
my_next_max_serial INT8;
my_rec RECORD;
my_irec RECORD;
my_jrec RECORD;
my_i INT;
my_min_serial INT8 DEFAULT NULL;
my_rval exchange_statistic_interval_amount_get_return_value;
BEGIN
SELECT imeta_serial_id
,ranges
,precisions
INTO my_rec
FROM exchange_statistic_interval_meta
WHERE slug=in_slug;
IF NOT FOUND
THEN
RETURN;
END IF;
my_meta = my_rec.imeta_serial_id;
my_ranges = my_rec.ranges;
my_rval.rvalue.val = 0;
my_rval.rvalue.frac = 0;
FOR my_i IN 1..COALESCE(array_length(my_ranges,1),0)
LOOP
my_range = my_ranges[my_i];
SELECT event_delimiter
,cumulative_value
INTO my_irec
FROM exchange_statistic_interval_amount
WHERE imeta_serial_id = my_meta
AND h_payto = in_h_payto
AND range = my_range;
IF FOUND
THEN
my_min_serial = my_irec.event_delimiter;
my_rval.rvalue.val = (my_rval.rvalue).val + (my_irec.cumulative_value).val + (my_irec.cumulative_value).frac / 100000000;
my_rval.rvalue.frac = (my_rval.rvalue).frac + (my_irec.cumulative_value).frac % 100000000;
IF (my_rval.rvalue).frac > 100000000
THEN
my_rval.rvalue.frac = (my_rval.rvalue).frac - 100000000;
my_rval.rvalue.val = (my_rval.rvalue).val + 1;
END IF;
SELECT SUM((esae.delta).val) AS value_sum
,SUM((esae.delta).frac) AS frac_sum
INTO my_jrec
FROM exchange_statistic_amount_event esae
WHERE imeta_serial_id = my_meta
AND h_payto = in_h_payto
AND slot < my_time - my_range
AND aevent_serial_id >= my_min_serial;
IF FOUND AND my_jrec.value_sum IS NOT NULL
THEN
my_delta_value = my_jrec.value_sum + my_jrec.frac_sum / 100000000;
my_delta_frac = my_jrec.frac_sum % 100000000;
my_rval.rvalue.val = (my_rval.rvalue).val - my_delta_value;
IF ((my_rval.rvalue).frac >= my_delta_frac)
THEN
my_rval.rvalue.frac = (my_rval.rvalue).frac - my_delta_frac;
ELSE
my_rval.rvalue.frac = 100000000 + (my_rval.rvalue).frac - my_delta_frac;
my_rval.rvalue.val = (my_rval.rvalue).val - 1;
END IF;
SELECT aevent_serial_id
INTO my_next_max_serial
FROM exchange_statistic_amount_event
WHERE imeta_serial_id = my_meta
AND h_payto = in_h_payto
AND slot >= my_time - my_range
AND aevent_serial_id >= my_min_serial
ORDER BY slot ASC
LIMIT 1;
IF FOUND
THEN
UPDATE exchange_statistic_interval_amount SET
cumulative_value.val = (cumulative_value).val - my_delta_value
- CASE
WHEN (cumulative_value).frac < my_delta_frac
THEN 1
ELSE 0
END,
cumulative_value.frac = (cumulative_value).frac - my_delta_frac
+ CASE
WHEN (cumulative_value).frac < my_delta_frac
THEN 100000000
ELSE 0
END,
event_delimiter = my_next_max_serial
WHERE imeta_serial_id = my_meta
AND h_payto = in_h_payto
AND range = my_range;
ELSE
DELETE FROM exchange_statistic_interval_amount
WHERE imeta_serial_id = my_meta
AND h_payto = in_h_payto
AND range = my_range;
END IF;
IF (my_i < array_length(my_ranges,1))
THEN
UPDATE exchange_statistic_interval_amount AS msia SET
cumulative_value.val = (cumulative_value).val + my_delta_value
+ CASE
WHEN (cumulative_value).frac + my_delta_frac > 100000000
THEN 1
ELSE 0
END,
cumulative_value.frac = (cumulative_value).frac + my_delta_value
- CASE
WHEN (cumulative_value).frac + my_delta_frac > 100000000
THEN 100000000
ELSE 0
END,
event_delimiter = LEAST (msia.event_delimiter,my_min_serial)
WHERE imeta_serial_id = my_meta
AND h_payto = in_h_payto
AND range=my_ranges[my_i+1];
IF NOT FOUND
THEN
my_delta.val = my_delta_value;
my_delta.frac = my_delta_frac;
INSERT INTO exchange_statistic_interval_amount
(imeta_serial_id
,h_payto
,event_delimiter
,range
,cumulative_value
) VALUES (
my_meta
,in_h_payto
,my_min_serial
,my_ranges[my_i+1]
,my_delta);
END IF;
ELSE
DELETE FROM exchange_statistic_amount_event
WHERE imeta_serial_id = my_meta
AND h_payto = in_h_payto
AND slot < my_time - my_range;
END IF;
END IF;
my_rval.range = my_range;
RETURN NEXT my_rval;
END IF;
END LOOP;
END $$;
COMMENT ON FUNCTION exchange_statistic_interval_amount_get
IS 'Returns deposit statistic tracking deposited amounts over certain time intervals; we first trim the stored data to only track what is still in-range, and then return the remaining value; multiple values are returned, one per range';
DROP PROCEDURE IF EXISTS exchange_statistic_counter_gc;
CREATE OR REPLACE PROCEDURE exchange_statistic_counter_gc ()
LANGUAGE plpgsql
AS $$
DECLARE
my_time INT8 DEFAULT ROUND(EXTRACT(epoch FROM CURRENT_TIMESTAMP(0)::TIMESTAMP) * 1000000)::INT8 / 1000 / 1000;
my_h_payto BYTEA;
my_rec RECORD;
my_sum RECORD;
my_meta INT8;
my_ranges INT8[];
my_precisions INT8[];
my_precision INT4;
my_i INT4;
min_slot INT8;
max_slot INT8;
end_slot INT8;
my_total INT8;
BEGIN
FOR my_h_payto IN
SELECT DISTINCT h_payto
FROM exchange_statistic_counter_event
LOOP
FOR my_rec IN
SELECT imeta_serial_id
,ranges
,precisions
,slug
FROM exchange_statistic_interval_meta
LOOP
PERFORM FROM exchange_statistic_interval_number_get (my_rec.slug, my_h_payto);
my_meta = my_rec.imeta_serial_id;
my_ranges = my_rec.ranges;
my_precisions = my_rec.precisions;
FOR my_i IN 1..COALESCE(array_length(my_ranges,1),0)
LOOP
my_precision = my_precisions[my_i];
IF 1 >= my_precision
THEN
CONTINUE;
END IF;
IF 1 = my_i
THEN
min_slot = 0;
ELSE
min_slot = my_ranges[my_i - 1];
END IF;
end_slot = my_ranges[my_i];
LOOP
EXIT WHEN min_slot >= end_slot;
max_slot = min_slot + my_precision;
SELECT SUM(delta) AS total,
COUNT(*) AS matches,
MIN(nevent_serial_id) AS rep_serial_id
INTO my_sum
FROM exchange_statistic_counter_event
WHERE h_payto=my_h_payto
AND imeta_serial_id=my_meta
AND slot >= my_time - max_slot
AND slot < my_time - min_slot;
IF FOUND AND my_sum.matches > 1
THEN
my_total = my_sum.total;
DELETE FROM exchange_statistic_counter_event
WHERE h_payto=my_h_payto
AND imeta_serial_id=my_meta
AND slot >= my_time - max_slot
AND slot < my_time - min_slot
AND nevent_serial_id > my_sum.rep_serial_id;
UPDATE exchange_statistic_counter_event SET
delta = my_total
WHERE imeta_serial_id = my_meta
AND h_payto = my_h_payto
AND nevent_serial_id = my_sum.rep_serial_id;
END IF;
min_slot = min_slot + my_precision;
END LOOP;
END LOOP;
DELETE FROM exchange_statistic_counter_event
WHERE h_payto=my_h_payto
AND imeta_serial_id=my_meta
AND slot < my_time - my_ranges[array_length(my_ranges,1)];
END LOOP;
END LOOP;
END $$;
COMMENT ON PROCEDURE exchange_statistic_counter_gc
IS 'Performs garbage collection and compaction of the exchange_statistic_counter_event table';
DROP PROCEDURE IF EXISTS exchange_statistic_amount_gc;
CREATE OR REPLACE PROCEDURE exchange_statistic_amount_gc ()
LANGUAGE plpgsql
AS $$
DECLARE
my_time INT8 DEFAULT ROUND(EXTRACT(epoch FROM CURRENT_TIMESTAMP(0)::TIMESTAMP) * 1000000)::INT8 / 1000 / 1000;
my_h_payto BYTEA;
my_rec RECORD;
my_sum RECORD;
my_meta INT8;
my_ranges INT8[];
my_precisions INT8[];
my_precision INT4;
my_i INT4;
min_slot INT8;
max_slot INT8;
end_slot INT8;
my_total_val INT8;
my_total_frac INT8;
BEGIN
FOR my_h_payto IN
SELECT DISTINCT h_payto
FROM exchange_statistic_counter_event
LOOP
FOR my_rec IN
SELECT imeta_serial_id
,ranges
,precisions
,slug
FROM exchange_statistic_interval_meta
LOOP
PERFORM FROM exchange_statistic_interval_amount_get (my_rec.slug, my_h_payto);
my_meta = my_rec.imeta_serial_id;
my_ranges = my_rec.ranges;
my_precisions = my_rec.precisions;
FOR my_i IN 1..COALESCE(array_length(my_ranges,1),0)
LOOP
my_precision = my_precisions[my_i];
IF 1 >= my_precision
THEN
CONTINUE;
END IF;
IF 1 = my_i
THEN
min_slot = 0;
ELSE
min_slot = my_ranges[my_i - 1];
END IF;
end_slot = my_ranges[my_i];
LOOP
EXIT WHEN min_slot >= end_slot;
max_slot = min_slot + my_precision;
SELECT SUM((delta).val) AS total_val,
SUM((delta).frac) AS total_frac,
COUNT(*) AS matches,
MIN(aevent_serial_id) AS rep_serial_id
INTO my_sum
FROM exchange_statistic_amount_event
WHERE imeta_serial_id=my_meta
AND h_payto=my_h_payto
AND slot >= my_time - max_slot
AND slot < my_time - max_slot;
IF FOUND AND my_sum.matches > 1
THEN
my_total_frac = my_sum.total_frac % 100000000;
my_total_val = my_sum.total_val + my_sum.total_frac / 100000000;
DELETE FROM exchange_statistic_amount_event
WHERE imeta_serial_id=my_meta
AND h_payto=my_h_payto
AND slot >= my_time - max_slot
AND slot < my_time - max_slot
AND aevent_serial_id > my_sum.rep_serial_id;
UPDATE exchange_statistic_amount_event SET
delta.val = my_total_value
,delta.frac = my_total_frac
WHERE imeta_serial_id = my_meta
AND h_payto = my_h_payto
AND aevent_serial_id = my_sum.rep_serial_id;
END IF;
min_slot = min_slot + my_precision;
END LOOP;
END LOOP;
DELETE FROM exchange_statistic_amount_event
WHERE h_payto=my_h_payto
AND imeta_serial_id=my_meta
AND slot < my_time - my_ranges[array_length(my_ranges,1)];
END LOOP;
END LOOP;
END $$;
COMMENT ON PROCEDURE exchange_statistic_amount_gc
IS 'Performs garbage collection and compaction of the exchange_statistic_amount_event table';
DROP PROCEDURE IF EXISTS exchange_statistic_bucket_gc;
CREATE OR REPLACE PROCEDURE exchange_statistic_bucket_gc ()
LANGUAGE plpgsql
AS $$
DECLARE
my_rec RECORD;
my_range TEXT;
my_now INT8;
my_end INT8;
BEGIN
my_now = EXTRACT(EPOCH FROM CURRENT_TIMESTAMP(0)::TIMESTAMP);
FOR my_rec IN
SELECT bmeta_serial_id
,stype
,ranges[array_length(ranges,1)] AS range
,ages[array_length(ages,1)] AS age
FROM exchange_statistic_bucket_meta
LOOP
my_range = '1 ' || my_rec.range::TEXT;
my_end = my_now - my_rec.age * EXTRACT(SECONDS FROM (SELECT my_range::INTERVAL));
IF my_rec.stype = 'amount'
THEN
DELETE
FROM exchange_statistic_bucket_amount
WHERE bmeta_serial_id = my_rec.bmeta_serial_id
AND bucket_start >= my_end;
ELSE
DELETE
FROM exchange_statistic_bucket_counter
WHERE bmeta_serial_id = my_rec.bmeta_serial_id
AND bucket_start >= my_end;
END IF;
END LOOP;
END $$;
COMMENT ON PROCEDURE exchange_statistic_bucket_gc
IS 'Performs garbage collection of the exchange_statistic_bucket_counter and exchange_statistic_bucket_amount tables';
DROP FUNCTION IF EXISTS exchange_drop_customization;
CREATE OR REPLACE FUNCTION exchange_drop_customization (
IN in_schema TEXT,
OUT out_found BOOLEAN
)
LANGUAGE plpgsql
AS $$
DECLARE
my_xpatches TEXT;
BEGIN
out_found = FALSE;
FOR my_xpatches IN
SELECT patch_name
FROM _v.patches
WHERE starts_with(patch_name, in_schema || '-')
LOOP
PERFORM _v.unregister_patch(my_xpatches);
out_found = TRUE;
END LOOP;
IF out_found
THEN
EXECUTE FORMAT('DROP SCHEMA %s CASCADE'
,in_schema);
END IF;
DELETE
FROM exchange_statistic_interval_meta
WHERE origin=in_schema;
DELETE
FROM exchange_statistic_bucket_meta
WHERE origin=in_schema;
END $$;
COMMENT ON FUNCTION exchange_drop_customization
IS 'Removes all entries related to a particular exchange customization schema';
CREATE OR REPLACE FUNCTION purse_requests_insert_trigger()
RETURNS trigger
LANGUAGE plpgsql
AS $$
BEGIN
INSERT INTO
exchange.purse_actions
(purse_pub
,action_date)
VALUES
(NEW.purse_pub
,NEW.purse_expiration);
RETURN NEW;
END $$;
COMMENT ON FUNCTION purse_requests_insert_trigger()
IS 'When a purse is created, insert it into the purse_action table to take action when the purse expires.';
CREATE OR REPLACE FUNCTION withdraw_delete_trigger()
RETURNS trigger
LANGUAGE plpgsql
AS $$
BEGIN
DELETE FROM exchange.unique_withdraw_blinding_seed
WHERE blinding_seed = OLD.blinding_seed;
RETURN OLD;
END $$;
COMMENT ON FUNCTION withdraw_delete_trigger()
IS 'Delete blinding_seed from unique_withdraw_blinding_seed table.';
CREATE OR REPLACE FUNCTION withdraw_insert_trigger()
RETURNS trigger
LANGUAGE plpgsql
AS $$
BEGIN
INSERT INTO exchange.reserve_history
(reserve_pub
,table_name
,serial_id
) VALUES (
NEW.reserve_pub
,'withdraw'
,NEW.withdraw_id
);
RETURN NEW;
END $$;
COMMENT ON FUNCTION withdraw_insert_trigger()
IS 'Replicate withdraw inserts into reserve_history table.';
CREATE OR REPLACE FUNCTION reserves_in_insert_trigger()
RETURNS trigger
LANGUAGE plpgsql
AS $$
BEGIN
INSERT INTO exchange.reserve_history
(reserve_pub
,table_name
,serial_id)
VALUES
(NEW.reserve_pub
,'reserves_in'
,NEW.reserve_in_serial_id);
RETURN NEW;
END $$;
COMMENT ON FUNCTION reserves_in_insert_trigger()
IS 'Automatically generate reserve history entry.';
CREATE OR REPLACE FUNCTION purse_decision_insert_trigger()
RETURNS trigger
LANGUAGE plpgsql
AS $$
BEGIN
UPDATE exchange.purse_requests
SET was_decided=TRUE
WHERE purse_pub=NEW.purse_pub;
IF NEW.refunded
THEN
INSERT INTO exchange.coin_history
(coin_pub
,table_name
,serial_id)
SELECT
pd.coin_pub
,'purse_decision'
,NEW.purse_decision_serial_id
FROM exchange.purse_deposits pd
WHERE purse_pub = NEW.purse_pub;
ELSE
INSERT INTO exchange.reserve_history
(reserve_pub
,table_name
,serial_id)
SELECT
reserve_pub
,'purse_decision'
,NEW.purse_decision_serial_id
FROM exchange.purse_merges
WHERE purse_pub=NEW.purse_pub;
END IF;
RETURN NEW;
END $$;
COMMENT ON FUNCTION purse_decision_insert_trigger()
IS 'Automatically generate coin history entry and update decision status for the purse.';
DROP FUNCTION IF EXISTS exchange_do_get_kyc_rules;
CREATE FUNCTION exchange_do_get_kyc_rules(
IN in_h_payto BYTEA,
IN in_now INT8,
IN in_merchant_pub BYTEA,
OUT out_target_pub BYTEA,
OUT out_reserve_pub BYTEA,
OUT out_jnew_rules JSONB
)
LANGUAGE plpgsql
AS $$
DECLARE
my_found BOOL;
BEGIN
IF in_merchant_pub IS NOT NULL
THEN
PERFORM FROM reserves_in
WHERE reserve_pub=in_merchant_pub
AND wire_source_h_payto IN
(SELECT wire_target_h_payto
FROM wire_targets
WHERE h_normalized_payto = in_h_payto);
my_found = FOUND;
ELSE
my_found = FALSE;
END IF;
IF FOUND
THEN
out_reserve_pub = in_merchant_pub;
ELSE
SELECT reserve_pub
INTO out_reserve_pub
FROM reserves_in
WHERE wire_source_h_payto IN
(SELECT wire_target_h_payto
FROM wire_targets
WHERE h_normalized_payto = in_h_payto)
ORDER BY execution_date DESC
LIMIT 1;
END IF;
SELECT target_pub
INTO out_target_pub
FROM kyc_targets
WHERE h_normalized_payto = in_h_payto;
SELECT jnew_rules
INTO out_jnew_rules
FROM legitimization_outcomes
WHERE h_payto = in_h_payto
AND COALESCE(expiration_time >= $2, TRUE)
AND COALESCE(is_active, TRUE)
ORDER BY expiration_time DESC
LIMIT 1;
END $$;
DROP PROCEDURE IF EXISTS exchange_do_gc;
CREATE PROCEDURE exchange_do_gc(
IN in_ancient_date INT8,
IN in_now INT8)
LANGUAGE plpgsql
AS $$
BEGIN
CALL exchange_do_main_gc(in_ancient_date,in_now);
CALL exchange_statistic_amount_gc ();
CALL exchange_statistic_bucket_gc ();
CALL exchange_statistic_counter_gc ();
END $$;
COMMENT ON PROCEDURE exchange_do_gc
IS 'calls all other garbage collection subroutines';
COMMIT;