diff --git a/src/exchangedb/melt.sql b/src/exchangedb/melt.sql index af1aa8d4a..fc6d24d45 100644 --- a/src/exchangedb/melt.sql +++ b/src/exchangedb/melt.sql @@ -5,20 +5,15 @@ -- Check patch versioning is in place. -- SELECT _v.register_patch('exchange-000x', NULL, NULL); -CREATE OR REPLACE FUNCTION exchange_do_melt( + +CREATE OR REPLACE FUNCTION exchange_check_coin_balance( IN denom_val INT8, -- value of the denomination of the coin IN denom_frac INT4, -- value of the denomination of the coin - IN amount_val INT8, -- requested melt amount (with fee) - IN amount_frac INT4, -- requested melt amount (with fee) - IN in_rc BYTEA, -- refresh session hash IN in_coin_pub BYTEA, -- coin public key - IN coin_sig BYTEA, -- melt signature - IN in_noreveal_index INT4, -- suggested random noreveal index + IN check_recoup BOOLEAN, -- do we need to check the recoup table? IN zombie_required BOOLEAN, -- do we need a zombie coin? - OUT out_noreval_index INT4, -- noreveal index to actually use OUT balance_ok BOOLEAN, -- balance satisfied? - OUT zombie_ok BOOLEAN, -- zombie satisfied? - OUT melt_ok BOOLEAN) -- everything OK? + OUT zombie_ok BOOLEAN) -- zombie satisfied? LANGUAGE plpgsql AS $$ DECLARE @@ -37,6 +32,9 @@ DECLARE unspent_frac INT8; -- how much of coin was refunded? BEGIN +-- Note: possible future optimization: get the coin_uuid from the previous +-- 'ensure_coin_known' and pass that here instead of the coin_pub. Might help +-- a tiny bit with performance. SELECT known_coin_id INTO coin_uuid FROM known_coins WHERE coin_pub=in_coin_pub; @@ -44,49 +42,17 @@ SELECT known_coin_id INTO coin_uuid IF NOT FOUND THEN -- coin unknown, should be impossible! - out_noreveal_index=-1; balance_ok=FALSE; zombie_ok=FALSE; - melt_ok=FALSE; ASSERT false, 'coin unknown'; RETURN; END IF; --- We optimistically insert, and then on conflict declare --- the query successful due to idempotency. -INSERT INTO refresh_commitments - (rc - ,old_known_coin_id - ,old_coin_sig - ,amount_with_fee_val - ,amount_with_fee_frac - ,noreveal_index) -VALUES - (in_rc - ,coin_uuid - ,coin_sig - ,amount_val - ,amount_frac - ,in_noreveal_index) -ON CONFLICT DO NOTHING; -IF FOUND -THEN - -- already melted, get noreveal_index - SELECT noreveal_index INTO out_noreveal_index - FROM refresh_commitments - WHERE rc=in_rc ; - balance_ok=TRUE; - zombie_ok=TRUE; - melt_ok=TRUE; - RETURN; -END IF; - --- Need to check for sufficient balance... spent_val = 0; spent_frac = 0; -unspent_val = 0; -unspent_frac = 0; +unspent_val = denom_val; +unspent_frac = denom_frac; SELECT SUM(amount_with_fee_val) -- overflow here is not plausible @@ -124,156 +90,81 @@ SELECT unspent_val = unspent_val + tmp_val; unspent_frac = unspent_frac + tmp_frac; -SELECT - SUM(amount_val) -- overflow here is not plausible - ,SUM(CAST(amount_frac AS INT8)) -- compute using 64 bits - INTO - tmp_val - ,tmp_frac - FROM recoup_refresh - WHERE known_coin_id=coin_uuid; - -unspent_val = unspent_val + tmp_val; -unspent_frac = unspent_frac + tmp_frac; - -SELECT - SUM(amount_val) -- overflow here is not plausible - ,SUM(CAST(amount_frac AS INT8)) -- compute using 64 bits - INTO - tmp_val - ,tmp_frac - FROM recoup - WHERE known_coin_id=coin_uuid; - -spent_val = spent_val + tmp_val; -spent_frac = spent_frac + tmp_frac; - -SELECT - SUM(amount_val) -- overflow here is not plausible - ,SUM(CAST(amount_frac AS INT8)) -- compute using 64 bits - INTO - tmp_val - ,tmp_frac - FROM recoup_refresh - JOIN refresh_revealed_coins rrc - USING (rrc_serial) - JOIN refresh_commitments rfc - ON (rrc.melt_serial_id = rfc.melt_serial_id) - WHERE rfc.old_known_coin_id=coin_uuid; - -spent_val = spent_val + tmp_val; -spent_frac = spent_frac + tmp_frac; - - -------------------- TBD from here - -SELECT - reserve_uuid - ,current_balance_val - ,current_balance_frac_uuid - ,expiration_date - ,gc_date - INTO - reserve_uuid - ,reserve_val - ,reserve_frac - ,reserve_gc - FROM reserves - WHERE reserve_pub=reserve_pub; - -IF NOT FOUND +-- Note: even if 'check_recoup' is true, the tables below +-- are in practice likely empty (as they only apply if +-- the exchange (ever) had to revoke keys). +IF check_recoup THEN - -- reserve unknown - reserve_found=FALSE; - balance_ok=FALSE; - kyc_ok=FALSE; - RETURN; -END IF; --- We optimistically insert, and then on conflict declare --- the query successful due to idempotency. -INSERT INTO reserves_out - (h_blind_ev - ,denom_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 - ,reserve_uuid - ,reserve_sig - ,now - ,amount_val - ,amount_frac) -ON CONFLICT DO NOTHING; + SELECT + SUM(amount_val) -- overflow here is not plausible + ,SUM(CAST(amount_frac AS INT8)) -- compute using 64 bits + INTO + tmp_val + ,tmp_frac + FROM recoup_refresh + WHERE known_coin_id=coin_uuid; -IF NOT FOUND -THEN - -- idempotent query, all constraints must be satisfied - reserve_found=TRUE; - balance_ok=TRUE; - kyc_ok=TRUE; - RETURN; -END IF; + unspent_val = unspent_val + tmp_val; + unspent_frac = unspent_frac + tmp_frac; --- Check reserve balance is sufficient. -IF (reserve_val > amount_val) -THEN - IF (reserve_frac > amount_frac) + SELECT + SUM(amount_val) -- overflow here is not plausible + ,SUM(CAST(amount_frac AS INT8)) -- compute using 64 bits + INTO + tmp_val + ,tmp_frac + FROM recoup + WHERE known_coin_id=coin_uuid; + + spent_val = spent_val + tmp_val; + spent_frac = spent_frac + tmp_frac; + + SELECT + SUM(amount_val) -- overflow here is not plausible + ,SUM(CAST(amount_frac AS INT8)) -- compute using 64 bits + INTO + tmp_val + ,tmp_frac + FROM recoup_refresh + JOIN refresh_revealed_coins rrc + USING (rrc_serial) + JOIN refresh_commitments rfc + ON (rrc.melt_serial_id = rfc.melt_serial_id) + WHERE rfc.old_known_coin_id=coin_uuid; + + spent_val = spent_val + tmp_val; + spent_frac = spent_frac + tmp_frac; + + IF ( (0 < tmp_val) OR (0 < tmp_frac) ) THEN - reserve_val=reserve_val - amount_val; - reserve_frac=reserve_frac - amount_frac; - ELSE - reserve_val=reserve_val - amount_val - 1; - reserve_frac=reserve_frac + 100000000 - amount_frac; + -- There was a transaction that justifies the zombie + -- status, clear the flag + zombie_required=FALSE; END IF; + +END IF; + + +-- Actually check if the coin balance is sufficient. Verbosely. ;-) +IF (unspent_val > spent_val) +THEN + balance_ok=TRUE; ELSE IF (reserve_val == amount_val) AND (reserve_frac >= amount_frac) THEN - reserve_val=0; - reserve_frac=reserve_frac - amount_frac; + balance_ok=TRUE; ELSE - reserve_found=TRUE; balance_ok=FALSE; - kyc_ok=FALSE; -- we do not really know or care - RETURN; END IF; END IF; --- Calculate new expiration dates. -min_reserve_gc=MAX(min_reserve_gc,reserve_gc); - --- Update reserve balance. -UPDATE reserves SET - gc_date=min_reserve_gc - ,current_balance_val=reserve_val - ,current_balance_frac=reserve_frac -WHERE - reserve_uuid=reserve_uuid; - -reserve_found=TRUE; -balance_ok=TRUE; - --- Obtain KYC status based on the last wire transfer into --- this reserve. FIXME: likely not adequate for reserves that got P2P transfers! -SELECT kyc_ok - INTO kyc_ok - FROM reserves_in - JOIN wire_targets USING (wire_target_serial_id) - WHERE reserve_uuid=reserve_uuid - LIMIT 1; -- limit 1 should not be required (without p2p transfers) - - +zombie_ok = NOT zombie_required; END $$; -COMMENT ON FUNCTION exchange_do_melt(INT8, INT4, BYTEA, BYTEA, BYTEA, BYTEA, BYTEA, INT8, INT8) - IS 'Checks whether the coin has sufficient balance for a melt operation (or the request is repeated and was previously approved) and if so updates the database with the result'; +COMMENT ON FUNCTION exchange_check_coin_balance(INT8, INT4, BYTEA, BOOLEAN, BOOLEAN) + IS 'Checks whether the coin has sufficient balance for all the operations associated with it'; -- Complete transaction