diff options
author | Christian Grothoff <christian@grothoff.org> | 2022-01-11 12:47:35 +0100 |
---|---|---|
committer | Christian Grothoff <christian@grothoff.org> | 2022-01-11 12:47:35 +0100 |
commit | e7aeec04f4eb52caaa61b1ff9362f6fe0ffe0f2d (patch) | |
tree | 7e788144cff60c41a07e2bc136e86ddadf610274 /src/exchangedb | |
parent | aaaaa9a103628d7694a4fb3bac6335501187cc00 (diff) | |
download | exchange-e7aeec04f4eb52caaa61b1ff9362f6fe0ffe0f2d.tar.xz |
The current recoup API is broken. I guess this is another example where "trivial" API changes turn out to have (multiple!) unexpected consequences.
The current "/recoup" API does not have clear idempotency semantics, as we've discussed on the phone. This is already bad by itself, as it makes it hard to write down what the API does other than "whatever the implementation does".
However, it actually breaks correctness in this (admittedly kinda contrived, but not impossible) case:
Say that we have a coin A obtained via withdrawal and a coin B obtained via refreshing coin A. Now the denominations of A gets revoked..
The wallet does a recoup of A for EUR:1.
Now the denomination of B also gets revoked. The wallet recoups B (incidentally also for EUR:1) and now A can be recouped again for EUR:1. But now the exchange is in a state where it will refuse a legitimate recoup request for A because the detection for an idempotent request kicks in.
This is IMHO bad API design, and the exchange should simply always recoup the maximum amount.
Furthermore, we usually follow the principle of "API calls that take up DB space are paid". With the current recoup API, I can do many tiny recoup requests which the exchange then has to store, right?
I guess it would not be a big change to remove the "amount" value from the recoup/recoup-refresh request bodies, right?
- Florian
Diffstat (limited to 'src/exchangedb')
-rw-r--r-- | src/exchangedb/exchange-0001.sql | 172 | ||||
-rw-r--r-- | src/exchangedb/plugin_exchangedb_postgres.c | 14 | ||||
-rw-r--r-- | src/exchangedb/test_exchangedb.c | 4 |
3 files changed, 81 insertions, 109 deletions
diff --git a/src/exchangedb/exchange-0001.sql b/src/exchangedb/exchange-0001.sql index c1665b3ba..51fd26eca 100644 --- a/src/exchangedb/exchange-0001.sql +++ b/src/exchangedb/exchange-0001.sql @@ -1508,8 +1508,6 @@ END $$; CREATE OR REPLACE FUNCTION exchange_do_recoup_to_reserve( IN in_reserve_pub BYTEA, IN in_reserve_out_serial_id INT8, - IN in_amount_val INT8, - IN in_amount_frac INT4, IN in_coin_blind BYTEA, IN in_coin_pub BYTEA, IN in_known_coin_id INT8, @@ -1523,76 +1521,71 @@ CREATE OR REPLACE FUNCTION exchange_do_recoup_to_reserve( LANGUAGE plpgsql AS $$ DECLARE - tmp_val INT8; -- previous amount recouped + tmp_val INT8; -- amount recouped DECLARE - tmp_frac INT8; -- previous amount recouped + tmp_frac INT8; -- amount recouped BEGIN -- Shards: SELECT known_coins (by coin_pub) -- SELECT recoup (by known_coin_id) +-- UPDATE known_coins (by coin_pub) -- UPDATE reserves (by reserve_pub) -- INSERT recoup (by known_coin_id) out_internal_failure=FALSE; --- Check and update balance of the coin. -UPDATE known_coins - SET - remaining_frac=remaining_frac-in_amount_frac - + CASE - WHEN remaining_frac < in_amount_frac - THEN 100000000 - ELSE 0 - END, - remaining_val=remaining_val-in_amount_val - - CASE - WHEN remaining_frac < in_amount_frac - THEN 1 - ELSE 0 - END - WHERE coin_pub=in_coin_pub - AND ( (remaining_val > in_amount_val) OR - ( (remaining_frac >= in_amount_frac) AND - (remaining_val >= in_amount_val) ) ); + +-- Check remaining balance of the coin. +SELECT + remaining_frac + ,remaining_val + INTO + tmp_frac + ,tmp_val +FROM known_coins + WHERE coin_pub=in_coin_pub; IF NOT FOUND THEN - -- Check if we already recouped this coin before! + out_internal_failure=TRUE; + out_recoup_ok=FALSE; + RETURN; +END IF; + +IF tmp_val + tmp_frac = 0 +THEN + -- Check for idempotency SELECT - amount_val - ,amount_frac - ,recoup_timestamp - INTO - tmp_val - ,tmp_frac - ,out_recoup_timestamp + recoup_timestamp + INTO + out_recoup_timestamp FROM recoup WHERE known_coin_id=in_known_coin_id; - IF FOUND - THEN - -- Idempotent request, all OK! - out_recoup_ok= (tmp_val = in_amount_val) AND - (tmp_frac = in_amount_frac); - RETURN; - END IF; - - out_recoup_ok=FALSE; + out_recoup_ok=FOUND; RETURN; END IF; +-- Update balance of the coin. +UPDATE known_coins + SET + remaining_frac=0 + ,remaining_val=0 + WHERE coin_pub=in_coin_pub; + + -- Credit the reserve and update reserve timers. UPDATE reserves SET - current_balance_frac=current_balance_frac+in_amount_frac + current_balance_frac=current_balance_frac+tmp_frac - CASE - WHEN current_balance_frac+in_amount_frac >= 100000000 + WHEN current_balance_frac+tmp_frac >= 100000000 THEN 100000000 ELSE 0 END, - current_balance_val=current_balance_val+in_amount_val + current_balance_val=current_balance_val+tmp_val + CASE - WHEN current_balance_frac+in_amount_frac >= 100000000 + WHEN current_balance_frac+tmp_frac >= 100000000 THEN 1 ELSE 0 END, @@ -1604,7 +1597,7 @@ UPDATE reserves IF NOT FOUND THEN RAISE NOTICE 'failed to increase reserve balance from recoup'; - out_recoup_ok=FALSE; + out_recoup_ok=TRUE; out_internal_failure=TRUE; RETURN; END IF; @@ -1623,8 +1616,8 @@ VALUES (in_known_coin_id ,in_coin_sig ,in_coin_blind - ,in_amount_val - ,in_amount_frac + ,tmp_val + ,tmp_frac ,in_recoup_timestamp ,in_reserve_out_serial_id); @@ -1645,8 +1638,6 @@ END $$; CREATE OR REPLACE FUNCTION exchange_do_recoup_to_coin( IN in_old_coin_pub BYTEA, IN in_rrc_serial INT8, - IN in_amount_val INT8, - IN in_amount_frac INT4, IN in_coin_blind BYTEA, IN in_coin_pub BYTEA, IN in_known_coin_id INT8, @@ -1658,9 +1649,9 @@ CREATE OR REPLACE FUNCTION exchange_do_recoup_to_coin( LANGUAGE plpgsql AS $$ DECLARE - tmp_val INT8; -- previous amount recouped + tmp_val INT8; -- amount recouped DECLARE - tmp_frac INT8; -- previous amount recouped + tmp_frac INT8; -- amount recouped BEGIN -- Shards: UPDATE known_coins (by coin_pub) @@ -1671,66 +1662,57 @@ BEGIN out_internal_failure=FALSE; --- Check and update balance of the coin. -UPDATE known_coins - SET - remaining_frac=remaining_frac-in_amount_frac - + CASE - WHEN remaining_frac < in_amount_frac - THEN 100000000 - ELSE 0 - END, - remaining_val=remaining_val-in_amount_val - - CASE - WHEN remaining_frac < in_amount_frac - THEN 1 - ELSE 0 - END - WHERE coin_pub=in_coin_pub - AND ( (remaining_val > in_amount_val) OR - ( (remaining_frac >= in_amount_frac) AND - (remaining_val >= in_amount_val) ) ); + +-- Check remaining balance of the coin. +SELECT + remaining_frac + ,remaining_val + INTO + tmp_frac + ,tmp_val +FROM known_coins + WHERE coin_pub=in_coin_pub; IF NOT FOUND THEN - -- Check if we already recouped this coin before! + out_internal_failure=TRUE; + out_recoup_ok=FALSE; + RETURN; +END IF; + +IF tmp_val + tmp_frac = 0 +THEN + -- Check for idempotency SELECT - amount_val - ,amount_frac - ,recoup_timestamp + recoup_timestamp INTO - tmp_val - ,tmp_frac - ,out_recoup_timestamp + out_recoup_timestamp FROM recoup_refresh WHERE known_coin_id=in_known_coin_id; - - IF FOUND - THEN - -- Idempotent request, all OK! - out_recoup_ok= (tmp_val = in_amount_val) AND - (tmp_frac = in_amount_frac); - RETURN; - END IF; - - -- Insufficient balance, not idempotent. - out_recoup_ok=FALSE; + out_recoup_ok=FOUND; RETURN; END IF; +-- Update balance of the coin. +UPDATE known_coins + SET + remaining_frac=0 + ,remaining_val=0 + WHERE coin_pub=in_coin_pub; + -- Credit the old coin. UPDATE known_coins SET - remaining_frac=remaining_frac+in_amount_frac + remaining_frac=remaining_frac+tmp_frac - CASE - WHEN remaining_frac+in_amount_frac >= 100000000 + WHEN remaining_frac+tmp_frac >= 100000000 THEN 100000000 ELSE 0 END, - remaining_val=remaining_val+in_amount_val + remaining_val=remaining_val+tmp_val + CASE - WHEN remaining_frac+in_amount_frac >= 100000000 + WHEN remaining_frac+tmp_frac >= 100000000 THEN 1 ELSE 0 END @@ -1740,7 +1722,7 @@ UPDATE known_coins IF NOT FOUND THEN RAISE NOTICE 'failed to increase old coin balance from recoup'; - out_recoup_ok=FALSE; + out_recoup_ok=TRUE; out_internal_failure=TRUE; RETURN; END IF; @@ -1759,8 +1741,8 @@ VALUES (in_known_coin_id ,in_coin_sig ,in_coin_blind - ,in_amount_val - ,in_amount_frac + ,tmp_val + ,tmp_frac ,in_recoup_timestamp ,in_rrc_serial); diff --git a/src/exchangedb/plugin_exchangedb_postgres.c b/src/exchangedb/plugin_exchangedb_postgres.c index fc90f37a6..a0bc14d52 100644 --- a/src/exchangedb/plugin_exchangedb_postgres.c +++ b/src/exchangedb/plugin_exchangedb_postgres.c @@ -636,8 +636,8 @@ prepare_statements (struct PostgresClosure *pg) ",out_recoup_ok AS recoup_ok" ",out_internal_failure AS internal_failure" " FROM exchange_do_recoup_to_reserve" - " ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11);", - 11), + " ($1,$2,$3,$4,$5,$6,$7,$8,$9);", + 9), /* Used in #postgres_do_recoup_refresh() to recoup a coin to a zombie coin. */ GNUNET_PQ_make_prepare ( "call_recoup_refresh", @@ -646,8 +646,8 @@ prepare_statements (struct PostgresClosure *pg) ",out_recoup_ok AS recoup_ok" ",out_internal_failure AS internal_failure" " FROM exchange_do_recoup_to_coin" - " ($1,$2,$3,$4,$5,$6,$7,$8,$9);", - 9), + " ($1,$2,$3,$4,$5,$6,$7);", + 7), /* Used in #postgres_get_withdraw_info() to locate the response for a /reserve/withdraw request using the hash of the blinded message. Used to @@ -4674,7 +4674,6 @@ postgres_do_refund ( * @param cls the `struct PostgresClosure` with the plugin-specific state * @param reserve_pub public key of the reserve to credit * @param reserve_out_serial_id row in the reserves_out table justifying the recoup - * @param requested_amount the amount to be recouped * @param coin_bks coin blinding key secret to persist * @param coin_pub public key of the coin being recouped * @param known_coin_id row of the @a coin_pub in the known_coins table @@ -4689,7 +4688,6 @@ postgres_do_recoup ( void *cls, const struct TALER_ReservePublicKeyP *reserve_pub, uint64_t reserve_out_serial_id, - const struct TALER_Amount *requested_amount, const union TALER_DenominationBlindingKeyP *coin_bks, const struct TALER_CoinSpendPublicKeyP *coin_pub, uint64_t known_coin_id, @@ -4706,7 +4704,6 @@ postgres_do_recoup ( struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_auto_from_type (reserve_pub), GNUNET_PQ_query_param_uint64 (&reserve_out_serial_id), - TALER_PQ_query_param_amount (requested_amount), GNUNET_PQ_query_param_auto_from_type (coin_bks), GNUNET_PQ_query_param_auto_from_type (coin_pub), GNUNET_PQ_query_param_uint64 (&known_coin_id), @@ -4743,7 +4740,6 @@ postgres_do_recoup ( * @param cls the `struct PostgresClosure` with the plugin-specific state * @param old_coin_pub public key of the old coin to credit * @param rrc_serial row in the refresh_revealed_coins table justifying the recoup-refresh - * @param requested_amount the amount to be recouped * @param coin_bks coin blinding key secret to persist * @param coin_pub public key of the coin being recouped * @param known_coin_id row of the @a coin_pub in the known_coins table @@ -4758,7 +4754,6 @@ postgres_do_recoup_refresh ( void *cls, const struct TALER_CoinSpendPublicKeyP *old_coin_pub, uint64_t rrc_serial, - const struct TALER_Amount *requested_amount, const union TALER_DenominationBlindingKeyP *coin_bks, const struct TALER_CoinSpendPublicKeyP *coin_pub, uint64_t known_coin_id, @@ -4771,7 +4766,6 @@ postgres_do_recoup_refresh ( struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_auto_from_type (old_coin_pub), GNUNET_PQ_query_param_uint64 (&rrc_serial), - TALER_PQ_query_param_amount (requested_amount), GNUNET_PQ_query_param_auto_from_type (coin_bks), GNUNET_PQ_query_param_auto_from_type (coin_pub), GNUNET_PQ_query_param_uint64 (&known_coin_id), diff --git a/src/exchangedb/test_exchangedb.c b/src/exchangedb/test_exchangedb.c index a0c648b2d..6724e7b42 100644 --- a/src/exchangedb/test_exchangedb.c +++ b/src/exchangedb/test_exchangedb.c @@ -1766,7 +1766,6 @@ run (void *cls) plugin->do_recoup_refresh (plugin->cls, &deposit.coin.coin_pub, rrc_serial, - &value, &coin_bks, &new_coin.coin_pub, new_known_coin_id, @@ -1786,7 +1785,6 @@ run (void *cls) struct TALER_EXCHANGEDB_KycStatus kyc; bool recoup_ok; bool internal_failure; - struct TALER_Amount requested_amount; struct GNUNET_TIME_Timestamp recoup_timestamp = GNUNET_TIME_timestamp_get (); @@ -1795,13 +1793,11 @@ run (void *cls) plugin->reserves_get (plugin->cls, &pre_reserve, &kyc)); - requested_amount = value; FAILIF (! TALER_amount_is_zero (&pre_reserve.balance)); FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != plugin->do_recoup (plugin->cls, &reserve_pub, reserve_out_serial_id, - &requested_amount, &coin_blind, &deposit.coin.coin_pub, known_coin_id, |