diff options
-rw-r--r-- | src/exchange/taler-exchange-httpd.c | 1 | ||||
-rw-r--r-- | src/exchange/taler-exchange-httpd_batch-deposit.c | 896 | ||||
-rw-r--r-- | src/exchange/taler-exchange-httpd_batch-deposit.h | 8 | ||||
-rw-r--r-- | src/exchange/taler-exchange-httpd_config.h | 2 | ||||
-rw-r--r-- | src/exchangedb/Makefile.am | 1 | ||||
-rw-r--r-- | src/exchangedb/pg_select_deposit_amounts_for_kyc_check.c | 153 | ||||
-rw-r--r-- | src/exchangedb/pg_select_deposit_amounts_for_kyc_check.h | 51 | ||||
-rw-r--r-- | src/exchangedb/pg_select_merge_amounts_for_kyc_check.c | 1 | ||||
-rw-r--r-- | src/exchangedb/plugin_exchangedb_postgres.c | 3 | ||||
-rw-r--r-- | src/include/taler_exchangedb_plugin.h | 22 | ||||
-rw-r--r-- | src/kyclogic/kyclogic_api.c | 6 | ||||
-rw-r--r-- | src/lib/exchange_api_handle.c | 21 |
12 files changed, 919 insertions, 246 deletions
diff --git a/src/exchange/taler-exchange-httpd.c b/src/exchange/taler-exchange-httpd.c index 36c567ebd..52e8ef6af 100644 --- a/src/exchange/taler-exchange-httpd.c +++ b/src/exchange/taler-exchange-httpd.c @@ -2585,6 +2585,7 @@ do_shutdown (void *cls) my_mhd = TALER_MHD_daemon_stop (); TEH_resume_keys_requests (true); + TEH_batch_deposit_cleanup (); TEH_age_withdraw_cleanup (); TEH_batch_withdraw_cleanup (); TEH_reserves_close_cleanup (); diff --git a/src/exchange/taler-exchange-httpd_batch-deposit.c b/src/exchange/taler-exchange-httpd_batch-deposit.c index 84f27dd94..b18acd8aa 100644 --- a/src/exchange/taler-exchange-httpd_batch-deposit.c +++ b/src/exchange/taler-exchange-httpd_batch-deposit.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2014-2023 Taler Systems SA + Copyright (C) 2014-2024 Taler Systems SA TALER is free software; you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software @@ -31,6 +31,7 @@ #include "taler_extensions_policy.h" #include "taler_json_lib.h" #include "taler_mhd_lib.h" +#include "taler-exchange-httpd_common_kyc.h" #include "taler-exchange-httpd_batch-deposit.h" #include "taler-exchange-httpd_responses.h" #include "taler_exchangedb_lib.h" @@ -44,34 +45,34 @@ struct BatchDepositContext { /** - * Array with the individual coin deposit fees. + * Kept in a DLL. */ - struct TALER_Amount *deposit_fees; + struct BatchDepositContext *next; /** - * Our timestamp (when we received the request). - * Possibly updated by the transaction if the - * request is idempotent (was repeated). + * Kept in a DLL. */ - struct GNUNET_TIME_Timestamp exchange_timestamp; + struct BatchDepositContext *prev; /** - * Details about the batch deposit operation. + * The request we are working on. */ - struct TALER_EXCHANGEDB_BatchDeposit bd; + struct TEH_RequestContext *rc; + /** + * Handle for the legitimization check. + */ + struct TEH_LegitimizationCheckHandle *lch; /** - * Total amount that is accumulated with this deposit, - * without fee. + * Array with the individual coin deposit fees. */ - struct TALER_Amount accumulated_total_without_fee; + struct TALER_Amount *deposit_fees; /** - * True, if no policy was present in the request. Then - * @e policy_json is NULL and @e h_policy will be all zero. + * Information about deposited coins. */ - bool has_no_policy; + struct TALER_EXCHANGEDB_CoinDepositInformation *cdis; /** * Additional details for policy extension relevant for this @@ -79,12 +80,16 @@ struct BatchDepositContext */ json_t *policy_json; + /** - * If @e policy_json was present, the corresponding policy extension - * calculates these details. These will be persisted in the policy_details - * table. + * Response to return, if set. */ - struct TALER_PolicyDetails policy_details; + struct MHD_Response *response; + + /** + * KYC status of the reserve used for the operation. + */ + struct TALER_EXCHANGEDB_KycStatus kyc; /** * Hash over @e policy_details, might be all zero @@ -102,63 +107,161 @@ struct BatchDepositContext */ uint64_t policy_details_serial_id; + /** + * Our timestamp (when we received the request). + * Possibly updated by the transaction if the + * request is idempotent (was repeated). + */ + struct GNUNET_TIME_Timestamp exchange_timestamp; + + /** + * Total amount that is accumulated with this deposit, + * without fee. + */ + struct TALER_Amount accumulated_total_without_fee; + + /** + * Details about the batch deposit operation. + */ + struct TALER_EXCHANGEDB_BatchDeposit bd; + + /** + * If @e policy_json was present, the corresponding policy extension + * calculates these details. These will be persisted in the policy_details + * table. + */ + struct TALER_PolicyDetails policy_details; + + /** + * HTTP status to return with @e response, or 0. + */ + unsigned int http_status; + + /** + * Our current state in the state machine. + */ + enum + { + BDC_PHASE_INIT = 0, + BDC_PHASE_PARSE = 1, + BDC_PHASE_POLICY = 2, + BDC_PHASE_KYC = 3, + BDC_PHASE_TRANSACT = 4, + BDC_PHASE_REPLY_SUCCESS = 5, + BDC_PHASE_SUSPENDED, + BDC_PHASE_CHECK_KYC_RESULT, + BDC_PHASE_GENERATE_REPLY_FAILURE, + BDC_PHASE_RETURN_YES, + BDC_PHASE_RETURN_NO, + } phase; + + /** + * True, if no policy was present in the request. Then + * @e policy_json is NULL and @e h_policy will be all zero. + */ + bool has_no_policy; }; /** + * Head of list of suspended batch deposit operations. + */ +static struct BatchDepositContext *bdc_head; + +/** + * Tail of list of suspended batch deposit operations. + */ +static struct BatchDepositContext *bdc_tail; + + +void +TEH_batch_deposit_cleanup () +{ + struct BatchDepositContext *bdc; + + while (NULL != (bdc = bdc_head)) + { + GNUNET_assert (BDC_PHASE_SUSPENDED == bdc->phase); + bdc->phase = BDC_PHASE_RETURN_NO; + MHD_resume_connection (bdc->rc->connection); + GNUNET_CONTAINER_DLL_remove (bdc_head, + bdc_tail, + bdc); + } +} + + +/** + * Terminate the main loop by returning the final + * result. + * + * @param[in,out] bdc context to update phase for + * @param mres MHD status to return + */ +static void +finish_loop (struct BatchDepositContext *bdc, + MHD_RESULT mres) +{ + bdc->phase = (MHD_YES == mres) + ? BDC_PHASE_RETURN_YES + : BDC_PHASE_RETURN_NO; +} + + +/** * Send confirmation of batch deposit success to client. This function will * create a signed message affirming the given information and return it to * the client. By this, the exchange affirms that the coins had sufficient * (residual) value for the specified transaction and that it will execute the * requested batch deposit operation with the given wiring details. * - * @param connection connection to the client - * @param dc information about the batch deposit - * @return MHD result code + * @param[in,out] bdc information about the batch deposit */ -static MHD_RESULT -reply_batch_deposit_success ( - struct MHD_Connection *connection, - const struct BatchDepositContext *dc) +static void +bdc_phase_reply_success ( + struct BatchDepositContext *bdc) { - const struct TALER_EXCHANGEDB_BatchDeposit *bd = &dc->bd; + const struct TALER_EXCHANGEDB_BatchDeposit *bd = &bdc->bd; const struct TALER_CoinSpendSignatureP *csigs[GNUNET_NZL (bd->num_cdis)]; enum TALER_ErrorCode ec; struct TALER_ExchangePublicKeyP pub; struct TALER_ExchangeSignatureP sig; - for (unsigned int i = 0; i<bd->num_cdis; i++) + for (unsigned int i = 0; i<bdc->bd.num_cdis; i++) csigs[i] = &bd->cdis[i].csig; if (TALER_EC_NONE != (ec = TALER_exchange_online_deposit_confirmation_sign ( &TEH_keys_exchange_sign_, &bd->h_contract_terms, - &dc->h_wire, - dc->has_no_policy ? NULL : &dc->h_policy, - dc->exchange_timestamp, + &bdc->h_wire, + bdc->has_no_policy ? NULL : &bdc->h_policy, + bdc->exchange_timestamp, bd->wire_deadline, bd->refund_deadline, - &dc->accumulated_total_without_fee, + &bdc->accumulated_total_without_fee, bd->num_cdis, csigs, - &dc->bd.merchant_pub, + &bdc->bd.merchant_pub, &pub, &sig))) { GNUNET_break (0); - return TALER_MHD_reply_with_ec (connection, - ec, - NULL); + finish_loop (bdc, + TALER_MHD_reply_with_ec (bdc->rc->connection, + ec, + NULL)); + return; } - return TALER_MHD_REPLY_JSON_PACK ( - connection, - MHD_HTTP_OK, - GNUNET_JSON_pack_timestamp ("exchange_timestamp", - dc->exchange_timestamp), - GNUNET_JSON_pack_data_auto ("exchange_pub", - &pub), - GNUNET_JSON_pack_data_auto ("exchange_sig", - &sig)); + finish_loop (bdc, + TALER_MHD_REPLY_JSON_PACK ( + bdc->rc->connection, + MHD_HTTP_OK, + GNUNET_JSON_pack_timestamp ("exchange_timestamp", + bdc->exchange_timestamp), + GNUNET_JSON_pack_data_auto ("exchange_pub", + &pub), + GNUNET_JSON_pack_data_auto ("exchange_sig", + &sig))); } @@ -180,8 +283,8 @@ batch_deposit_transaction (void *cls, struct MHD_Connection *connection, MHD_RESULT *mhd_ret) { - struct BatchDepositContext *dc = cls; - const struct TALER_EXCHANGEDB_BatchDeposit *bd = &dc->bd; + struct BatchDepositContext *bdc = cls; + const struct TALER_EXCHANGEDB_BatchDeposit *bd = &bdc->bd; enum GNUNET_DB_QueryStatus qs = GNUNET_DB_STATUS_HARD_ERROR; uint32_t bad_balance_coin_index = UINT32_MAX; bool balance_ok; @@ -189,26 +292,26 @@ batch_deposit_transaction (void *cls, /* If the deposit has a policy associated to it, persist it. This will * insert or update the record. */ - if (! dc->has_no_policy) + if (! bdc->has_no_policy) { qs = TEH_plugin->persist_policy_details ( TEH_plugin->cls, - &dc->policy_details, - &dc->bd.policy_details_serial_id, - &dc->accumulated_total_without_fee, - &dc->policy_details.fulfillment_state); + &bdc->policy_details, + &bdc->bd.policy_details_serial_id, + &bdc->accumulated_total_without_fee, + &bdc->policy_details.fulfillment_state); if (qs < 0) return qs; - dc->bd.policy_blocked = - dc->policy_details.fulfillment_state != TALER_PolicyFulfillmentSuccess; + bdc->bd.policy_blocked = + bdc->policy_details.fulfillment_state != TALER_PolicyFulfillmentSuccess; } /* FIXME: replace by batch insert! */ - for (unsigned int i = 0; i<bd->num_cdis; i++) + for (unsigned int i = 0; i<bdc->bd.num_cdis; i++) { const struct TALER_EXCHANGEDB_CoinDepositInformation *cdi - = &bd->cdis[i]; + = &bdc->cdis[i]; uint64_t known_coin_id; qs = TEH_make_coin_known (&cdi->coin, @@ -226,7 +329,7 @@ batch_deposit_transaction (void *cls, qs = TEH_plugin->do_deposit ( TEH_plugin->cls, bd, - &dc->exchange_timestamp, + &bdc->exchange_timestamp, &balance_ok, &bad_balance_coin_index, &in_conflict); @@ -236,10 +339,11 @@ batch_deposit_transaction (void *cls, return qs; TALER_LOG_WARNING ( "Failed to store /batch-deposit information in database\n"); - *mhd_ret = TALER_MHD_reply_with_error (connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_GENERIC_DB_STORE_FAILED, - "batch-deposit"); + *mhd_ret = TALER_MHD_reply_with_error ( + connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_STORE_FAILED, + "batch-deposit"); return qs; } GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, @@ -261,10 +365,11 @@ batch_deposit_transaction (void *cls, { TALER_LOG_WARNING ( "Failed to retrieve conflicting contract details from database\n"); - *mhd_ret = TALER_MHD_reply_with_error (connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_GENERIC_DB_STORE_FAILED, - "batch-deposit"); + *mhd_ret = TALER_MHD_reply_with_error ( + connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_STORE_FAILED, + "batch-deposit"); return qs; } @@ -277,16 +382,16 @@ batch_deposit_transaction (void *cls, } if (! balance_ok) { - GNUNET_assert (bad_balance_coin_index < bd->num_cdis); + GNUNET_assert (bad_balance_coin_index < bdc->bd.num_cdis); GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "returning history of conflicting coin (%s)\n", - TALER_B2S (&bd->cdis[bad_balance_coin_index].coin.coin_pub)); + TALER_B2S (&bdc->cdis[bad_balance_coin_index].coin.coin_pub)); *mhd_ret = TEH_RESPONSE_reply_coin_insufficient_funds ( connection, TALER_EC_EXCHANGE_GENERIC_INSUFFICIENT_FUNDS, - &bd->cdis[bad_balance_coin_index].coin.denom_pub_hash, - &bd->cdis[bad_balance_coin_index].coin.coin_pub); + &bdc->cdis[bad_balance_coin_index].coin.denom_pub_hash, + &bdc->cdis[bad_balance_coin_index].coin.coin_pub); return GNUNET_DB_STATUS_HARD_ERROR; } TEH_METRICS_num_success[TEH_MT_SUCCESS_DEPOSIT]++; @@ -295,12 +400,288 @@ batch_deposit_transaction (void *cls, /** + * Run database transaction. + * + * @param[in,out] bdc request context + */ +static void +bdc_phase_transact (struct BatchDepositContext *bdc) +{ + MHD_RESULT mhd_ret; + + if (GNUNET_SYSERR == + TEH_plugin->preflight (TEH_plugin->cls)) + { + GNUNET_break (0); + finish_loop (bdc, + TALER_MHD_reply_with_error ( + bdc->rc->connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_START_FAILED, + "preflight failure")); + return; + } + + if (GNUNET_OK != + TEH_DB_run_transaction (bdc->rc->connection, + "execute batch deposit", + TEH_MT_REQUEST_BATCH_DEPOSIT, + &mhd_ret, + &batch_deposit_transaction, + bdc)) + { + finish_loop (bdc, + mhd_ret); + return; + } + bdc->phase++; +} + + +/** + * Check if the @a bdc is replayed and we already have an + * answer. If so, replay the existing answer and return the + * HTTP response. + * + * @param bdc parsed request data + * @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 BatchDepositContext *bdc) +{ +#if FIXME_PLACEHOLDER + const struct TEH_RequestContext *rc = bdc->rc; + + for (unsigned int i = 0; i<bwc->planchets_length; i++) + { + struct PlanchetContext *pc = &bwc->planchets[i]; + enum GNUNET_DB_QueryStatus qs; + struct TALER_EXCHANGEDB_CollectableBlindcoin collectable; + + qs = TEH_plugin->get_withdraw_info ( + TEH_plugin->cls, + &pc->collectable.h_coin_envelope, + &collectable); + if (0 > qs) + { + /* FIXME: soft error not handled correctly! */ + GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); + finish_loop (bwc, + TALER_MHD_reply_with_error ( + rc->connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_FETCH_FAILED, + "get_withdraw_info")); + return true; + } + if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) + return false; + pc->collectable = collectable; + } + /* generate idempotent reply */ + TEH_METRICS_num_requests[TEH_MT_REQUEST_IDEMPOTENT_BATCH_WITHDRAW]++; + bwc->phase = BDC_PHASE_GENERATE_REPLY_SUCCESS; + return true; +#else + GNUNET_break (0); // NOT IMPLEMENTED + return false; +#endif +} + + +/** + * Check the KYC result. + * + * @param bdc storage for request processing + */ +static void +bdc_phase_check_kyc_result (struct BatchDepositContext *bdc) +{ + /* return final positive response */ + if (! bdc->kyc.ok) + { + if (check_request_idempotent (bdc)) + { + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Request is idempotent!\n"); + return; + } + /* KYC required */ + finish_loop (bdc, + TEH_RESPONSE_reply_kyc_required ( + bdc->rc->connection, + &bdc->bd.wire_target_h_payto, + &bdc->kyc)); + return; + } + bdc->phase = BDC_PHASE_TRANSACT; +} + + +/** + * Function called with the result of a legitimization + * check. + * + * @param cls closure + * @param lcr legitimization check result + */ +static void +deposit_legi_cb ( + void *cls, + const struct TEH_LegitimizationCheckResult *lcr) +{ + struct BatchDepositContext *bdc = cls; + + bdc->lch = NULL; + GNUNET_assert (BDC_PHASE_SUSPENDED == + bdc->phase); + MHD_resume_connection (bdc->rc->connection); + GNUNET_CONTAINER_DLL_remove (bdc_head, + bdc_tail, + bdc); + TALER_MHD_daemon_trigger (); + if (NULL != lcr->response) + { + bdc->response = lcr->response; + bdc->http_status = lcr->http_status; + bdc->phase = BDC_PHASE_GENERATE_REPLY_FAILURE; + return; + } + bdc->kyc = lcr->kyc; + bdc->phase = BDC_PHASE_CHECK_KYC_RESULT; +} + + +/** + * Function called to iterate over KYC-relevant transaction amounts for a + * particular time range. Called within a database transaction, so must + * not start a new one. + * + * @param cls closure, identifies the event type and account to iterate + * over events for + * @param limit maximum time-range for which events should be fetched + * (timestamp in the past) + * @param cb function to call on each event found, events must be returned + * in reverse chronological order + * @param cb_cls closure for @a cb, of type struct AgeWithdrawContext + * @return transaction status + */ +static enum GNUNET_DB_QueryStatus +deposit_amount_cb ( + void *cls, + struct GNUNET_TIME_Absolute limit, + TALER_EXCHANGEDB_KycAmountCallback cb, + void *cb_cls) +{ + struct BatchDepositContext *bdc = cls; + enum GNUNET_GenericReturnValue ret; + enum GNUNET_DB_QueryStatus qs; + + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Signaling amount %s for KYC check during deposit\n", + TALER_amount2s (&bdc->accumulated_total_without_fee)); + ret = cb (cb_cls, + &bdc->accumulated_total_without_fee, + bdc->exchange_timestamp.abs_time); + GNUNET_break (GNUNET_SYSERR != ret); + if (GNUNET_OK != ret) + return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS; + qs = TEH_plugin->select_deposit_amounts_for_kyc_check ( + TEH_plugin->cls, + &bdc->bd.wire_target_h_payto, + limit, + cb, + cb_cls); + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Got %d additional transactions for this deposit and limit %llu\n", + qs, + (unsigned long long) limit.abs_value_us); + GNUNET_break (qs >= 0); + return qs; +} + + +/** + * Run KYC check. + * + * @param[in,out] bdc request context + */ +static void +bdc_phase_kyc (struct BatchDepositContext *bdc) +{ + if (GNUNET_YES != TEH_enable_kyc) + { + bdc->phase++; + return; + } + /* FIXME: this fails to check that the + merchant_pub used in this request + matches the registered public key */ + bdc->lch = TEH_legitimization_check ( + &bdc->rc->async_scope_id, + TALER_KYCLOGIC_KYC_TRIGGER_DEPOSIT, + bdc->bd.receiver_wire_account, + &bdc->bd.wire_target_h_payto, + NULL, + &deposit_amount_cb, + bdc, + &deposit_legi_cb, + bdc); + GNUNET_assert (NULL != bdc->lch); + GNUNET_CONTAINER_DLL_insert (bdc_head, + bdc_tail, + bdc); + MHD_suspend_connection (bdc->rc->connection); + bdc->phase = BDC_PHASE_SUSPENDED; +} + + +/** + * Handle policy. + * + * @param[in,out] bdc request context + */ +static void +bdc_phase_policy (struct BatchDepositContext *bdc) +{ + const char *error_hint = NULL; + + if (bdc->has_no_policy) + { + bdc->phase++; + return; + } + if (GNUNET_OK != + TALER_extensions_create_policy_details ( + TEH_currency, + bdc->policy_json, + &bdc->policy_details, + &error_hint)) + { + GNUNET_break_op (0); + finish_loop (bdc, + TALER_MHD_reply_with_error ( + bdc->rc->connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_EXCHANGE_DEPOSITS_POLICY_NOT_ACCEPTED, + error_hint)); + return; + } + + TALER_deposit_policy_hash (bdc->policy_json, + &bdc->h_policy); + bdc->phase++; +} + + +/** * Parse per-coin deposit information from @a jcoin * into @a deposit. Fill in generic information from * @a ctx. * - * @param connection connection we are handling - * @param dc information about the overall batch + * @param bdc information about the overall batch * @param jcoin coin data to parse * @param[out] cdi where to store the result * @param[out] deposit_fee where to write the deposit fee @@ -308,13 +689,12 @@ batch_deposit_transaction (void *cls, * #GNUNET_SYSERR on failure and no error could be returned */ static enum GNUNET_GenericReturnValue -parse_coin (struct MHD_Connection *connection, - const struct BatchDepositContext *dc, +parse_coin (const struct BatchDepositContext *bdc, json_t *jcoin, struct TALER_EXCHANGEDB_CoinDepositInformation *cdi, struct TALER_Amount *deposit_fee) { - const struct TALER_EXCHANGEDB_BatchDeposit *bd = &dc->bd; + const struct TALER_EXCHANGEDB_BatchDeposit *bd = &bdc->bd; struct GNUNET_JSON_Specification spec[] = { TALER_JSON_spec_amount ("contribution", TEH_currency, @@ -336,7 +716,7 @@ parse_coin (struct MHD_Connection *connection, enum GNUNET_GenericReturnValue res; if (GNUNET_OK != - (res = TALER_MHD_parse_json_data (connection, + (res = TALER_MHD_parse_json_data (bdc->rc->connection, jcoin, spec))) return res; @@ -345,9 +725,10 @@ parse_coin (struct MHD_Connection *connection, struct TEH_DenominationKey *dk; MHD_RESULT mret; - dk = TEH_keys_denomination_by_hash (&cdi->coin.denom_pub_hash, - connection, - &mret); + dk = TEH_keys_denomination_by_hash ( + &cdi->coin.denom_pub_hash, + bdc->rc->connection, + &mret); if (NULL == dk) { GNUNET_JSON_parse_free (spec); @@ -361,10 +742,11 @@ parse_coin (struct MHD_Connection *connection, GNUNET_break_op (0); GNUNET_JSON_parse_free (spec); return (MHD_YES == - TALER_MHD_reply_with_error (connection, - MHD_HTTP_BAD_REQUEST, - TALER_EC_EXCHANGE_GENERIC_AMOUNT_EXCEEDS_DENOMINATION_VALUE, - NULL)) + TALER_MHD_reply_with_error ( + bdc->rc->connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_EXCHANGE_GENERIC_AMOUNT_EXCEEDS_DENOMINATION_VALUE, + NULL)) ? GNUNET_NO : GNUNET_SYSERR; } @@ -374,7 +756,7 @@ parse_coin (struct MHD_Connection *connection, GNUNET_JSON_parse_free (spec); return (MHD_YES == TEH_RESPONSE_reply_expired_denom_pub_hash ( - connection, + bdc->rc->connection, &cdi->coin.denom_pub_hash, TALER_EC_EXCHANGE_GENERIC_DENOMINATION_EXPIRED, "DEPOSIT")) @@ -387,7 +769,7 @@ parse_coin (struct MHD_Connection *connection, GNUNET_JSON_parse_free (spec); return (MHD_YES == TEH_RESPONSE_reply_expired_denom_pub_hash ( - connection, + bdc->rc->connection, &cdi->coin.denom_pub_hash, TALER_EC_EXCHANGE_GENERIC_DENOMINATION_VALIDITY_IN_FUTURE, "DEPOSIT")) @@ -400,7 +782,7 @@ parse_coin (struct MHD_Connection *connection, GNUNET_JSON_parse_free (spec); return (MHD_YES == TEH_RESPONSE_reply_expired_denom_pub_hash ( - connection, + bdc->rc->connection, &cdi->coin.denom_pub_hash, TALER_EC_EXCHANGE_GENERIC_DENOMINATION_REVOKED, "DEPOSIT")) @@ -413,10 +795,11 @@ parse_coin (struct MHD_Connection *connection, /* denomination cipher and denomination signature cipher not the same */ GNUNET_JSON_parse_free (spec); return (MHD_YES == - TALER_MHD_reply_with_error (connection, - MHD_HTTP_BAD_REQUEST, - TALER_EC_EXCHANGE_GENERIC_CIPHER_MISMATCH, - NULL)) + TALER_MHD_reply_with_error ( + bdc->rc->connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_EXCHANGE_GENERIC_CIPHER_MISMATCH, + NULL)) ? GNUNET_NO : GNUNET_SYSERR; } @@ -441,10 +824,11 @@ parse_coin (struct MHD_Connection *connection, TALER_LOG_WARNING ("Invalid coin passed for /batch-deposit\n"); GNUNET_JSON_parse_free (spec); return (MHD_YES == - TALER_MHD_reply_with_error (connection, - MHD_HTTP_FORBIDDEN, - TALER_EC_EXCHANGE_DENOMINATION_SIGNATURE_INVALID, - NULL)) + TALER_MHD_reply_with_error ( + bdc->rc->connection, + MHD_HTTP_FORBIDDEN, + TALER_EC_EXCHANGE_DENOMINATION_SIGNATURE_INVALID, + NULL)) ? GNUNET_NO : GNUNET_SYSERR; } @@ -455,10 +839,11 @@ parse_coin (struct MHD_Connection *connection, GNUNET_break_op (0); GNUNET_JSON_parse_free (spec); return (MHD_YES == - TALER_MHD_reply_with_error (connection, - MHD_HTTP_BAD_REQUEST, - TALER_EC_EXCHANGE_DEPOSIT_NEGATIVE_VALUE_AFTER_FEE, - NULL)) + TALER_MHD_reply_with_error ( + bdc->rc->connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_EXCHANGE_DEPOSIT_NEGATIVE_VALUE_AFTER_FEE, + NULL)) ? GNUNET_NO : GNUNET_SYSERR; } @@ -468,13 +853,13 @@ parse_coin (struct MHD_Connection *connection, TALER_wallet_deposit_verify ( &cdi->amount_with_fee, deposit_fee, - &dc->h_wire, + &bdc->h_wire, &bd->h_contract_terms, &bd->wallet_data_hash, cdi->coin.no_age_commitment ? NULL : &cdi->coin.h_age_commitment, - NULL != dc->policy_json ? &dc->h_policy : NULL, + NULL != bdc->policy_json ? &bdc->h_policy : NULL, &cdi->coin.denom_pub_hash, bd->wallet_timestamp, &bd->merchant_pub, @@ -485,10 +870,11 @@ parse_coin (struct MHD_Connection *connection, TALER_LOG_WARNING ("Invalid signature on /batch-deposit request\n"); GNUNET_JSON_parse_free (spec); return (MHD_YES == - TALER_MHD_reply_with_error (connection, - MHD_HTTP_FORBIDDEN, - TALER_EC_EXCHANGE_DEPOSIT_COIN_SIGNATURE_INVALID, - TALER_B2S (&cdi->coin.coin_pub))) + TALER_MHD_reply_with_error ( + bdc->rc->connection, + MHD_HTTP_FORBIDDEN, + TALER_EC_EXCHANGE_DEPOSIT_COIN_SIGNATURE_INVALID, + TALER_B2S (&cdi->coin.coin_pub))) ? GNUNET_NO : GNUNET_SYSERR; } @@ -496,15 +882,19 @@ parse_coin (struct MHD_Connection *connection, } -MHD_RESULT -TEH_handler_batch_deposit (struct TEH_RequestContext *rc, - const json_t *root, - const char *const args[]) +/** + * Run processing phase that parses the request. + * + * @param[in,out] bdc request context + * @param root JSON object that was POSTed + */ +static void +bdc_phase_parse (struct BatchDepositContext *bdc, + const json_t *root) { - struct MHD_Connection *connection = rc->connection; - struct BatchDepositContext dc = { 0 }; - struct TALER_EXCHANGEDB_BatchDeposit *bd = &dc.bd; + struct TALER_EXCHANGEDB_BatchDeposit *bd = &bdc->bd; const json_t *coins; + const json_t *policy_json; bool no_refund_deadline = true; struct GNUNET_JSON_Specification spec[] = { TALER_JSON_spec_payto_uri ("merchant_payto_uri", @@ -522,9 +912,9 @@ TEH_handler_batch_deposit (struct TEH_RequestContext *rc, GNUNET_JSON_spec_array_const ("coins", &coins), GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_json ("policy", - &dc.policy_json), - &dc.has_no_policy), + GNUNET_JSON_spec_object_const ("policy", + &policy_json), + &bdc->has_no_policy), GNUNET_JSON_spec_timestamp ("timestamp", &bd->wallet_timestamp), GNUNET_JSON_spec_mark_optional ( @@ -536,24 +926,31 @@ TEH_handler_batch_deposit (struct TEH_RequestContext *rc, GNUNET_JSON_spec_end () }; - (void) args; { enum GNUNET_GenericReturnValue res; - res = TALER_MHD_parse_json_data (connection, + res = TALER_MHD_parse_json_data (bdc->rc->connection, root, spec); if (GNUNET_SYSERR == res) { + /* hard failure */ GNUNET_break (0); - return MHD_NO; /* hard failure */ + finish_loop (bdc, + MHD_NO); + return; } if (GNUNET_NO == res) { + /* failure */ GNUNET_break_op (0); - return MHD_YES; /* failure */ + finish_loop (bdc, + MHD_YES); + return; } } + bdc->policy_json + = json_incref ((json_t *) policy_json); /* validate merchant's wire details (as far as we can) */ { @@ -566,12 +963,14 @@ TEH_handler_batch_deposit (struct TEH_RequestContext *rc, GNUNET_break_op (0); GNUNET_JSON_parse_free (spec); - ret = TALER_MHD_reply_with_error (connection, + ret = TALER_MHD_reply_with_error (bdc->rc->connection, MHD_HTTP_BAD_REQUEST, TALER_EC_GENERIC_PARAMETER_MALFORMED, emsg); GNUNET_free (emsg); - return ret; + finish_loop (bdc, + ret); + return; } } if (GNUNET_TIME_timestamp_cmp (bd->refund_deadline, @@ -580,156 +979,183 @@ TEH_handler_batch_deposit (struct TEH_RequestContext *rc, { GNUNET_break_op (0); GNUNET_JSON_parse_free (spec); - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_BAD_REQUEST, - TALER_EC_EXCHANGE_DEPOSIT_REFUND_DEADLINE_AFTER_WIRE_DEADLINE, - NULL); + finish_loop (bdc, + TALER_MHD_reply_with_error ( + bdc->rc->connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_EXCHANGE_DEPOSIT_REFUND_DEADLINE_AFTER_WIRE_DEADLINE, + NULL)); + return; } if (GNUNET_TIME_absolute_is_never (bd->wire_deadline.abs_time)) { GNUNET_break_op (0); GNUNET_JSON_parse_free (spec); - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_BAD_REQUEST, - TALER_EC_EXCHANGE_DEPOSIT_WIRE_DEADLINE_IS_NEVER, - NULL); + finish_loop (bdc, + TALER_MHD_reply_with_error ( + bdc->rc->connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_EXCHANGE_DEPOSIT_WIRE_DEADLINE_IS_NEVER, + NULL)); + return; } TALER_payto_hash (bd->receiver_wire_account, &bd->wire_target_h_payto); TALER_merchant_wire_signature_hash (bd->receiver_wire_account, &bd->wire_salt, - &dc.h_wire); - - GNUNET_assert (GNUNET_OK == - TALER_amount_set_zero (TEH_currency, - &dc.accumulated_total_without_fee)); + &bdc->h_wire); - /* handle policy, if present */ - if (! dc.has_no_policy) - { - const char *error_hint = NULL; - - if (GNUNET_OK != - TALER_extensions_create_policy_details ( - TEH_currency, - dc.policy_json, - &dc.policy_details, - &error_hint)) - { - GNUNET_break_op (0); - GNUNET_JSON_parse_free (spec); - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_BAD_REQUEST, - TALER_EC_EXCHANGE_DEPOSITS_POLICY_NOT_ACCEPTED, - error_hint); - } - - TALER_deposit_policy_hash (dc.policy_json, - &dc.h_policy); - } bd->num_cdis = json_array_size (coins); if (0 == bd->num_cdis) { GNUNET_break_op (0); GNUNET_JSON_parse_free (spec); - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_BAD_REQUEST, - TALER_EC_GENERIC_PARAMETER_MALFORMED, - "coins"); + finish_loop (bdc, + TALER_MHD_reply_with_error ( + bdc->rc->connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_GENERIC_PARAMETER_MALFORMED, + "coins")); + return; } if (TALER_MAX_FRESH_COINS < bd->num_cdis) { GNUNET_break_op (0); GNUNET_JSON_parse_free (spec); - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_BAD_REQUEST, - TALER_EC_GENERIC_PARAMETER_MALFORMED, - "coins"); + finish_loop (bdc, + TALER_MHD_reply_with_error ( + bdc->rc->connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_GENERIC_PARAMETER_MALFORMED, + "coins")); + return; } + bdc->cdis + = GNUNET_new_array (bd->num_cdis, + struct TALER_EXCHANGEDB_CoinDepositInformation); + bdc->deposit_fees + = GNUNET_new_array (bd->num_cdis, + struct TALER_Amount); + bd->cdis = bdc->cdis; + for (unsigned i = 0; i<bd->num_cdis; i++) { - struct TALER_EXCHANGEDB_CoinDepositInformation cdis[ - GNUNET_NZL (bd->num_cdis)]; - struct TALER_Amount deposit_fees[GNUNET_NZL (bd->num_cdis)]; + struct TALER_Amount amount_without_fee; enum GNUNET_GenericReturnValue res; - unsigned int i; - bd->cdis = cdis; - dc.deposit_fees = deposit_fees; - for (i = 0; i<bd->num_cdis; i++) - { - struct TALER_Amount amount_without_fee; - - res = parse_coin (connection, - &dc, - json_array_get (coins, - i), - &cdis[i], - &deposit_fees[i]); - if (GNUNET_OK != res) - break; - GNUNET_assert (0 <= - TALER_amount_subtract ( - &amount_without_fee, - &cdis[i].amount_with_fee, - &deposit_fees[i])); - - GNUNET_assert (0 <= - TALER_amount_add ( - &dc.accumulated_total_without_fee, - &dc.accumulated_total_without_fee, - &amount_without_fee)); - } + res = parse_coin (bdc, + json_array_get (coins, + i), + &bdc->cdis[i], + &bdc->deposit_fees[i]); if (GNUNET_OK != res) { - for (unsigned int j = 0; j<i; j++) - TALER_denom_sig_free (&cdis[j].coin.denom_sig); - GNUNET_JSON_parse_free (spec); - return (GNUNET_NO == res) ? MHD_YES : MHD_NO; + finish_loop (bdc, + (GNUNET_NO == res) + ? MHD_YES + : MHD_NO); + return; } + GNUNET_assert (0 <= + TALER_amount_subtract ( + &amount_without_fee, + &bdc->cdis[i].amount_with_fee, + &bdc->deposit_fees[i])); - dc.exchange_timestamp = GNUNET_TIME_timestamp_get (); - if (GNUNET_SYSERR == - TEH_plugin->preflight (TEH_plugin->cls)) - { - GNUNET_break (0); - GNUNET_JSON_parse_free (spec); - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_GENERIC_DB_START_FAILED, - "preflight failure"); - } + GNUNET_assert (0 <= + TALER_amount_add ( + &bdc->accumulated_total_without_fee, + &bdc->accumulated_total_without_fee, + &amount_without_fee)); + } - /* execute transaction */ - { - MHD_RESULT mhd_ret; - - if (GNUNET_OK != - TEH_DB_run_transaction (connection, - "execute batch deposit", - TEH_MT_REQUEST_BATCH_DEPOSIT, - &mhd_ret, - &batch_deposit_transaction, - &dc)) - { - for (unsigned int j = 0; j<bd->num_cdis; j++) - TALER_denom_sig_free (&cdis[j].coin.denom_sig); - GNUNET_JSON_parse_free (spec); - return mhd_ret; - } - } + GNUNET_JSON_parse_free (spec); + bdc->phase++; +} - /* generate regular response */ - { - MHD_RESULT mhd_ret; - mhd_ret = reply_batch_deposit_success (connection, - &dc); - for (unsigned int j = 0; j<bd->num_cdis; j++) - TALER_denom_sig_free (&cdis[j].coin.denom_sig); - GNUNET_JSON_parse_free (spec); - return mhd_ret; +/** + * Function called to clean up a context. + * + * @param rc request context with data to clean up + */ +static void +bdc_cleaner (struct TEH_RequestContext *rc) +{ + struct BatchDepositContext *bdc = rc->rh_ctx; + + if (NULL != bdc->lch) + { + TEH_legitimization_check_cancel (bdc->lch); + bdc->lch = NULL; + } + for (unsigned int i = 0; i<bdc->bd.num_cdis; i++) + TALER_denom_sig_free (&bdc->cdis[i].coin.denom_sig); + GNUNET_free (bdc->cdis); + GNUNET_free (bdc->deposit_fees); + json_decref (bdc->policy_json); + GNUNET_free (bdc); +} + + +MHD_RESULT +TEH_handler_batch_deposit (struct TEH_RequestContext *rc, + const json_t *root, + const char *const args[]) +{ + struct BatchDepositContext *bdc = rc->rh_ctx; + + (void) args; + if (NULL == bdc) + { + bdc = GNUNET_new (struct BatchDepositContext); + bdc->rc = rc; + rc->rh_ctx = bdc; + rc->rh_cleaner = &bdc_cleaner; + bdc->phase = BDC_PHASE_PARSE; + bdc->exchange_timestamp = GNUNET_TIME_timestamp_get (); + GNUNET_assert (GNUNET_OK == + TALER_amount_set_zero (TEH_currency, + &bdc->accumulated_total_without_fee)); + } + while (1) + { + switch (bdc->phase) + { + case BDC_PHASE_INIT: + GNUNET_break (0); + bdc->phase = BDC_PHASE_RETURN_NO; + break; + case BDC_PHASE_PARSE: + bdc_phase_parse (bdc, + root); + break; + case BDC_PHASE_POLICY: + bdc_phase_policy (bdc); + break; + case BDC_PHASE_KYC: + bdc_phase_kyc (bdc); + break; + case BDC_PHASE_TRANSACT: + bdc_phase_transact (bdc); + break; + case BDC_PHASE_REPLY_SUCCESS: + bdc_phase_reply_success (bdc); + break; + case BDC_PHASE_SUSPENDED: + return MHD_YES; + case BDC_PHASE_CHECK_KYC_RESULT: + bdc_phase_check_kyc_result (bdc); + break; + case BDC_PHASE_GENERATE_REPLY_FAILURE: + return MHD_queue_response (bdc->rc->connection, + bdc->http_status, + bdc->response); + case BDC_PHASE_RETURN_YES: + return MHD_YES; + case BDC_PHASE_RETURN_NO: + return MHD_NO; } } } diff --git a/src/exchange/taler-exchange-httpd_batch-deposit.h b/src/exchange/taler-exchange-httpd_batch-deposit.h index 187fb9f20..b5b7b0a32 100644 --- a/src/exchange/taler-exchange-httpd_batch-deposit.h +++ b/src/exchange/taler-exchange-httpd_batch-deposit.h @@ -29,6 +29,14 @@ /** + * Resumes all suspended batch deposit requests + * during cleanup. + */ +void +TEH_batch_deposit_cleanup (void); + + +/** * Handle a "/batch-deposit" request. Parses the JSON, and, if * successful, passes the JSON data to #deposit_transaction() to * further check the details of the operation specified. If everything checks diff --git a/src/exchange/taler-exchange-httpd_config.h b/src/exchange/taler-exchange-httpd_config.h index 036409778..486a8b83b 100644 --- a/src/exchange/taler-exchange-httpd_config.h +++ b/src/exchange/taler-exchange-httpd_config.h @@ -41,7 +41,7 @@ * * Returned via both /config and /keys endpoints. */ -#define EXCHANGE_PROTOCOL_VERSION "20:0:3" +#define EXCHANGE_PROTOCOL_VERSION "21:0:4" /** diff --git a/src/exchangedb/Makefile.am b/src/exchangedb/Makefile.am index ab9e666ca..a81c2d722 100644 --- a/src/exchangedb/Makefile.am +++ b/src/exchangedb/Makefile.am @@ -124,6 +124,7 @@ libtaler_plugin_exchangedb_postgres_la_SOURCES = \ pg_select_withdraw_amounts_for_kyc_check.h pg_select_withdraw_amounts_for_kyc_check.c \ pg_select_merge_amounts_for_kyc_check.h pg_select_merge_amounts_for_kyc_check.c \ pg_profit_drains_set_finished.h pg_profit_drains_set_finished.c \ + pg_select_deposit_amounts_for_kyc_check.h pg_select_deposit_amounts_for_kyc_check.c \ pg_lookup_aml_history.h pg_lookup_aml_history.c \ pg_lookup_kyc_history.h pg_lookup_kyc_history.c \ pg_profit_drains_get_pending.h pg_profit_drains_get_pending.c \ diff --git a/src/exchangedb/pg_select_deposit_amounts_for_kyc_check.c b/src/exchangedb/pg_select_deposit_amounts_for_kyc_check.c new file mode 100644 index 000000000..c34fd1da5 --- /dev/null +++ b/src/exchangedb/pg_select_deposit_amounts_for_kyc_check.c @@ -0,0 +1,153 @@ +/* + This file is part of TALER + Copyright (C) 2024 Taler Systems SA + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ +/** + * @file exchangedb/pg_select_deposit_amounts_for_kyc_check.c + * @brief Implementation of the select_deposit_amounts_for_kyc_check function for Postgres + * @author Christian Grothoff + */ +#include "platform.h" +#include "taler_error_codes.h" +#include "taler_dbevents.h" +#include "taler_pq_lib.h" +#include "pg_select_deposit_amounts_for_kyc_check.h" +#include "pg_helper.h" + +/** + * Closure for #get_kyc_amounts_cb(). + */ +struct KycAmountCheckContext +{ + /** + * Function to call per result. + */ + TALER_EXCHANGEDB_KycAmountCallback cb; + + /** + * Closure for @e cb. + */ + void *cb_cls; + + /** + * Plugin context. + */ + struct PostgresClosure *pg; + + /** + * Flag set to #GNUNET_OK as long as everything is fine. + */ + enum GNUNET_GenericReturnValue status; + +}; + +/** + * Invoke the callback for each result. + * + * @param cls a `struct KycAmountCheckContext *` + * @param result SQL result + * @param num_results number of rows in @a result + */ +static void +get_kyc_amounts_cb (void *cls, + PGresult *result, + unsigned int num_results) +{ + struct KycAmountCheckContext *ctx = cls; + struct PostgresClosure *pg = ctx->pg; + + for (unsigned int i = 0; i < num_results; i++) + { + struct GNUNET_TIME_Absolute date; + struct TALER_Amount amount; + struct GNUNET_PQ_ResultSpec rs[] = { + TALER_PQ_RESULT_SPEC_AMOUNT ("amount", + &amount), + GNUNET_PQ_result_spec_absolute_time ("date", + &date), + GNUNET_PQ_result_spec_end + }; + enum GNUNET_GenericReturnValue ret; + + if (GNUNET_OK != + GNUNET_PQ_extract_result (result, + rs, + i)) + { + GNUNET_break (0); + ctx->status = GNUNET_SYSERR; + return; + } + ret = ctx->cb (ctx->cb_cls, + &amount, + date); + GNUNET_PQ_cleanup_result (rs); + switch (ret) + { + case GNUNET_OK: + continue; + case GNUNET_NO: + break; + case GNUNET_SYSERR: + ctx->status = GNUNET_SYSERR; + break; + } + break; + } +} + + +enum GNUNET_DB_QueryStatus +TEH_PG_select_deposit_amounts_for_kyc_check ( + void *cls, + const struct TALER_PaytoHashP *h_payto, + struct GNUNET_TIME_Absolute time_limit, + TALER_EXCHANGEDB_KycAmountCallback kac, + void *kac_cls) +{ + struct PostgresClosure *pg = cls; + struct GNUNET_PQ_QueryParam params[] = { + GNUNET_PQ_query_param_auto_from_type (h_payto), + GNUNET_PQ_query_param_absolute_time (&time_limit), + GNUNET_PQ_query_param_end + }; + struct KycAmountCheckContext ctx = { + .cb = kac, + .cb_cls = kac_cls, + .pg = pg, + .status = GNUNET_OK + }; + enum GNUNET_DB_QueryStatus qs; + + PREPARE (pg, + "select_kyc_relevant_deposit_events", + "SELECT" + " cd.amount_with_fee AS amount" + ",bd.exchange_timestamp AS date" + " FROM batch_deposits bd" + " JOIN coin_deposits cd" + " USING (batch_deposit_serial_id)" + " WHERE wire_target_h_payto=$1" + " AND bd.exchange_timestamp >= $2" + " ORDER BY bd.exchange_timestamp DESC"); + qs = GNUNET_PQ_eval_prepared_multi_select ( + pg->conn, + "select_kyc_relevant_deposit_events", + params, + &get_kyc_amounts_cb, + &ctx); + if (GNUNET_OK != ctx.status) + return GNUNET_DB_STATUS_HARD_ERROR; + return qs; +} diff --git a/src/exchangedb/pg_select_deposit_amounts_for_kyc_check.h b/src/exchangedb/pg_select_deposit_amounts_for_kyc_check.h new file mode 100644 index 000000000..7e4ab31b2 --- /dev/null +++ b/src/exchangedb/pg_select_deposit_amounts_for_kyc_check.h @@ -0,0 +1,51 @@ +/* + This file is part of TALER + Copyright (C) 2024 Taler Systems SA + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ +/** + * @file exchangedb/pg_select_deposit_amounts_for_kyc_check.h + * @brief implementation of the select_deposit_amounts_for_kyc_check function for Postgres + * @author Christian Grothoff + */ +#ifndef PG_SELECT_DEPOSIT_AMOUNTS_FOR_KYC_CHECK_H +#define PG_SELECT_DEPOSIT_AMOUNTS_FOR_KYC_CHECK_H + +#include "taler_util.h" +#include "taler_json_lib.h" +#include "taler_exchangedb_plugin.h" + + +/** + * Call @a kac on deposited amounts after @a time_limit which are relevant for a + * KYC trigger for a merchant identified by @a h_payto. + * + * @param cls the @e cls of this struct with the plugin-specific state + * @param h_payto account identifier + * @param time_limit oldest transaction that could be relevant + * @param kac function to call for each applicable amount, + * in reverse chronological order (or until @a kac aborts + * by returning anything except #GNUNET_OK). + * @param kac_cls closure for @a kac + * @return transaction status code, @a kac aborting with #GNUNET_NO is not an error + */ +enum GNUNET_DB_QueryStatus +TEH_PG_select_deposit_amounts_for_kyc_check ( + void *cls, + const struct TALER_PaytoHashP *h_payto, + struct GNUNET_TIME_Absolute time_limit, + TALER_EXCHANGEDB_KycAmountCallback kac, + void *kac_cls); + + +#endif diff --git a/src/exchangedb/pg_select_merge_amounts_for_kyc_check.c b/src/exchangedb/pg_select_merge_amounts_for_kyc_check.c index 417d78ec7..8df91a398 100644 --- a/src/exchangedb/pg_select_merge_amounts_for_kyc_check.c +++ b/src/exchangedb/pg_select_merge_amounts_for_kyc_check.c @@ -131,7 +131,6 @@ TEH_PG_select_merge_amounts_for_kyc_check ( }; enum GNUNET_DB_QueryStatus qs; - PREPARE (pg, "select_kyc_relevant_merge_events", "SELECT" diff --git a/src/exchangedb/plugin_exchangedb_postgres.c b/src/exchangedb/plugin_exchangedb_postgres.c index c13a7d193..3915e3a46 100644 --- a/src/exchangedb/plugin_exchangedb_postgres.c +++ b/src/exchangedb/plugin_exchangedb_postgres.c @@ -55,6 +55,7 @@ #include "pg_lookup_records_by_table.h" #include "pg_lookup_kyc_status_by_token.h" #include "pg_lookup_serial_by_table.h" +#include "pg_select_deposit_amounts_for_kyc_check.h" #include "pg_lookup_pending_legitimization.h" #include "pg_lookup_completed_legitimization.h" #include "pg_lookup_active_legitimization.h" @@ -741,6 +742,8 @@ libtaler_plugin_exchangedb_postgres_init (void *cls) = &TEH_PG_get_wire_fees; plugin->select_aml_decisions = &TEH_PG_select_aml_decisions; + plugin->select_deposit_amounts_for_kyc_check + = &TEH_PG_select_deposit_amounts_for_kyc_check; plugin->insert_signkey_revocation = &TEH_PG_insert_signkey_revocation; plugin->select_aml_attributes diff --git a/src/include/taler_exchangedb_plugin.h b/src/include/taler_exchangedb_plugin.h index f4b5d8556..278994287 100644 --- a/src/include/taler_exchangedb_plugin.h +++ b/src/include/taler_exchangedb_plugin.h @@ -7164,6 +7164,28 @@ struct TALER_EXCHANGEDB_Plugin /** + * Call @a kac on deposited amounts after @a time_limit which are relevant for a + * KYC trigger for a merchant identified by @a h_payto. + * + * @param cls the @e cls of this struct with the plugin-specific state + * @param h_payto account identifier + * @param time_limit oldest transaction that could be relevant + * @param kac function to call for each applicable amount, + * in reverse chronological order (or until @a kac aborts + * by returning anything except #GNUNET_OK). + * @param kac_cls closure for @a kac + * @return transaction status code, @a kac aborting with #GNUNET_NO is not an error + */ + enum GNUNET_DB_QueryStatus + (*select_deposit_amounts_for_kyc_check)( + void *cls, + const struct TALER_PaytoHashP *h_payto, + struct GNUNET_TIME_Absolute time_limit, + TALER_EXCHANGEDB_KycAmountCallback kac, + void *kac_cls); + + + /** * Store automated legitimization outcome. * * @param cls closure diff --git a/src/kyclogic/kyclogic_api.c b/src/kyclogic/kyclogic_api.c index 7ea6b2b82..5f5b45f92 100644 --- a/src/kyclogic/kyclogic_api.c +++ b/src/kyclogic/kyclogic_api.c @@ -2777,6 +2777,12 @@ TALER_KYCLOGIC_kyc_test_required ( rule->timeframe); } + if (! have_threshold) + { + *triggered_rule = NULL; + return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS; + } + { struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get (); diff --git a/src/lib/exchange_api_handle.c b/src/lib/exchange_api_handle.c index 5890fa75e..329e2c2e6 100644 --- a/src/lib/exchange_api_handle.c +++ b/src/lib/exchange_api_handle.c @@ -40,12 +40,12 @@ * Which version of the Taler protocol is implemented * by this library? Used to determine compatibility. */ -#define EXCHANGE_PROTOCOL_CURRENT 19 +#define EXCHANGE_PROTOCOL_CURRENT 21 /** * How many versions are we backwards compatible with? */ -#define EXCHANGE_PROTOCOL_AGE 2 +#define EXCHANGE_PROTOCOL_AGE 4 /** * Set to 1 for extra debug logging. @@ -975,7 +975,7 @@ decode_keys_json (const json_t *resp_obj, EXITIF (1); } { - const json_t *hard_limits; + const json_t *hard_limits = NULL; struct GNUNET_JSON_Specification sspec[] = { TALER_JSON_spec_currency_specification ( "currency_specification", @@ -989,9 +989,11 @@ decode_keys_json (const json_t *resp_obj, "stefan_log", currency, &key_data->stefan_log), - GNUNET_JSON_spec_array_const ( - "hard_limits", - &hard_limits), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_array_const ( + "hard_limits", + &hard_limits), + NULL), GNUNET_JSON_spec_double ( "stefan_lin", &key_data->stefan_lin), @@ -1010,9 +1012,10 @@ decode_keys_json (const json_t *resp_obj, eline); EXITIF (1); } - if (GNUNET_OK != - parse_hard_limits (hard_limits, - key_data)) + if ( (NULL != hard_limits) && + (GNUNET_OK != + parse_hard_limits (hard_limits, + key_data)) ) { GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Parsing hard limits of /keys failed\n"); |