diff --git a/src/exchangedb/withdraw.sql b/src/exchangedb/withdraw.sql new file mode 100644 index 000000000..0ac9a73f0 --- /dev/null +++ b/src/exchangedb/withdraw.sql @@ -0,0 +1,130 @@ +CREATE OR REPLACE FUNCTION exchange_do_withdraw( + IN amount_val INT8, + IN amount_frac INT4, + IN h_denom_pub BYTEA, + IN rpub BYTEA, + IN reserve_sig BYTEA, + IN h_coin_envelope BYTEA, + IN denom_sig BYTEA, + IN now INT8, + IN min_reserve_gc INT8, + OUT reserve_found BOOLEAN, + OUT balance_ok BOOLEAN, + OUT kycok BOOLEAN, + OUT ruuid INT8, + OUT account_uuid INT8) +LANGUAGE plpgsql +AS $$ +DECLARE + reserve_gc INT8; +DECLARE + denom_serial INT8; +DECLARE + reserve_val INT8; +DECLARE + reserve_frac INT4; +BEGIN + + +SELECT denominations_serial INTO denom_serial + FROM denominations + WHERE denom_pub_hash=h_denom_pub; + +IF NOT FOUND +THEN + -- denomination unknown, should be impossible! + reserve_found=FALSE; + balance_ok=FALSE; + kycok=FALSE; + ruuid=0; + account_uuid=0; + ASSERT false, 'denomination unknown'; + RETURN; +END IF; + + +UPDATE reserves SET + gc_date=GREATEST(gc_date, min_reserve_gc) + ,current_balance_val=current_balance_val - amount_val + - CASE WHEN (current_balance_frac < amount_frac) + THEN 1 + ELSE 0 + END + ,current_balance_frac=current_balance_frac - amount_frac + + CASE WHEN (current_balance_frac < amount_frac) + THEN 100000000 + ELSE 0 + END + WHERE reserves.reserve_pub=rpub + AND ( (current_balance_val > amount_val) OR + ( (current_balance_val = amount_val) AND + (current_balance_frac >= amount_frac) ) ); + +balance_ok=FOUND; + +-- Obtain KYC status based on the last wire transfer into +-- this reserve. FIXME: likely not adequate for reserves that got P2P transfers! +SELECT + kyc_ok + ,wire_source_serial_id + ,reserve_uuid + INTO + kycok + ,account_uuid + ,ruuid + FROM reserves + JOIN reserves_in USING (reserve_uuid) + JOIN wire_targets ON (wire_source_serial_id = wire_target_serial_id) + WHERE reserves.reserve_pub=rpub + LIMIT 1; -- limit 1 should not be required (without p2p transfers) + +IF NOT FOUND +THEN + -- reserve unknown + reserve_found=FALSE; + balance_ok=FALSE; + kycok=FALSE; + account_uuid=0; + RETURN; +END IF; + +reserve_found=TRUE; + + +-- We optimistically insert, and then on conflict declare +-- the query successful due to idempotency. +INSERT INTO reserves_out + (h_blind_ev + ,denominations_serial + ,denom_sig + ,reserve_uuid + ,reserve_sig + ,execution_date + ,amount_with_fee_val + ,amount_with_fee_frac) +VALUES + (h_coin_envelope + ,denom_serial + ,denom_sig + ,ruuid + ,reserve_sig + ,now + ,amount_val + ,amount_frac) +ON CONFLICT DO NOTHING; + +IF NOT FOUND +THEN + -- idempotent query, all constraints must be satisfied + balance_ok=TRUE; + -- rollback any potential balance update we may have made + ROLLBACK; + START TRANSACTION ISOLATION LEVEL SERIALIZABLE; + RETURN; +END IF; + +END $$; + +COMMENT ON FUNCTION exchange_do_withdraw(INT8, INT4, BYTEA, BYTEA, BYTEA, BYTEA, BYTEA, INT8, INT8) + IS 'Checks whether the reserve has sufficient balance for a withdraw operation (or the request is repeated and was previously approved) and if so updates the database with the result'; +