diff --git a/src/exchange/taler-exchange-httpd.c b/src/exchange/taler-exchange-httpd.c index b948c315c..6bda5821b 100644 --- a/src/exchange/taler-exchange-httpd.c +++ b/src/exchange/taler-exchange-httpd.c @@ -1866,8 +1866,8 @@ run (void *cls, MHD_OPTION_CONNECTION_TIMEOUT, connection_timeout, (0 == allow_address_reuse) - ? MHD_OPTION_END - : MHD_OPTION_LISTENING_ADDRESS_REUSE, + ? MHD_OPTION_END + : MHD_OPTION_LISTENING_ADDRESS_REUSE, (unsigned int) allow_address_reuse, MHD_OPTION_END); if (NULL == mhd) diff --git a/src/exchange/taler-exchange-httpd_db.c b/src/exchange/taler-exchange-httpd_db.c index 1d78fb8e4..fb876f92b 100644 --- a/src/exchange/taler-exchange-httpd_db.c +++ b/src/exchange/taler-exchange-httpd_db.c @@ -95,6 +95,9 @@ TEH_make_coin_known (const struct TALER_CoinPublicInfo *coin, NULL); return qs; } + // FIXME: why do we even return the transaction + // history here!? This is a coin with multiple + // associated denominations, after all... *mhd_ret = TEH_RESPONSE_reply_coin_insufficient_funds ( connection, diff --git a/src/exchange/taler-exchange-httpd_deposit.c b/src/exchange/taler-exchange-httpd_deposit.c index e9851de79..6b651f40e 100644 --- a/src/exchange/taler-exchange-httpd_deposit.c +++ b/src/exchange/taler-exchange-httpd_deposit.c @@ -139,11 +139,12 @@ struct DepositContext /** - * Check if /deposit is already in the database. IF it returns a non-error - * code, the transaction logic MUST NOT queue a MHD response. IF it returns - * an hard error, the transaction logic MUST queue a MHD response and set @a - * mhd_ret. We do return a "hard" error also if we found the deposit in the - * database and generated a regular response. + * Execute database transaction for /deposit. Runs the transaction + * logic; IF it returns a non-error code, the transaction logic MUST + * NOT queue a MHD response. IF it returns an hard error, the + * transaction logic MUST queue a MHD response and set @a mhd_ret. IF + * it returns the soft error code, the function MAY be called again to + * retry and MUST not queue a MHD response. * * @param cls a `struct DepositContext` * @param connection MHD request context @@ -151,15 +152,24 @@ struct DepositContext * @return transaction status */ static enum GNUNET_DB_QueryStatus -deposit_precheck (void *cls, - struct MHD_Connection *connection, - MHD_RESULT *mhd_ret) +deposit_transaction (void *cls, + struct MHD_Connection *connection, + MHD_RESULT *mhd_ret) { struct DepositContext *dc = cls; const struct TALER_EXCHANGEDB_Deposit *deposit = dc->deposit; - struct TALER_Amount deposit_fee; + struct TALER_Amount spent; enum GNUNET_DB_QueryStatus qs; + struct TALER_Amount deposit_fee; + /* make sure coin is 'known' in database */ + qs = TEH_make_coin_known (&deposit->coin, + connection, + mhd_ret); + if (qs < 0) + return qs; + + /* Check for idempotency: did we get this request before? */ qs = TEH_plugin->have_deposit (TEH_plugin->cls, deposit, &deposit_fee, @@ -196,51 +206,8 @@ deposit_precheck (void *cls, deposit->wire_deadline, &deposit->merchant_pub, &amount_without_fee); - /* Treat as 'hard' DB error as we want to rollback and - never try again. */ return GNUNET_DB_STATUS_HARD_ERROR; } - return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS; -} - - -/** - * Execute database transaction for /deposit. Runs the transaction - * logic; IF it returns a non-error code, the transaction logic MUST - * NOT queue a MHD response. IF it returns an hard error, the - * transaction logic MUST queue a MHD response and set @a mhd_ret. IF - * it returns the soft error code, the function MAY be called again to - * retry and MUST not queue a MHD response. - * - * @param cls a `struct DepositContext` - * @param connection MHD request context - * @param[out] mhd_ret set to MHD status on error - * @return transaction status - */ -static enum GNUNET_DB_QueryStatus -deposit_transaction (void *cls, - struct MHD_Connection *connection, - MHD_RESULT *mhd_ret) -{ - struct DepositContext *dc = cls; - const struct TALER_EXCHANGEDB_Deposit *deposit = dc->deposit; - struct TALER_Amount spent; - enum GNUNET_DB_QueryStatus qs; - - /* make sure coin is 'known' in database */ - qs = TEH_make_coin_known (&deposit->coin, - connection, - mhd_ret); - if (qs < 0) - return qs; - /* Theoretically, someone other threat may have received - and committed the deposit in the meantime. Check now - that we are in the transaction scope. */ - qs = deposit_precheck (cls, - connection, - mhd_ret); - if (qs < 0) - return qs; /* Start with fee for THIS transaction */ spent = deposit->amount_with_fee; @@ -412,22 +379,7 @@ TEH_handler_deposit (struct MHD_Connection *connection, TALER_merchant_wire_signature_hash (dc.payto_uri, &deposit.wire_salt, &dc.h_wire); - /* Check for idempotency: did we get this request before? */ dc.deposit = &deposit; - { - MHD_RESULT mhd_ret; - - if (GNUNET_OK != - TEH_DB_run_transaction (connection, - "precheck deposit", - &mhd_ret, - &deposit_precheck, - &dc)) - { - GNUNET_JSON_parse_free (spec); - return mhd_ret; - } - } /* new deposit */ dc.exchange_timestamp = GNUNET_TIME_absolute_get (); diff --git a/src/exchange/taler-exchange-httpd_keys.c b/src/exchange/taler-exchange-httpd_keys.c index 86366eaf6..f722c16a7 100644 --- a/src/exchange/taler-exchange-httpd_keys.c +++ b/src/exchange/taler-exchange-httpd_keys.c @@ -2110,6 +2110,8 @@ TEH_keys_denomination_by_hash2 ( &h_denom_pub->hash); if (NULL == dk) { + if (NULL == conn) + return NULL; *mret = TEH_RESPONSE_reply_unknown_denom_pub_hash (conn, h_denom_pub); return NULL; @@ -2388,8 +2390,8 @@ TEH_keys_get_handler (struct TEH_RequestContext *rc, MHD_HTTP_OK, (MHD_YES == TALER_MHD_can_compress (rc->connection)) - ? krd->response_compressed - : krd->response_uncompressed); + ? krd->response_compressed + : krd->response_uncompressed); } } diff --git a/src/exchange/taler-exchange-httpd_melt.c b/src/exchange/taler-exchange-httpd_melt.c index c33473b4b..3dbff43a8 100644 --- a/src/exchange/taler-exchange-httpd_melt.c +++ b/src/exchange/taler-exchange-httpd_melt.c @@ -311,16 +311,6 @@ melt_transaction (void *cls, enum GNUNET_DB_QueryStatus qs; uint32_t noreveal_index; - /* First, make sure coin is 'known' in database */ - if (! rmc->coin_is_dirty) - { - qs = TEH_make_coin_known (&rmc->refresh_session.coin, - connection, - mhd_ret); - if (qs < 0) - return qs; - } - /* Check if we already created a matching refresh_session */ qs = TEH_plugin->get_melt_index (TEH_plugin->cls, &rmc->refresh_session.rc, @@ -420,7 +410,22 @@ handle_melt (struct MHD_Connection *connection, } } - /* run database transaction */ + /* first, make sure coin is known */ + if (! rmc->coin_is_dirty) + { + MHD_RESULT mhd_ret = MHD_NO; + enum GNUNET_DB_QueryStatus qs; + + qs = TEH_make_coin_known (&rmc->refresh_session.coin, + connection, + &mhd_ret); + /* no transaction => no serialization failures should be possible */ + GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs); + if (qs < 0) + return mhd_ret; + } + + /* run main database transaction */ { MHD_RESULT mhd_ret; @@ -513,10 +518,6 @@ check_for_denomination_key (struct MHD_Connection *connection, TALER_EC_GENERIC_DB_FETCH_FAILED, "coin denomination"); } - /* sanity check */ - GNUNET_break (0 == - GNUNET_memcmp (&denom_hash, - &rmc->refresh_session.coin.denom_pub_hash)); if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs) { struct GNUNET_TIME_Absolute now; @@ -531,11 +532,23 @@ check_for_denomination_key (struct MHD_Connection *connection, TALER_EC_EXCHANGE_GENERIC_DENOMINATION_EXPIRED, "MELT"); } - else + /* Minor optimization: no need to run the + "ensure_coin_known" part of the transaction */ + rmc->coin_is_dirty = true; + /* sanity check */ + if (0 != + GNUNET_memcmp (&denom_hash, + &rmc->refresh_session.coin.denom_pub_hash)) { - /* Minor optimization: no need to run the - "ensure_coin_known" part of the transaction */ - rmc->coin_is_dirty = true; + GNUNET_break_op (0); + // => this is probably the wrong call, as this + // is NOT about insufficient funds! + // (see also taler-exchange-httpd_db.c for an equivalent issue) + return TEH_RESPONSE_reply_coin_insufficient_funds ( + connection, + TALER_EC_EXCHANGE_GENERIC_COIN_CONFLICTING_DENOMINATION_KEY, + &rmc->refresh_session.coin.coin_pub, + NULL); } rmc->zombie_required = true; /* check later that zombie is satisfied */ } diff --git a/src/exchange/taler-exchange-httpd_reserves_get.c b/src/exchange/taler-exchange-httpd_reserves_get.c index 89a7dd498..3b8354215 100644 --- a/src/exchange/taler-exchange-httpd_reserves_get.c +++ b/src/exchange/taler-exchange-httpd_reserves_get.c @@ -235,11 +235,13 @@ reserve_history_transaction (void *cls, MHD_RESULT *mhd_ret) { struct ReserveHistoryContext *rsc = cls; + struct TALER_Amount balance; (void) connection; (void) mhd_ret; return TEH_plugin->get_reserve_history (TEH_plugin->cls, &rsc->reserve_pub, + &balance, &rsc->rh); } diff --git a/src/exchange/taler-exchange-httpd_withdraw.c b/src/exchange/taler-exchange-httpd_withdraw.c index d393567e4..8540fca4b 100644 --- a/src/exchange/taler-exchange-httpd_withdraw.c +++ b/src/exchange/taler-exchange-httpd_withdraw.c @@ -33,14 +33,6 @@ #include "taler-exchange-httpd_keys.h" -/** - * Perform RSA signature before checking with the database? - * Reduces time spent in transaction, but may cause us to - * waste CPU time if DB check fails. - */ -#define OPTIMISTIC_SIGN 1 - - /** * Send reserve history information to client with the * message that we have insufficient funds for the @@ -99,21 +91,6 @@ struct WithdrawContext */ struct TALER_WithdrawRequestPS wsrd; - /** - * Value of the coin plus withdraw fee. - */ - struct TALER_Amount amount_required; - - /** - * Hash of the denomination public key. - */ - struct TALER_DenominationHash denom_pub_hash; - - /** - * Signature over the request. - */ - struct TALER_ReserveSignatureP signature; - /** * Blinded planchet. */ @@ -134,39 +111,9 @@ struct WithdrawContext */ struct TALER_EXCHANGEDB_KycStatus kyc; - /** - * Set to true if the operation was denied due to - * failing @e kyc checks. - */ - bool kyc_denied; - }; -/** - * Function called with another amount that was - * already withdrawn. Accumulates all amounts in - * @a cls. - * - * @param[in,out] cls a `struct TALER_Amount` - * @param val value to add to @a cls - */ -static void -accumulate_withdraws (void *cls, - const struct TALER_Amount *val) -{ - struct TALER_Amount *acc = cls; - - if (GNUNET_OK != - TALER_amount_is_valid (acc)) - return; /* ignore */ - GNUNET_break (0 <= - TALER_amount_add (acc, - acc, - val)); -} - - /** * Function implementing withdraw transaction. Runs the * transaction logic; IF it returns a non-error code, the transaction @@ -175,15 +122,8 @@ accumulate_withdraws (void *cls, * IF it returns the soft error code, the function MAY be called again * to retry and MUST not queue a MHD response. * - * Note that "wc->collectable.sig" may already be set before entering - * this function, either because OPTIMISTIC_SIGN was used and we signed - * before entering the transaction, or because this function is run - * twice (!) by #TEH_DB_run_transaction() and the first time created - * the signature and then failed to commit. Furthermore, we may get - * a 2nd correct signature briefly if "get_withdraw_info" succeeds and - * finds one in the DB. To avoid signing twice, the function may - * return a valid signature in "wc->collectable.sig" **even if it failed**. - * The caller must thus free the signature in either case. + * Note that "wc->collectable.sig" is set before entering this function as we + * signed before entering the transaction. * * @param cls a `struct WithdrawContext *` * @param connection MHD request which triggered the transaction @@ -197,71 +137,34 @@ withdraw_transaction (void *cls, MHD_RESULT *mhd_ret) { struct WithdrawContext *wc = cls; - struct TALER_EXCHANGEDB_Reserve r; enum GNUNET_DB_QueryStatus qs; - struct TALER_BlindedDenominationSignature denom_sig; + bool found = false; + bool balance_ok = false; + uint64_t reserve_uuid; + struct GNUNET_TIME_Absolute now; -#if OPTIMISTIC_SIGN - /* store away optimistic signature to protect - it from being overwritten by get_withdraw_info */ - denom_sig = wc->collectable.sig; - memset (&wc->collectable.sig, - 0, - sizeof (wc->collectable.sig)); -#endif - qs = TEH_plugin->get_withdraw_info (TEH_plugin->cls, - &wc->wsrd.h_coin_envelope, - &wc->collectable); - if (0 > qs) - { - GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); - if (GNUNET_DB_STATUS_HARD_ERROR == qs) - *mhd_ret = TALER_MHD_reply_with_error (connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_GENERIC_DB_FETCH_FAILED, - "withdraw details"); - wc->collectable.sig = denom_sig; - return qs; - } + now = GNUNET_TIME_absolute_get (); + (void) GNUNET_TIME_round_abs (&now); - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Asked to withdraw from %s amount of %s\n", - TALER_B2S (&wc->wsrd.reserve_pub), - TALER_amount2s (&wc->amount_required)); - /* Don't sign again if we have already signed the coin */ - if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs) - { - /* Toss out the optimistic signature, we got another one from the DB; - optimization trade-off loses in this case: we unnecessarily computed - a signature :-( */ -#if OPTIMISTIC_SIGN - TALER_blinded_denom_sig_free (&denom_sig); -#endif - return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT; - } - /* We should never get more than one result, and we handled - the errors (negative case) above, so that leaves no results. */ - GNUNET_assert (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs); - wc->collectable.sig = denom_sig; /* Note: might still be NULL if we didn't do OPTIMISTIC_SIGN */ - - /* Check if balance is sufficient */ - r.pub = wc->wsrd.reserve_pub; /* other fields of 'r' initialized in reserves_get (if successful) */ - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Trying to withdraw from reserve: %s\n", - TALER_B2S (&r.pub)); - qs = TEH_plugin->reserves_get (TEH_plugin->cls, - &r, - &wc->kyc); + wc->collectable.reserve_pub = wc->wsrd.reserve_pub; + wc->collectable.h_coin_envelope = wc->wsrd.h_coin_envelope; + qs = TEH_plugin->do_withdraw (TEH_plugin->cls, + &wc->collectable, + now, + &found, + &balance_ok, + &wc->kyc, + &reserve_uuid); if (0 > qs) { if (GNUNET_DB_STATUS_HARD_ERROR == qs) *mhd_ret = TALER_MHD_reply_with_error (connection, MHD_HTTP_INTERNAL_SERVER_ERROR, TALER_EC_GENERIC_DB_FETCH_FAILED, - "reserves"); + "do_withdraw"); return qs; } - if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) + if (! found) { *mhd_ret = TALER_MHD_reply_with_error (connection, MHD_HTTP_NOT_FOUND, @@ -269,29 +172,29 @@ withdraw_transaction (void *cls, NULL); return GNUNET_DB_STATUS_HARD_ERROR; } - if (0 < TALER_amount_cmp (&wc->amount_required, - &r.balance)) + if (! balance_ok) { struct TALER_EXCHANGEDB_ReserveHistory *rh; + struct TALER_Amount balance; + TEH_plugin->rollback (TEH_plugin->cls); + if (GNUNET_OK != + TEH_plugin->start (TEH_plugin->cls, + "get_reserve_history on insufficient balance")) + { + GNUNET_break (0); + if (NULL != mhd_ret) + *mhd_ret = TALER_MHD_reply_with_error (connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_START_FAILED, + NULL); + return GNUNET_DB_STATUS_HARD_ERROR; + } /* The reserve does not have the required amount (actual * amount + withdraw fee) */ -#if GNUNET_EXTRA_LOGGING - { - char *amount_required; - char *r_balance; - - amount_required = TALER_amount_to_string (&wc->amount_required); - r_balance = TALER_amount_to_string (&r.balance); - TALER_LOG_DEBUG ("Asked %s over a reserve worth %s\n", - amount_required, - r_balance); - GNUNET_free (amount_required); - GNUNET_free (r_balance); - } -#endif qs = TEH_plugin->get_reserve_history (TEH_plugin->cls, &wc->wsrd.reserve_pub, + &balance, &rh); if (NULL == rh) { @@ -303,41 +206,41 @@ withdraw_transaction (void *cls, return GNUNET_DB_STATUS_HARD_ERROR; } *mhd_ret = reply_withdraw_insufficient_funds (connection, - &r.balance, + &balance, rh); TEH_plugin->free_reserve_history (TEH_plugin->cls, rh); return GNUNET_DB_STATUS_HARD_ERROR; } - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "KYC status is %s for %s\n", - wc->kyc.ok ? "ok" : "missing", - TALER_B2S (&r.pub)); - if ( (! wc->kyc.ok) && - (TEH_KYC_NONE != TEH_kyc_config.mode) && + if ( (TEH_KYC_NONE != TEH_kyc_config.mode) && + (! wc->kyc.ok) && (TALER_EXCHANGEDB_KYC_W2W == wc->kyc.type) ) { /* Wallet-to-wallet payments _always_ require KYC */ - wc->kyc_denied = true; - return qs; + *mhd_ret = TALER_MHD_REPLY_JSON_PACK ( + connection, + MHD_HTTP_ACCEPTED, + GNUNET_JSON_pack_uint64 ("payment_target_uuid", + wc->kyc.payment_target_uuid)); + return GNUNET_DB_STATUS_HARD_ERROR; } - if ( (! wc->kyc.ok) && - (TEH_KYC_NONE != TEH_kyc_config.mode) && + if ( (TEH_KYC_NONE != TEH_kyc_config.mode) && + (! wc->kyc.ok) && (TALER_EXCHANGEDB_KYC_WITHDRAW == wc->kyc.type) && (! GNUNET_TIME_relative_is_zero (TEH_kyc_config.withdraw_period)) ) { /* Withdraws require KYC if above threshold */ - struct TALER_Amount acc; enum GNUNET_DB_QueryStatus qs2; + bool below_limit; - acc = wc->amount_required; - qs2 = TEH_plugin->select_withdraw_amounts_by_account ( + qs2 = TEH_plugin->do_withdraw_limit_check ( TEH_plugin->cls, - &wc->wsrd.reserve_pub, - TEH_kyc_config.withdraw_period, - &accumulate_withdraws, - &acc); + reserve_uuid, + GNUNET_TIME_absolute_subtract (now, + TEH_kyc_config.withdraw_period), + &TEH_kyc_config.withdraw_limit, + &below_limit); if (0 > qs2) { GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs2); @@ -345,74 +248,64 @@ withdraw_transaction (void *cls, *mhd_ret = TALER_MHD_reply_with_error (connection, MHD_HTTP_INTERNAL_SERVER_ERROR, TALER_EC_GENERIC_DB_FETCH_FAILED, - "withdraw details"); + "do_withdraw_limit_check"); return qs2; } - - if (GNUNET_OK != - TALER_amount_is_valid (&acc)) + if (! below_limit) { - GNUNET_break (0); - *mhd_ret = TALER_MHD_reply_with_ec (connection, - TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE, - NULL); - return GNUNET_DB_STATUS_HARD_ERROR; - } - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Amount withdrawn so far is %s\n", - TALER_amount2s (&acc)); - if (1 == /* 1: acc > withdraw_limit */ - TALER_amount_cmp (&acc, - &TEH_kyc_config.withdraw_limit)) - { - wc->kyc_denied = true; - return qs; - } - } - - /* Balance is good, sign the coin! */ -#if ! OPTIMISTIC_SIGN - if (NULL == wc->collectable.sig.rsa_signature) - { - enum TALER_ErrorCode ec = TALER_EC_NONE; - - wc->collectable.sig - = TEH_keys_denomination_sign (&wc->denom_pub_hash, - wc->blinded_msg, - wc->blinded_msg_len, - &ec); - if (TALER_EC_NONE != ec) - { - GNUNET_break (0); - *mhd_ret = TALER_MHD_reply_with_ec (connection, - ec, - NULL); + *mhd_ret = TALER_MHD_REPLY_JSON_PACK ( + connection, + MHD_HTTP_ACCEPTED, + GNUNET_JSON_pack_uint64 ("payment_target_uuid", + wc->kyc.payment_target_uuid)); return GNUNET_DB_STATUS_HARD_ERROR; } } -#endif - wc->collectable.denom_pub_hash = wc->denom_pub_hash; - wc->collectable.amount_with_fee = wc->amount_required; - wc->collectable.reserve_pub = wc->wsrd.reserve_pub; - wc->collectable.h_coin_envelope = wc->wsrd.h_coin_envelope; - wc->collectable.reserve_sig = wc->signature; - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Persisting withdraw from %s over %s\n", - TALER_B2S (&r.pub), - TALER_amount2s (&wc->amount_required)); - qs = TEH_plugin->insert_withdraw_info (TEH_plugin->cls, - &wc->collectable); + return qs; +} + + +/** + * Check if the @a rc is replayed and we already have an + * answer. If so, replay the existing answer and return the + * HTTP response. + * + * @param rc request context + * @param[in,out] wc parsed request data + * @param[out] mret HTTP status, set if we return true + * @return true if the request is idempotent with an existing request + * false if we did not find the request in the DB and did not set @a mret + */ +static bool +check_request_idempotent (struct TEH_RequestContext *rc, + struct WithdrawContext *wc, + MHD_RESULT *mret) +{ + enum GNUNET_DB_QueryStatus qs; + + qs = TEH_plugin->get_withdraw_info (TEH_plugin->cls, + &wc->wsrd.h_coin_envelope, + &wc->collectable); if (0 > qs) { GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); if (GNUNET_DB_STATUS_HARD_ERROR == qs) - *mhd_ret = TALER_MHD_reply_with_error (connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_GENERIC_DB_STORE_FAILED, - "withdraw details"); - return qs; + *mret = TALER_MHD_reply_with_error (rc->connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_FETCH_FAILED, + "get_withdraw_info"); + return true; /* well, kind-of */ } - return qs; + if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) + return false; + /* generate idempotent reply */ + *mret = TALER_MHD_REPLY_JSON_PACK ( + rc->connection, + MHD_HTTP_OK, + TALER_JSON_pack_blinded_denom_sig ("ev_sig", + &wc->collectable.sig)); + TALER_blinded_denom_sig_free (&wc->collectable.sig); + return true; } @@ -427,9 +320,9 @@ TEH_handler_withdraw (struct TEH_RequestContext *rc, (void **) &wc.blinded_msg, &wc.blinded_msg_len), GNUNET_JSON_spec_fixed_auto ("reserve_sig", - &wc.signature), + &wc.collectable.reserve_sig), GNUNET_JSON_spec_fixed_auto ("denom_pub_hash", - &wc.denom_pub_hash), + &wc.collectable.denom_pub_hash), GNUNET_JSON_spec_end () }; enum TALER_ErrorCode ec; @@ -463,12 +356,39 @@ TEH_handler_withdraw (struct TEH_RequestContext *rc, { MHD_RESULT mret; struct GNUNET_TIME_Absolute now; + struct TEH_KeyStateHandle *ksh; - dk = TEH_keys_denomination_by_hash (&wc.denom_pub_hash, - rc->connection, - &mret); + ksh = TEH_keys_get_state (); + if (NULL == ksh) + { + if (! check_request_idempotent (rc, + &wc, + &mret)) + { + GNUNET_JSON_parse_free (spec); + return TALER_MHD_reply_with_error (rc->connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING, + NULL); + } + GNUNET_JSON_parse_free (spec); + return mret; + } + dk = TEH_keys_denomination_by_hash2 (ksh, + &wc.collectable.denom_pub_hash, + NULL, + NULL); if (NULL == dk) { + if (! check_request_idempotent (rc, + &wc, + &mret)) + { + GNUNET_JSON_parse_free (spec); + return TEH_RESPONSE_reply_unknown_denom_pub_hash ( + rc->connection, + &wc.collectable.denom_pub_hash); + } GNUNET_JSON_parse_free (spec); return mret; } @@ -481,13 +401,20 @@ TEH_handler_withdraw (struct TEH_RequestContext *rc, now = GNUNET_TIME_absolute_get (); (void) GNUNET_TIME_round_abs (&now); /* This denomination is past the expiration time for withdraws */ + if (! check_request_idempotent (rc, + &wc, + &mret)) + { + GNUNET_JSON_parse_free (spec); + return TEH_RESPONSE_reply_expired_denom_pub_hash ( + rc->connection, + &wc.collectable.denom_pub_hash, + now, + TALER_EC_EXCHANGE_GENERIC_DENOMINATION_EXPIRED, + "WITHDRAW"); + } GNUNET_JSON_parse_free (spec); - return TEH_RESPONSE_reply_expired_denom_pub_hash ( - rc->connection, - &wc.denom_pub_hash, - now, - TALER_EC_EXCHANGE_GENERIC_DENOMINATION_EXPIRED, - "WITHDRAW"); + return mret; } if (GNUNET_TIME_absolute_is_future (dk->meta.start)) { @@ -495,11 +422,12 @@ TEH_handler_withdraw (struct TEH_RequestContext *rc, now = GNUNET_TIME_absolute_get (); (void) GNUNET_TIME_round_abs (&now); - /* This denomination is not yet valid */ + /* This denomination is not yet valid, no need to check + for idempotency! */ GNUNET_JSON_parse_free (spec); return TEH_RESPONSE_reply_expired_denom_pub_hash ( rc->connection, - &wc.denom_pub_hash, + &wc.collectable.denom_pub_hash, now, TALER_EC_EXCHANGE_GENERIC_DENOMINATION_VALIDITY_IN_FUTURE, "WITHDRAW"); @@ -511,19 +439,26 @@ TEH_handler_withdraw (struct TEH_RequestContext *rc, now = GNUNET_TIME_absolute_get (); (void) GNUNET_TIME_round_abs (&now); /* This denomination has been revoked */ + if (! check_request_idempotent (rc, + &wc, + &mret)) + { + GNUNET_JSON_parse_free (spec); + return TEH_RESPONSE_reply_expired_denom_pub_hash ( + rc->connection, + &wc.collectable.denom_pub_hash, + now, + TALER_EC_EXCHANGE_GENERIC_DENOMINATION_REVOKED, + "WITHDRAW"); + } GNUNET_JSON_parse_free (spec); - return TEH_RESPONSE_reply_expired_denom_pub_hash ( - rc->connection, - &wc.denom_pub_hash, - now, - TALER_EC_EXCHANGE_GENERIC_DENOMINATION_REVOKED, - "WITHDRAW"); + return mret; } } { if (0 > - TALER_amount_add (&wc.amount_required, + TALER_amount_add (&wc.collectable.amount_with_fee, &dk->meta.value, &dk->meta.fee_withdraw)) { @@ -534,7 +469,7 @@ TEH_handler_withdraw (struct TEH_RequestContext *rc, NULL); } TALER_amount_hton (&wc.wsrd.amount_with_fee, - &wc.amount_required); + &wc.collectable.amount_with_fee); } /* verify signature! */ @@ -543,15 +478,16 @@ TEH_handler_withdraw (struct TEH_RequestContext *rc, wc.wsrd.purpose.purpose = htonl (TALER_SIGNATURE_WALLET_RESERVE_WITHDRAW); wc.wsrd.h_denomination_pub - = wc.denom_pub_hash; + = wc.collectable.denom_pub_hash; TALER_coin_ev_hash (wc.blinded_msg, wc.blinded_msg_len, &wc.wsrd.h_coin_envelope); if (GNUNET_OK != - GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_WALLET_RESERVE_WITHDRAW, - &wc.wsrd, - &wc.signature.eddsa_signature, - &wc.wsrd.reserve_pub.eddsa_pub)) + GNUNET_CRYPTO_eddsa_verify ( + TALER_SIGNATURE_WALLET_RESERVE_WITHDRAW, + &wc.wsrd, + &wc.collectable.reserve_sig.eddsa_signature, + &wc.wsrd.reserve_pub.eddsa_pub)) { TALER_LOG_WARNING ( "Client supplied invalid signature for withdraw request\n"); @@ -562,11 +498,10 @@ TEH_handler_withdraw (struct TEH_RequestContext *rc, NULL); } -#if OPTIMISTIC_SIGN /* Sign before transaction! */ ec = TALER_EC_NONE; wc.collectable.sig - = TEH_keys_denomination_sign (&wc.denom_pub_hash, + = TEH_keys_denomination_sign (&wc.collectable.denom_pub_hash, wc.blinded_msg, wc.blinded_msg_len, &ec); @@ -578,10 +513,8 @@ TEH_handler_withdraw (struct TEH_RequestContext *rc, ec, NULL); } -#endif /* run transaction and sign (if not optimistically signed before) */ - wc.kyc_denied = false; { MHD_RESULT mhd_ret; @@ -603,16 +536,6 @@ TEH_handler_withdraw (struct TEH_RequestContext *rc, /* Clean up and send back final response */ GNUNET_JSON_parse_free (spec); - if (wc.kyc_denied) - { - TALER_blinded_denom_sig_free (&wc.collectable.sig); - return TALER_MHD_REPLY_JSON_PACK ( - rc->connection, - MHD_HTTP_ACCEPTED, - GNUNET_JSON_pack_uint64 ("payment_target_uuid", - wc.kyc.payment_target_uuid)); - } - { MHD_RESULT ret; diff --git a/src/exchangedb/.gitignore b/src/exchangedb/.gitignore index 830cf10cc..aea9a74e4 100644 --- a/src/exchangedb/.gitignore +++ b/src/exchangedb/.gitignore @@ -4,3 +4,4 @@ test-exchangedb-fees test-exchangedb-postgres test-exchangedb-signkeys test-perf-taler-exchangedb +bench-db-postgres diff --git a/src/exchangedb/bench_db.c b/src/exchangedb/bench_db.c index 1e95df279..a8dbfbfa5 100644 --- a/src/exchangedb/bench_db.c +++ b/src/exchangedb/bench_db.c @@ -379,7 +379,7 @@ run (void *cls) j = GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_NONCE, TOTAL); - if (! bm_select ((0 == f)? conn2 : conn, + if (! bm_select ((0 == f) ? conn2 : conn, j)) { GNUNET_PQ_disconnect (conn); @@ -422,7 +422,7 @@ run (void *cls) j = GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_NONCE, TOTAL); - if (! bhm_select ((0 == f)? conn2 : conn, + if (! bhm_select ((0 == f) ? conn2 : conn, j)) { GNUNET_PQ_disconnect (conn); @@ -465,7 +465,7 @@ run (void *cls) j = GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_NONCE, TOTAL); - if (! bem_select ((0 == f)? conn2 : conn, + if (! bem_select ((0 == f) ? conn2 : conn, j)) { GNUNET_PQ_disconnect (conn); diff --git a/src/exchangedb/drop0001.sql b/src/exchangedb/drop0001.sql index 52079e52c..3dcbb81fa 100644 --- a/src/exchangedb/drop0001.sql +++ b/src/exchangedb/drop0001.sql @@ -54,6 +54,10 @@ DROP TABLE IF EXISTS reserves CASCADE; DROP TABLE IF EXISTS denomination_revocations CASCADE; DROP TABLE IF EXISTS denominations CASCADE; +DROP FUNCTION IF EXISTS exchange_do_withdraw(bigint,integer,bytea,bytea,bytea,bytea,bytea,bigint,bigint) ; + +DROP FUNCTION IF EXISTS exchange_do_withdraw_limit_check(bigint,bigint,bigint,int) ; + -- And we're out of here... COMMIT; diff --git a/src/exchangedb/exchange-0001.sql b/src/exchangedb/exchange-0001.sql index 7acd67244..dc6b2bba2 100644 --- a/src/exchangedb/exchange-0001.sql +++ b/src/exchangedb/exchange-0001.sql @@ -75,7 +75,7 @@ CREATE TABLE IF NOT EXISTS wire_targets ,PRIMARY KEY (h_payto) ); COMMENT ON TABLE wire_targets - IS 'All recipients of money via the exchange'; + IS 'All senders and recipients of money via the exchange'; COMMENT ON COLUMN wire_targets.payto_uri IS 'Can be a regular bank account, or also be a URI identifying a reserve-account (for P2P payments)'; COMMENT ON COLUMN wire_targets.h_payto @@ -131,7 +131,7 @@ CREATE TABLE IF NOT EXISTS reserves_in COMMENT ON TABLE reserves_in IS 'list of transfers of funds into the reserves, one per incoming wire transfer'; COMMENT ON COLUMN reserves_in.wire_source_serial_id - IS 'Identifies the debited bank account and KYC status';-- FIXME: explain 'wire_reference'! + IS 'Identifies the debited bank account and KYC status'; CREATE INDEX IF NOT EXISTS reserves_in_execution_index ON reserves_in (exchange_account_section @@ -263,7 +263,7 @@ CREATE TABLE IF NOT EXISTS signkey_revocations ,master_sig BYTEA NOT NULL CHECK (LENGTH(master_sig)=64) ); COMMENT ON TABLE signkey_revocations - IS 'remembering which online signing keys have been revoked'; + IS 'Table storing which online signing keys have been revoked'; CREATE TABLE IF NOT EXISTS known_coins @@ -301,6 +301,12 @@ CREATE TABLE IF NOT EXISTS refresh_commitments ); COMMENT ON TABLE refresh_commitments IS 'Commitments made when melting coins and the gamma value chosen by the exchange.'; +COMMENT ON COLUMN refresh_commitments.noreveal_index + IS 'The gamma value chosen by the exchange in the cut-and-choose protocol'; +COMMENT ON COLUMN refresh_commitments.rc + IS 'Commitment made by the client, hash over the various client inputs in the cut-and-choose protocol'; +COMMENT ON COLUMN refresh_commitments.old_known_coin_id + IS 'Coin being melted in the refresh process.'; CREATE INDEX IF NOT EXISTS refresh_commitments_old_coin_id_index ON refresh_commitments @@ -526,11 +532,15 @@ CREATE TABLE IF NOT EXISTS recoup ,reserve_out_serial_id INT8 NOT NULL REFERENCES reserves_out (reserve_out_serial_id) ON DELETE CASCADE ); COMMENT ON TABLE recoup - IS 'Information about recoups that were executed'; + IS 'Information about recoups that were executed between a coin and a reserve. In this type of recoup, the amount is credited back to the reserve from which the coin originated.'; COMMENT ON COLUMN recoup.known_coin_id - IS 'Do not CASCADE ON DROP on the known_coin_id, as we may keep the coin alive!'; + IS 'Coin that is being debited in the recoup. Do not CASCADE ON DROP on the known_coin_id, as we may keep the coin alive!'; COMMENT ON COLUMN recoup.reserve_out_serial_id - IS 'Identifies the h_blind_ev of the recouped coin.'; + IS 'Identifies the h_blind_ev of the recouped coin and provides the link to the credited reserve.'; +COMMENT ON COLUMN recoup.coin_sig + IS 'Signature by the coin affirming the recoup, of type TALER_SIGNATURE_WALLET_COIN_RECOUP'; +COMMENT ON COLUMN recoup.coin_blind + IS 'Denomination blinding key used when creating the blinded coin from the planchet. Secret revealed during the recoup to provide the linkage between the coin and the withdraw operation.'; CREATE INDEX IF NOT EXISTS recoup_by_h_blind_ev ON recoup @@ -552,10 +562,14 @@ CREATE TABLE IF NOT EXISTS recoup_refresh ,timestamp INT8 NOT NULL ,rrc_serial INT8 NOT NULL UNIQUE REFERENCES refresh_revealed_coins (rrc_serial) ON DELETE CASCADE ); +COMMENT ON TABLE recoup_refresh + IS 'Table of coins that originated from a refresh operation and that were recouped. Links the (fresh) coin to the melted operation (and thus the old coin). A recoup on a refreshed coin credits the old coin and debits the fresh coin.'; COMMENT ON COLUMN recoup_refresh.known_coin_id - IS 'Do not CASCADE ON DROP on the known_coin_id, as we may keep the coin alive!'; + IS 'Refreshed coin of a revoked denomination where the residual value is credited to the old coin. Do not CASCADE ON DROP on the known_coin_id, as we may keep the coin alive!'; COMMENT ON COLUMN recoup_refresh.rrc_serial - IS 'Identifies the h_blind_ev of the recouped coin (as h_coin_ev).'; + IS 'Link to the refresh operation. Also identifies the h_blind_ev of the recouped coin (as h_coin_ev).'; +COMMENT ON COLUMN recoup_refresh.coin_blind + IS 'Denomination blinding key used when creating the blinded coin from the planchet. Secret revealed during the recoup to provide the linkage between the coin and the refresh operation.'; CREATE INDEX IF NOT EXISTS recoup_refresh_by_h_blind_ev ON recoup_refresh @@ -680,6 +694,211 @@ CREATE INDEX IF NOT EXISTS revolving_work_shards_index ); +-- Stored procedures + + +DROP FUNCTION IF EXISTS exchange_do_withdraw(bigint,integer,bytea,bytea,bytea,bytea,bytea,bigint,bigint) ; + +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; + +SELECT + reserves.reserve_uuid + ,current_balance_val + ,current_balance_frac + ,expiration_date + ,gc_date + INTO + ruuid + ,reserve_val + ,reserve_frac + ,reserve_gc + FROM reserves + WHERE reserves.reserve_pub=rpub; + +IF NOT FOUND +THEN + -- reserve unknown + reserve_found=FALSE; + balance_ok=FALSE; + kycok=FALSE; + account_uuid=0; + RETURN; +END IF; + +-- 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 + reserve_found=TRUE; + balance_ok=TRUE; + kycok=TRUE; + account_uuid=0; + RETURN; +END IF; + +-- Check reserve balance is sufficient. +IF (reserve_val > amount_val) +THEN + IF (reserve_frac > amount_frac) + THEN + reserve_val=reserve_val - amount_val; + reserve_frac=reserve_frac - amount_frac; + ELSE + reserve_val=reserve_val - amount_val - 1; + reserve_frac=reserve_frac + 100000000 - amount_frac; + END IF; +ELSE + IF (reserve_val = amount_val) AND (reserve_frac >= amount_frac) + THEN + reserve_val=0; + reserve_frac=reserve_frac - amount_frac; + ELSE + reserve_found=TRUE; + balance_ok=FALSE; + kycok=FALSE; -- we do not really know or care + account_uuid=0; + RETURN; + END IF; +END IF; + +-- Calculate new expiration dates. +min_reserve_gc=GREATEST(min_reserve_gc,reserve_gc); + +-- Update reserve balance. +UPDATE reserves SET + gc_date=min_reserve_gc + ,current_balance_val=reserve_val + ,current_balance_frac=reserve_frac +WHERE + reserves.reserve_uuid=ruuid; + +reserve_found=TRUE; +balance_ok=TRUE; + +-- Obtain KYC status based on the last wire transfer into +-- this reserve. FIXME: likely not adequate for reserves that got P2P transfers! +SELECT + kyc_ok + ,wire_source_serial_id + INTO + kycok + ,account_uuid + FROM reserves_in + JOIN wire_targets ON (wire_source_serial_id = wire_target_serial_id) + WHERE reserve_uuid=ruuid + LIMIT 1; -- limit 1 should not be required (without p2p transfers) + +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'; + + + +DROP FUNCTION IF EXISTS exchange_do_withdraw_limit_check(bigint,bigint,bigint,int) ; + + +CREATE OR REPLACE FUNCTION exchange_do_withdraw_limit_check( + IN ruuid INT8, + IN start_time INT8, + IN upper_limit_val INT8, + IN upper_limit_frac INT4, + OUT below_limit BOOLEAN) +LANGUAGE plpgsql +AS $$ +DECLARE + total_val INT8; +DECLARE + total_frac INT8; -- INT4 could overflow during accumulation! +BEGIN + +SELECT + SUM(amount_with_fee_val) -- overflow here is not plausible + ,SUM(CAST(amount_with_fee_frac AS INT8)) -- compute using 64 bits + INTO + total_val + ,total_frac + FROM reserves_out + WHERE reserves_out.reserve_uuid=ruuid + AND execution_date > start_time; + +-- normalize result +total_val = total_val + total_frac / 100000000; +total_frac = total_frac % 100000000; + +-- compare to threshold +below_limit = (total_val < upper_limit_val) OR + ( (total_val = upper_limit_val) AND + (total_frac <= upper_limit_frac) ); +END $$; + +COMMENT ON FUNCTION exchange_do_withdraw_limit_check(INT8, INT8, INT8, INT4) + IS 'Check whether the withdrawals from the given reserve since the given time are below the given threshold'; + + + -- Complete transaction diff --git a/src/exchangedb/plugin_exchangedb_postgres.c b/src/exchangedb/plugin_exchangedb_postgres.c index 004516c51..79013179a 100644 --- a/src/exchangedb/plugin_exchangedb_postgres.c +++ b/src/exchangedb/plugin_exchangedb_postgres.c @@ -596,6 +596,34 @@ prepare_statements (struct PostgresClosure *pg) "lock_withdraw", "LOCK TABLE reserves_out;", 0), + /* Used in #postgres_do_withdraw() to store + the signature of a blinded coin with the blinded coin's + details before returning it during /reserve/withdraw. We store + the coin's denomination information (public key, signature) + and the blinded message as well as the reserve that the coin + is being withdrawn from and the signature of the message + authorizing the withdrawal. */ + GNUNET_PQ_make_prepare ( + "call_withdraw", + "SELECT " + " reserve_found" + ",balance_ok" + ",kycok AS kyc_ok" + ",ruuid AS reserve_uuid" + ",account_uuid AS payment_target_uuid" + " FROM exchange_do_withdraw" + " ($1,$2,$3,$4,$5,$6,$7,$8,$9);", + 9), + /* Used in #postgres_do_withdraw_limit_check() to check + if the withdrawals remain below the limit under which + KYC is not required. */ + GNUNET_PQ_make_prepare ( + "call_withdraw_limit_check", + "SELECT " + " below_limit" + " FROM exchange_do_withdraw_limit_check" + " ($1,$2,$3,$4);", + 4), /* Used in #postgres_insert_withdraw_info() to store the signature of a blinded coin with the blinded coin's details before returning it during /reserve/withdraw. We store @@ -753,7 +781,8 @@ prepare_statements (struct PostgresClosure *pg) ",denom_sig" ") SELECT $1, denominations_serial, $3 " " FROM denominations" - " WHERE denom_pub_hash=$2;", + " WHERE denom_pub_hash=$2" + " ON CONFLICT DO NOTHING;", 3), /* Used in #postgres_insert_melt() to store @@ -3378,12 +3407,12 @@ dominations_cb_helper (void *cls, struct TALER_DenominationPublicKey denom_pub; struct TALER_MasterSignatureP master_sig; struct TALER_DenominationHash h_denom_pub; - uint8_t revoked; + bool revoked; struct GNUNET_PQ_ResultSpec rs[] = { GNUNET_PQ_result_spec_auto_from_type ("master_sig", &master_sig), - GNUNET_PQ_result_spec_auto_from_type ("revoked", - &revoked), + GNUNET_PQ_result_spec_bool ("revoked", + &revoked), TALER_PQ_result_spec_absolute_time ("valid_from", &meta.start), TALER_PQ_result_spec_absolute_time ("expire_withdraw", @@ -3422,7 +3451,7 @@ dominations_cb_helper (void *cls, &h_denom_pub, &meta, &master_sig, - (0 != revoked)); + revoked); GNUNET_PQ_cleanup_result (rs); } } @@ -3777,7 +3806,6 @@ postgres_reserves_get (void *cls, GNUNET_PQ_query_param_auto_from_type (&reserve->pub), GNUNET_PQ_query_param_end }; - uint8_t ok8; struct GNUNET_PQ_ResultSpec rs[] = { TALER_PQ_RESULT_SPEC_AMOUNT ("current_balance", &reserve->balance), @@ -3787,19 +3815,16 @@ postgres_reserves_get (void *cls, &reserve->gc), GNUNET_PQ_result_spec_uint64 ("payment_target_uuid", &kyc->payment_target_uuid), - GNUNET_PQ_result_spec_auto_from_type ("kyc_ok", - &ok8), + GNUNET_PQ_result_spec_bool ("kyc_ok", + &kyc->ok), GNUNET_PQ_result_spec_end }; - enum GNUNET_DB_QueryStatus qs; - qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn, - "reserves_get_with_kyc", - params, - rs); kyc->type = TALER_EXCHANGEDB_KYC_WITHDRAW; - kyc->ok = (0 != ok8); - return qs; + return GNUNET_PQ_eval_prepared_singleton_select (pg->conn, + "reserves_get_with_kyc", + params, + rs); } @@ -3874,23 +3899,19 @@ postgres_get_kyc_status (void *cls, GNUNET_PQ_query_param_string (payto_uri), GNUNET_PQ_query_param_end }; - uint8_t ok8; struct GNUNET_PQ_ResultSpec rs[] = { GNUNET_PQ_result_spec_uint64 ("payment_target_uuid", &kyc->payment_target_uuid), GNUNET_PQ_result_spec_auto_from_type ("kyc_ok", - &ok8), + &kyc->ok), GNUNET_PQ_result_spec_end }; - enum GNUNET_DB_QueryStatus qs; - qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn, - "get_kyc_status", - params, - rs); kyc->type = TALER_EXCHANGEDB_KYC_DEPOSIT; - kyc->ok = (0 != ok8); - return qs; + return GNUNET_PQ_eval_prepared_singleton_select (pg->conn, + "get_kyc_status", + params, + rs); } @@ -3914,24 +3935,20 @@ postgres_select_kyc_status (void *cls, GNUNET_PQ_query_param_uint64 (&payment_target_uuid), GNUNET_PQ_query_param_end }; - uint8_t ok8; struct GNUNET_PQ_ResultSpec rs[] = { GNUNET_PQ_result_spec_auto_from_type ("h_payto", h_payto), GNUNET_PQ_result_spec_auto_from_type ("kyc_ok", - &ok8), + &kyc->ok), GNUNET_PQ_result_spec_end }; - enum GNUNET_DB_QueryStatus qs; - qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn, - "select_kyc_status", - params, - rs); kyc->type = TALER_EXCHANGEDB_KYC_UNKNOWN; - kyc->ok = (0 != ok8); kyc->payment_target_uuid = payment_target_uuid; - return qs; + return GNUNET_PQ_eval_prepared_singleton_select (pg->conn, + "select_kyc_status", + params, + rs); } @@ -3962,12 +3979,11 @@ inselect_account_kyc_status ( GNUNET_PQ_query_param_auto_from_type (&h_payto), GNUNET_PQ_query_param_end }; - uint8_t ok8 = 0; struct GNUNET_PQ_ResultSpec rs[] = { GNUNET_PQ_result_spec_uint64 ("wire_target_serial_id", &kyc->payment_target_uuid), - GNUNET_PQ_result_spec_auto_from_type ("kyc_ok", - &ok8), + GNUNET_PQ_result_spec_bool ("kyc_ok", + &kyc->ok), GNUNET_PQ_result_spec_end }; @@ -3998,10 +4014,6 @@ inselect_account_kyc_status ( return GNUNET_DB_STATUS_SOFT_ERROR; kyc->ok = false; } - else - { - kyc->ok = (0 != ok8); - } } kyc->type = TALER_EXCHANGEDB_KYC_BALANCE; return qs; @@ -4478,86 +4490,104 @@ postgres_get_withdraw_info ( /** - * Store collectable bit coin under the corresponding - * hash of the blinded message. + * Perform withdraw operation, checking for sufficient balance + * and possibly persisting the withdrawal details. * * @param cls the `struct PostgresClosure` with the plugin-specific state * @param collectable corresponding collectable coin (blind signature) * if a coin is found + * @param now current time (rounded) + * @param[out] found set to true if the reserve was found + * @param[out] balance_ok set to true if the balance was sufficient + * @param[out] kyc_ok set to true if the kyc status of the reserve is satisfied + * @param[out] reserve_uuid set to the UUID of the reserve * @return query execution status */ static enum GNUNET_DB_QueryStatus -postgres_insert_withdraw_info ( +postgres_do_withdraw ( void *cls, - const struct TALER_EXCHANGEDB_CollectableBlindcoin *collectable) + const struct TALER_EXCHANGEDB_CollectableBlindcoin *collectable, + struct GNUNET_TIME_Absolute now, + bool *found, + bool *balance_ok, + struct TALER_EXCHANGEDB_KycStatus *kyc, + uint64_t *reserve_uuid) { struct PostgresClosure *pg = cls; - struct TALER_EXCHANGEDB_Reserve reserve; - struct GNUNET_TIME_Absolute now; struct GNUNET_TIME_Absolute gc; struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_auto_from_type (&collectable->h_coin_envelope), + TALER_PQ_query_param_amount (&collectable->amount_with_fee), GNUNET_PQ_query_param_auto_from_type (&collectable->denom_pub_hash), - TALER_PQ_query_param_blinded_denom_sig (&collectable->sig), GNUNET_PQ_query_param_auto_from_type (&collectable->reserve_pub), GNUNET_PQ_query_param_auto_from_type (&collectable->reserve_sig), + GNUNET_PQ_query_param_auto_from_type (&collectable->h_coin_envelope), + TALER_PQ_query_param_blinded_denom_sig (&collectable->sig), TALER_PQ_query_param_absolute_time (&now), - TALER_PQ_query_param_amount (&collectable->amount_with_fee), + TALER_PQ_query_param_absolute_time (&gc), GNUNET_PQ_query_param_end }; - enum GNUNET_DB_QueryStatus qs; + struct GNUNET_PQ_ResultSpec rs[] = { + GNUNET_PQ_result_spec_bool ("reserve_found", + found), + GNUNET_PQ_result_spec_bool ("balance_ok", + balance_ok), + GNUNET_PQ_result_spec_bool ("kyc_ok", + &kyc->ok), + GNUNET_PQ_result_spec_uint64 ("reserve_uuid", + reserve_uuid), + GNUNET_PQ_result_spec_uint64 ("payment_target_uuid", + &kyc->payment_target_uuid), + GNUNET_PQ_result_spec_end + }; - now = GNUNET_TIME_absolute_get (); - (void) GNUNET_TIME_round_abs (&now); - qs = GNUNET_PQ_eval_prepared_non_select (pg->conn, - "insert_withdraw_info", - params); - if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs) - { - GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); - return qs; - } - - /* update reserve balance */ - reserve.pub = collectable->reserve_pub; - if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != - (qs = reserves_get_internal (pg, - &reserve))) - { - /* Should have been checked before we got here... */ - GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); - if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) - qs = GNUNET_DB_STATUS_HARD_ERROR; - return qs; - } - if (0 > - TALER_amount_subtract (&reserve.balance, - &reserve.balance, - &collectable->amount_with_fee)) - { - /* The reserve history was checked to make sure there is enough of a balance - left before we tried this; however, concurrent operations may have changed - the situation by now, causing us to fail here. As reserves can no longer - be topped up, retrying should not help either. */ - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Withdrawal from reserve `%s' refused due to insufficient balance.\n", - TALER_B2S (&collectable->reserve_pub)); - return GNUNET_DB_STATUS_HARD_ERROR; - } gc = GNUNET_TIME_absolute_add (now, pg->legal_reserve_expiration_time); - reserve.gc = GNUNET_TIME_absolute_max (gc, - reserve.gc); - (void) GNUNET_TIME_round_abs (&reserve.gc); - qs = reserves_update (pg, - &reserve); - GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR != qs); - if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) - { - GNUNET_break (0); - qs = GNUNET_DB_STATUS_HARD_ERROR; - } - return qs; + (void) GNUNET_TIME_round_abs (&gc); + kyc->type = TALER_EXCHANGEDB_KYC_WITHDRAW; + return GNUNET_PQ_eval_prepared_singleton_select (pg->conn, + "call_withdraw", + params, + rs); + +} + + +/** + * Check that reserve remains below threshold for KYC + * checks after withdraw operation. + * + * @param cls the `struct PostgresClosure` with the plugin-specific state + * @param reserve_uuid reserve to check + * @param withdraw_start starting point to accumulate from + * @param upper_limit maximum amount allowed + * @param[out] below_limit set to true if the limit was not exceeded + * @return query execution status + */ +static enum GNUNET_DB_QueryStatus +postgres_do_withdraw_limit_check ( + void *cls, + uint64_t reserve_uuid, + struct GNUNET_TIME_Absolute withdraw_start, + const struct TALER_Amount *upper_limit, + bool *below_limit) +{ + struct PostgresClosure *pg = cls; + struct GNUNET_PQ_QueryParam params[] = { + GNUNET_PQ_query_param_uint64 (&reserve_uuid), + TALER_PQ_query_param_absolute_time (&withdraw_start), + TALER_PQ_query_param_amount (upper_limit), + GNUNET_PQ_query_param_end + }; + struct GNUNET_PQ_ResultSpec rs[] = { + GNUNET_PQ_result_spec_bool ("below_limit", + below_limit), + GNUNET_PQ_result_spec_end + }; + + return GNUNET_PQ_eval_prepared_singleton_select (pg->conn, + "call_withdraw_limit_check", + params, + rs); } @@ -4587,11 +4617,21 @@ struct ReserveHistoryContext */ struct PostgresClosure *pg; + /** + * Sum of all credit transactions. + */ + struct TALER_Amount balance_in; + + /** + * Sum of all debit transactions. + */ + struct TALER_Amount balance_out; + /** * Set to #GNUNET_SYSERR on serious internal errors during * the callbacks. */ - int status; + enum GNUNET_GenericReturnValue status; }; @@ -4667,6 +4707,10 @@ add_bank_to_exchange (void *cls, return; } } + GNUNET_assert (0 <= + TALER_amount_add (&rhc->balance_in, + &rhc->balance_in, + &bt->amount)); bt->reserve_pub = *rhc->reserve_pub; tail = append_rh (rhc); tail->type = TALER_EXCHANGEDB_RO_BANK_TO_EXCHANGE; @@ -4724,6 +4768,10 @@ add_withdraw_coin (void *cls, return; } } + GNUNET_assert (0 <= + TALER_amount_add (&rhc->balance_out, + &rhc->balance_out, + &cbc->amount_with_fee)); cbc->reserve_pub = *rhc->reserve_pub; tail = append_rh (rhc); tail->type = TALER_EXCHANGEDB_RO_WITHDRAW_COIN; @@ -4784,6 +4832,10 @@ add_recoup (void *cls, return; } } + GNUNET_assert (0 <= + TALER_amount_add (&rhc->balance_in, + &rhc->balance_in, + &recoup->value)); recoup->reserve_pub = *rhc->reserve_pub; tail = append_rh (rhc); tail->type = TALER_EXCHANGEDB_RO_RECOUP_COIN; @@ -4840,6 +4892,10 @@ add_exchange_to_bank (void *cls, return; } } + GNUNET_assert (0 <= + TALER_amount_add (&rhc->balance_out, + &rhc->balance_out, + &closing->amount)); closing->reserve_pub = *rhc->reserve_pub; tail = append_rh (rhc); tail->type = TALER_EXCHANGEDB_RO_EXCHANGE_TO_BANK; @@ -4854,12 +4910,14 @@ add_exchange_to_bank (void *cls, * * @param cls the `struct PostgresClosure` with the plugin-specific state * @param reserve_pub public key of the reserve + * @param[out] balance set to the reserve balance * @param[out] rhp set to known transaction history (NULL if reserve is unknown) * @return transaction status */ static enum GNUNET_DB_QueryStatus postgres_get_reserve_history (void *cls, const struct TALER_ReservePublicKeyP *reserve_pub, + struct TALER_Amount *balance, struct TALER_EXCHANGEDB_ReserveHistory **rhp) { struct PostgresClosure *pg = cls; @@ -4902,6 +4960,12 @@ postgres_get_reserve_history (void *cls, rhc.rh_tail = NULL; rhc.pg = pg; rhc.status = GNUNET_OK; + GNUNET_assert (GNUNET_OK == + TALER_amount_set_zero (pg->currency, + &rhc.balance_in)); + GNUNET_assert (GNUNET_OK == + TALER_amount_set_zero (pg->currency, + &rhc.balance_out)); qs = GNUNET_DB_STATUS_SUCCESS_NO_RESULTS; /* make static analysis happy */ for (unsigned int i = 0; NULL != work[i].cb; i++) { @@ -4927,6 +4991,10 @@ postgres_get_reserve_history (void *cls, } } *rhp = rhc.rh; + GNUNET_assert (0 <= + TALER_amount_subtract (balance, + &rhc.balance_in, + &rhc.balance_out)); return qs; } @@ -5308,13 +5376,12 @@ postgres_get_ready_deposit (void *cls, void *deposit_cb_cls) { struct PostgresClosure *pg = cls; - uint8_t kyc_override = (kyc_off) ? 1 : 0; struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get (); struct GNUNET_PQ_QueryParam params[] = { TALER_PQ_query_param_absolute_time (&now), GNUNET_PQ_query_param_uint64 (&start_shard_row), GNUNET_PQ_query_param_uint64 (&end_shard_row), - GNUNET_PQ_query_param_auto_from_type (&kyc_override), + GNUNET_PQ_query_param_bool (kyc_off), GNUNET_PQ_query_param_end }; struct TALER_Amount amount_with_fee; @@ -5701,16 +5768,24 @@ postgres_ensure_coin_known (void *cls, &denom_pub_hash), GNUNET_PQ_result_spec_end }; -#if EXPLICIT_LOCKS - struct GNUNET_PQ_QueryParam no_params[] = { - GNUNET_PQ_query_param_end - }; - if (0 > (qs = GNUNET_PQ_eval_prepared_non_select (pg->conn, - "lock_known_coins", - no_params))) - return qs; -#endif + /* First, try to simply insert it */ + qs = insert_known_coin (pg, + coin); + switch (qs) + { + case GNUNET_DB_STATUS_HARD_ERROR: + GNUNET_break (0); + return TALER_EXCHANGEDB_CKS_HARD_FAIL; + case GNUNET_DB_STATUS_SOFT_ERROR: + return TALER_EXCHANGEDB_CKS_SOFT_FAIL; + case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: + /* continued below */ + break; + case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: + return TALER_EXCHANGEDB_CKS_ADDED; + } + /* check if the coin is already known */ qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn, "get_known_coin_dh", @@ -5729,26 +5804,13 @@ postgres_ensure_coin_known (void *cls, GNUNET_break_op (0); return TALER_EXCHANGEDB_CKS_CONFLICT; case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: - break; - } - - /* if not known, insert it */ - qs = insert_known_coin (pg, - coin); - switch (qs) - { - case GNUNET_DB_STATUS_HARD_ERROR: + /* should be impossible */ GNUNET_break (0); return TALER_EXCHANGEDB_CKS_HARD_FAIL; - case GNUNET_DB_STATUS_SOFT_ERROR: - return TALER_EXCHANGEDB_CKS_SOFT_FAIL; - case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: - GNUNET_break (0); - return TALER_EXCHANGEDB_CKS_HARD_FAIL; - case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: - break; } - return TALER_EXCHANGEDB_CKS_ADDED; + /* we should never get here */ + GNUNET_break (0); + return TALER_EXCHANGEDB_CKS_HARD_FAIL; } @@ -6609,7 +6671,6 @@ add_coin_deposit (void *cls, chc->have_deposit_or_melt = true; deposit = GNUNET_new (struct TALER_EXCHANGEDB_DepositListEntry); { - uint8_t done = 0; struct GNUNET_PQ_ResultSpec rs[] = { TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee", &deposit->amount_with_fee), @@ -6636,7 +6697,7 @@ add_coin_deposit (void *cls, GNUNET_PQ_result_spec_uint64 ("deposit_serial_id", &serial_id), GNUNET_PQ_result_spec_auto_from_type ("done", - &done), + &deposit->done), GNUNET_PQ_result_spec_end }; @@ -6650,7 +6711,6 @@ add_coin_deposit (void *cls, chc->failed = true; return; } - deposit->done = (0 != done); } tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList); tl->next = chc->head; @@ -7340,7 +7400,6 @@ postgres_lookup_transfer_by_deposit ( /* Check if transaction exists in deposits, so that we just do not have a WTID yet. In that case, return without wtid (by setting 'pending' true). */ - uint8_t ok8 = 0; struct GNUNET_PQ_ResultSpec rs2[] = { GNUNET_PQ_result_spec_auto_from_type ("wire_salt", &wire_salt), @@ -7349,7 +7408,7 @@ postgres_lookup_transfer_by_deposit ( GNUNET_PQ_result_spec_uint64 ("payment_target_uuid", &kyc->payment_target_uuid), GNUNET_PQ_result_spec_auto_from_type ("kyc_ok", - &ok8), + &kyc->ok), TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee", amount_with_fee), TALER_PQ_RESULT_SPEC_AMOUNT ("fee_deposit", @@ -7377,7 +7436,6 @@ postgres_lookup_transfer_by_deposit ( return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS; } kyc->type = TALER_EXCHANGEDB_KYC_DEPOSIT; - kyc->ok = (0 != ok8); return qs; } } @@ -8164,7 +8222,7 @@ deposit_serial_helper_cb (void *cls, struct TALER_EXCHANGEDB_Deposit deposit; struct GNUNET_TIME_Absolute exchange_timestamp; struct TALER_DenominationPublicKey denom_pub; - uint8_t done = 0; + bool done; uint64_t rowid; struct GNUNET_PQ_ResultSpec rs[] = { TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee", @@ -8191,8 +8249,8 @@ deposit_serial_helper_cb (void *cls, &deposit.wire_salt), GNUNET_PQ_result_spec_string ("receiver_wire_account", &deposit.receiver_wire_account), - GNUNET_PQ_result_spec_auto_from_type ("done", - &done), + GNUNET_PQ_result_spec_bool ("done", + &done), GNUNET_PQ_result_spec_uint64 ("deposit_serial_id", &rowid), GNUNET_PQ_result_spec_end @@ -8213,7 +8271,7 @@ deposit_serial_helper_cb (void *cls, exchange_timestamp, &deposit, &denom_pub, - (0 != done) ? true : false); + done); GNUNET_PQ_cleanup_result (rs); if (GNUNET_OK != ret) break; @@ -9783,8 +9841,8 @@ missing_wire_cb (void *cls, struct TALER_Amount amount; char *payto_uri; struct GNUNET_TIME_Absolute deadline; - uint8_t tiny; - uint8_t done; + bool tiny; + bool done; struct GNUNET_PQ_ResultSpec rs[] = { GNUNET_PQ_result_spec_uint64 ("deposit_serial_id", &rowid), @@ -9796,10 +9854,10 @@ missing_wire_cb (void *cls, &payto_uri), TALER_PQ_result_spec_absolute_time ("wire_deadline", &deadline), - GNUNET_PQ_result_spec_auto_from_type ("tiny", - &tiny), - GNUNET_PQ_result_spec_auto_from_type ("done", - &done), + GNUNET_PQ_result_spec_bool ("tiny", + &tiny), + GNUNET_PQ_result_spec_bool ("done", + &done), GNUNET_PQ_result_spec_end }; @@ -9923,22 +9981,18 @@ postgres_lookup_auditor_status ( GNUNET_PQ_query_param_auto_from_type (auditor_pub), GNUNET_PQ_query_param_end }; - uint8_t enabled8 = 0; struct GNUNET_PQ_ResultSpec rs[] = { GNUNET_PQ_result_spec_string ("auditor_url", auditor_url), - GNUNET_PQ_result_spec_auto_from_type ("is_active", - &enabled8), + GNUNET_PQ_result_spec_bool ("is_active", + enabled), GNUNET_PQ_result_spec_end }; - enum GNUNET_DB_QueryStatus qs; - qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn, - "lookup_auditor_status", - params, - rs); - *enabled = (0 != enabled8); - return qs; + return GNUNET_PQ_eval_prepared_singleton_select (pg->conn, + "lookup_auditor_status", + params, + rs); } @@ -9996,12 +10050,11 @@ postgres_update_auditor (void *cls, bool enabled) { struct PostgresClosure *pg = cls; - uint8_t enabled8 = enabled ? 1 : 0; struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_auto_from_type (auditor_pub), GNUNET_PQ_query_param_string (auditor_url), GNUNET_PQ_query_param_string (auditor_name), - GNUNET_PQ_query_param_auto_from_type (&enabled8), + GNUNET_PQ_query_param_bool (enabled), GNUNET_PQ_query_param_absolute_time (&change_date), GNUNET_PQ_query_param_end }; @@ -10091,10 +10144,9 @@ postgres_update_wire (void *cls, bool enabled) { struct PostgresClosure *pg = cls; - uint8_t enabled8 = enabled ? 1 : 0; struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_string (payto_uri), - GNUNET_PQ_query_param_auto_from_type (&enabled8), + GNUNET_PQ_query_param_bool (enabled), GNUNET_PQ_query_param_absolute_time (&change_date), GNUNET_PQ_query_param_end }; @@ -11767,7 +11819,9 @@ libtaler_plugin_exchangedb_postgres_init (void *cls) plugin->get_latest_reserve_in_reference = &postgres_get_latest_reserve_in_reference; plugin->get_withdraw_info = &postgres_get_withdraw_info; - plugin->insert_withdraw_info = &postgres_insert_withdraw_info; + // plugin->insert_withdraw_info = &postgres_insert_withdraw_info; + plugin->do_withdraw = &postgres_do_withdraw; + plugin->do_withdraw_limit_check = &postgres_do_withdraw_limit_check; plugin->get_reserve_history = &postgres_get_reserve_history; plugin->select_withdraw_amounts_by_account = &postgres_select_withdraw_amounts_by_account; diff --git a/src/exchangedb/test_exchangedb.c b/src/exchangedb/test_exchangedb.c index 6807c2425..ce943226b 100644 --- a/src/exchangedb/test_exchangedb.c +++ b/src/exchangedb/test_exchangedb.c @@ -1659,10 +1659,26 @@ run (void *cls) cbc.reserve_pub = reserve_pub; cbc.amount_with_fee = value; GNUNET_assert (GNUNET_OK == - TALER_amount_set_zero (CURRENCY, &cbc.withdraw_fee)); - FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != - plugin->insert_withdraw_info (plugin->cls, - &cbc)); + TALER_amount_set_zero (CURRENCY, + &cbc.withdraw_fee)); + { + bool found; + bool balance_ok; + struct TALER_EXCHANGEDB_KycStatus kyc; + uint64_t ruuid; + + FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != + plugin->do_withdraw (plugin->cls, + &cbc, + now, + &found, + &balance_ok, + &kyc, + &ruuid)); + GNUNET_assert (found); + GNUNET_assert (balance_ok); + GNUNET_assert (! kyc.ok); + } FAILIF (GNUNET_OK != check_reserve (&reserve_pub, value.value, @@ -1780,9 +1796,14 @@ run (void *cls) value.currency)); result = 7; - qs = plugin->get_reserve_history (plugin->cls, - &reserve_pub, - &rh); + { + struct TALER_Amount balance; + + qs = plugin->get_reserve_history (plugin->cls, + &reserve_pub, + &balance, + &rh); + } FAILIF (0 > qs); FAILIF (NULL == rh); rh_head = rh; diff --git a/src/include/taler_exchangedb_plugin.h b/src/include/taler_exchangedb_plugin.h index 9a1dc78b6..fd2f3dc42 100644 --- a/src/include/taler_exchangedb_plugin.h +++ b/src/include/taler_exchangedb_plugin.h @@ -496,7 +496,7 @@ struct TALER_EXCHANGEDB_ClosingTransfer struct TALER_ReservePublicKeyP reserve_pub; /** - * Amount that was transferred to the exchange. + * Amount that was transferred from the exchange. */ struct TALER_Amount amount; @@ -2512,23 +2512,70 @@ struct TALER_EXCHANGEDB_Plugin * @return statement execution status */ enum GNUNET_DB_QueryStatus - (*insert_withdraw_info)( + (*insert_withdraw_infoXX)( void *cls, const struct TALER_EXCHANGEDB_CollectableBlindcoin *collectable); + /** + * Perform withdraw operation, checking for sufficient balance + * and possibly persisting the withdrawal details. + * + * @param cls the `struct PostgresClosure` with the plugin-specific state + * @param collectable corresponding collectable coin (blind signature) + * if a coin is found + * @param now current time (rounded) + * @param[out] found set to true if the reserve was found + * @param[out] balance_ok set to true if the balance was sufficient + * @param[out] kyc set to the KYC status of the reserve + * @param[out] reserve_uuid set to the UUID of the reserve + * @return query execution status + */ + enum GNUNET_DB_QueryStatus + (*do_withdraw)( + void *cls, + const struct TALER_EXCHANGEDB_CollectableBlindcoin *collectable, + struct GNUNET_TIME_Absolute now, + bool *found, + bool *balance_ok, + struct TALER_EXCHANGEDB_KycStatus *kyc_ok, + uint64_t *reserve_uuid); + + + /** + * Check that reserve remains below threshold for KYC + * checks after withdraw operation. + * + * @param cls the `struct PostgresClosure` with the plugin-specific state + * @param reserve_uuid reserve to check + * @param withdraw_start starting point to accumulate from + * @param upper_limit maximum amount allowed + * @param[out] below_limit set to true if the limit was not exceeded + * @return query execution status + */ + enum GNUNET_DB_QueryStatus + (*do_withdraw_limit_check)( + void *cls, + uint64_t reserve_uuid, + struct GNUNET_TIME_Absolute withdraw_start, + const struct TALER_Amount *upper_limit, + bool *below_limit); + + /** * Get all of the transaction history associated with the specified * reserve. * * @param cls the @e cls of this struct with the plugin-specific state * @param reserve_pub public key of the reserve + * @param[out] balance set to the reserve balance * @param[out] rhp set to known transaction history (NULL if reserve is unknown) * @return transaction status */ enum GNUNET_DB_QueryStatus (*get_reserve_history)(void *cls, const struct TALER_ReservePublicKeyP *reserve_pub, + struct TALER_Amount *balance, struct TALER_EXCHANGEDB_ReserveHistory **rhp); diff --git a/src/include/taler_extensions.h b/src/include/taler_extensions.h index c4e9d1f5a..b02edf682 100644 --- a/src/include/taler_extensions.h +++ b/src/include/taler_extensions.h @@ -46,8 +46,9 @@ enum TALER_EXTENSION_ReturnValue * The default age mask represents the age groups * 0-7, 8-9, 10-11, 12-13, 14-15, 16-17, 18-20, 21-... */ -#define TALER_EXTENSION_DEFAULT_AGE_MASK (1 | 1 << 8 | 1 << 10 | 1 << 12 | 1 << \ - 14 | 1 << 16 | 1 << 18 | 1 << 21) +#define TALER_EXTENSION_DEFAULT_AGE_MASK (1 | 1 << 8 | 1 << 10 | 1 << 12 | 1 \ + << 14 | 1 << 16 | 1 << 18 | 1 \ + << 21) /** * @param groups String representation of age groups, like: "8:10:12:14:16:18:21" diff --git a/src/lib/exchange_api_common.c b/src/lib/exchange_api_common.c index 38c4ce1a3..4f6588a20 100644 --- a/src/lib/exchange_api_common.c +++ b/src/lib/exchange_api_common.c @@ -767,6 +767,8 @@ TALER_EXCHANGE_verify_coin_history ( else if (0 == strcasecmp (type, "RECOUP-REFRESH")) { + /* This is the coin that was subjected to a recoup, + the value being credited to the old coin. */ struct TALER_RecoupRefreshConfirmationPS pc = { .purpose.size = htonl (sizeof (pc)), .purpose.purpose = htonl ( @@ -836,6 +838,8 @@ TALER_EXCHANGE_verify_coin_history ( else if (0 == strcasecmp (type, "OLD-COIN-RECOUP")) { + /* This is the coin that was credited in a recoup, + the value being credited to the this coin. */ struct TALER_RecoupRefreshConfirmationPS pc = { .purpose.size = htonl (sizeof (pc)), .purpose.purpose = htonl ( @@ -878,7 +882,7 @@ TALER_EXCHANGE_verify_coin_history ( GNUNET_break_op (0); return GNUNET_SYSERR; } - add = GNUNET_YES; + add = GNUNET_NO; } else { @@ -911,7 +915,8 @@ TALER_EXCHANGE_verify_coin_history ( However, for the implementation, we first *add* up all of these negative amounts, as we might get refunds before deposits from a semi-evil exchange. Then, at the end, we do - the subtraction by calculating "total = total - rtotal" */GNUNET_assert (GNUNET_NO == add); + the subtraction by calculating "total = total - rtotal" */ + GNUNET_assert (GNUNET_NO == add); if (0 > TALER_amount_add (&rtotal, &rtotal, diff --git a/src/lib/exchange_api_deposit.c b/src/lib/exchange_api_deposit.c index bb935514b..8b93f56ef 100644 --- a/src/lib/exchange_api_deposit.c +++ b/src/lib/exchange_api_deposit.c @@ -629,8 +629,8 @@ TALER_EXCHANGE_deposit ( &h_wire, h_contract_terms, (NULL != extension_details) - ? &ech - : NULL, + ? &ech + : NULL, coin_pub, denom_sig, denom_pub, diff --git a/src/util/crypto_helper_rsa.c b/src/util/crypto_helper_rsa.c index 85741d5e5..3138ebf3e 100644 --- a/src/util/crypto_helper_rsa.c +++ b/src/util/crypto_helper_rsa.c @@ -83,6 +83,8 @@ try_connect (struct TALER_CRYPTO_RsaDenominationHelper *dh) { if (-1 != dh->sock) return GNUNET_OK; + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Establishing connection!\n"); dh->sock = socket (AF_UNIX, SOCK_STREAM, 0); @@ -103,6 +105,7 @@ try_connect (struct TALER_CRYPTO_RsaDenominationHelper *dh) do_disconnect (dh); return GNUNET_SYSERR; } + TALER_CRYPTO_helper_rsa_poll (dh); return GNUNET_OK; } @@ -153,7 +156,6 @@ TALER_CRYPTO_helper_rsa_connect ( TALER_CRYPTO_helper_rsa_disconnect (dh); return NULL; } - TALER_CRYPTO_helper_rsa_poll (dh); return dh; } @@ -397,6 +399,8 @@ TALER_CRYPTO_helper_rsa_sign ( .cipher = TALER_DENOMINATION_INVALID }; + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Starting signature process\n"); if (GNUNET_OK != try_connect (dh)) { @@ -406,6 +410,8 @@ TALER_CRYPTO_helper_rsa_sign ( return ds; } + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Requesting signature\n"); { char buf[sizeof (struct TALER_CRYPTO_SignRequest) + msg_size]; struct TALER_CRYPTO_SignRequest *sr @@ -431,6 +437,8 @@ TALER_CRYPTO_helper_rsa_sign ( } } + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Awaiting reply\n"); { char buf[UINT16_MAX]; size_t off = 0; @@ -512,6 +520,8 @@ more: *ec = TALER_EC_EXCHANGE_DENOMINATION_HELPER_BUG; goto end; } + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Received signature\n"); *ec = TALER_EC_NONE; finished = true; ds.cipher = TALER_DENOMINATION_RSA; @@ -531,10 +541,14 @@ more: (const struct TALER_CRYPTO_SignFailure *) buf; *ec = (enum TALER_ErrorCode) ntohl (sf->ec); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Signing failed!\n"); finished = true; break; } case TALER_HELPER_RSA_MT_AVAIL: + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Received new key!\n"); if (GNUNET_OK != handle_mt_avail (dh, hdr)) @@ -546,6 +560,8 @@ more: } break; /* while(1) loop ensures we recvfrom() again */ case TALER_HELPER_RSA_MT_PURGE: + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Received revocation!\n"); if (GNUNET_OK != handle_mt_purge (dh, hdr)) diff --git a/src/util/secmod_common.c b/src/util/secmod_common.c index 0a83bfb6c..6f3423869 100644 --- a/src/util/secmod_common.c +++ b/src/util/secmod_common.c @@ -78,7 +78,7 @@ TES_transmit_raw (int sock, size_t end, const void *pos) { - ssize_t off = 0; + size_t off = 0; GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Sending message of length %u\n", diff --git a/src/util/taler-exchange-secmod-rsa.c b/src/util/taler-exchange-secmod-rsa.c index 43109b5a4..3b06a56ed 100644 --- a/src/util/taler-exchange-secmod-rsa.c +++ b/src/util/taler-exchange-secmod-rsa.c @@ -388,6 +388,11 @@ handle_sign_request (struct TES_Client *client, GNUNET_YES)); ret = TES_transmit (client->csock, &sr->header); + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Sent RSA signature after %s\n", + GNUNET_STRINGS_relative_time_to_string ( + GNUNET_TIME_absolute_get_duration (now), + GNUNET_YES)); GNUNET_free (sr); return ret; }