diff options
Diffstat (limited to 'src/exchange/taler-exchange-httpd_deposits_get.c')
-rw-r--r-- | src/exchange/taler-exchange-httpd_deposits_get.c | 525 |
1 files changed, 343 insertions, 182 deletions
diff --git a/src/exchange/taler-exchange-httpd_deposits_get.c b/src/exchange/taler-exchange-httpd_deposits_get.c index d981a8dd9..0850d19eb 100644 --- a/src/exchange/taler-exchange-httpd_deposits_get.c +++ b/src/exchange/taler-exchange-httpd_deposits_get.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2014-2017, 2021 Taler Systems SA + Copyright (C) 2014-2023 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 @@ -23,6 +23,7 @@ #include <jansson.h> #include <microhttpd.h> #include <pthread.h> +#include "taler_dbevents.h" #include "taler_json_lib.h" #include "taler_mhd_lib.h" #include "taler_signatures.h" @@ -32,83 +33,52 @@ /** - * A merchant asked for details about a deposit. Provide - * them. Generates the 200 reply. - * - * @param connection connection to the client - * @param h_contract_terms hash of the contract - * @param h_wire hash of wire account details - * @param coin_pub public key of the coin - * @param coin_contribution how much did the coin we asked about - * contribute to the total transfer value? (deposit value minus fee) - * @param wtid raw wire transfer identifier - * @param exec_time execution time of the wire transfer - * @return MHD result code + * Closure for #handle_wtid_data. */ -static MHD_RESULT -reply_deposit_details (struct MHD_Connection *connection, - const struct TALER_PrivateContractHash *h_contract_terms, - const struct TALER_MerchantWireHash *h_wire, - const struct TALER_CoinSpendPublicKeyP *coin_pub, - const struct TALER_Amount *coin_contribution, - const struct TALER_WireTransferIdentifierRawP *wtid, - struct GNUNET_TIME_Absolute exec_time) +struct DepositWtidContext { - struct TALER_ExchangePublicKeyP pub; - struct TALER_ExchangeSignatureP sig; - struct TALER_ConfirmWirePS cw = { - .purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_WIRE), - .purpose.size = htonl (sizeof (cw)), - .h_wire = *h_wire, - .h_contract_terms = *h_contract_terms, - .wtid = *wtid, - .coin_pub = *coin_pub, - .execution_time = GNUNET_TIME_absolute_hton (exec_time) - }; - enum TALER_ErrorCode ec; - TALER_amount_hton (&cw.coin_contribution, - coin_contribution); - if (TALER_EC_NONE != - (ec = TEH_keys_exchange_sign (&cw, - &pub, - &sig))) - { - return TALER_MHD_reply_with_ec (connection, - ec, - NULL); - } - return TALER_MHD_REPLY_JSON_PACK ( - connection, - MHD_HTTP_OK, - GNUNET_JSON_pack_data_auto ("wtid", - wtid), - GNUNET_JSON_pack_time_abs ("execution_time", - exec_time), - TALER_JSON_pack_amount ("coin_contribution", - coin_contribution), - GNUNET_JSON_pack_data_auto ("exchange_sig", - &sig), - GNUNET_JSON_pack_data_auto ("exchange_pub", - &pub)); -} + /** + * Kept in a DLL. + */ + struct DepositWtidContext *next; + + /** + * Kept in a DLL. + */ + struct DepositWtidContext *prev; + /** + * Context for the request we are processing. + */ + struct TEH_RequestContext *rc; -/** - * Closure for #handle_wtid_data. - */ -struct DepositWtidContext -{ + /** + * Subscription for the database event we are waiting for. + */ + struct GNUNET_DB_EventHandler *eh; /** - * Deposit details. + * Hash over the proposal data of the contract for which this deposit is made. */ - const struct TALER_DepositTrackPS *tps; + struct TALER_PrivateContractHashP h_contract_terms; /** - * Public key of the merchant. + * Hash over the wiring information of the merchant. */ - const struct TALER_MerchantPublicKeyP *merchant_pub; + struct TALER_MerchantWireHashP h_wire; + + /** + * The Merchant's public key. The deposit inquiry request is to be + * signed by the corresponding private key (using EdDSA). + */ + struct TALER_MerchantPublicKeyP merchant; + + /** + * The coin's public key. This is the value that must have been + * signed (blindly) by the Exchange. + */ + struct TALER_CoinSpendPublicKeyP coin_pub; /** * Set by #handle_wtid data to the wire transfer ID. @@ -116,6 +86,12 @@ struct DepositWtidContext struct TALER_WireTransferIdentifierRawP wtid; /** + * Signature by the merchant. + */ + struct TALER_MerchantSignatureP merchant_sig; + + + /** * Set by #handle_wtid data to the coin's contribution to the wire transfer. */ struct TALER_Amount coin_contribution; @@ -128,7 +104,12 @@ struct DepositWtidContext /** * Set by #handle_wtid data to the wire transfer execution time. */ - struct GNUNET_TIME_Absolute execution_time; + struct GNUNET_TIME_Timestamp execution_time; + + /** + * Timeout of the request, for long-polling. + */ + struct GNUNET_TIME_Absolute timeout; /** * Set by #handle_wtid to the coin contribution to the transaction @@ -142,15 +123,107 @@ struct DepositWtidContext struct TALER_EXCHANGEDB_KycStatus kyc; /** + * AML status information for the receiving account. + */ + enum TALER_AmlDecisionState aml_decision; + + /** * Set to #GNUNET_YES by #handle_wtid if the wire transfer is still pending * (and the above were not set). * Set to #GNUNET_SYSERR if there was a serious error. */ enum GNUNET_GenericReturnValue pending; + + /** + * #GNUNET_YES if we were suspended, #GNUNET_SYSERR + * if we were woken up due to shutdown. + */ + enum GNUNET_GenericReturnValue suspended; }; /** + * Head of DLL of suspended requests. + */ +static struct DepositWtidContext *dwc_head; + +/** + * Tail of DLL of suspended requests. + */ +static struct DepositWtidContext *dwc_tail; + + +void +TEH_deposits_get_cleanup () +{ + struct DepositWtidContext *n; + + for (struct DepositWtidContext *ctx = dwc_head; + NULL != ctx; + ctx = n) + { + n = ctx->next; + GNUNET_assert (GNUNET_YES == ctx->suspended); + ctx->suspended = GNUNET_SYSERR; + MHD_resume_connection (ctx->rc->connection); + GNUNET_CONTAINER_DLL_remove (dwc_head, + dwc_tail, + ctx); + } +} + + +/** + * A merchant asked for details about a deposit. Provide + * them. Generates the 200 reply. + * + * @param connection connection to the client + * @param ctx details to respond with + * @return MHD result code + */ +static MHD_RESULT +reply_deposit_details ( + struct MHD_Connection *connection, + const struct DepositWtidContext *ctx) +{ + struct TALER_ExchangePublicKeyP pub; + struct TALER_ExchangeSignatureP sig; + enum TALER_ErrorCode ec; + + if (TALER_EC_NONE != + (ec = TALER_exchange_online_confirm_wire_sign ( + &TEH_keys_exchange_sign_, + &ctx->h_wire, + &ctx->h_contract_terms, + &ctx->wtid, + &ctx->coin_pub, + ctx->execution_time, + &ctx->coin_delta, + &pub, + &sig))) + { + GNUNET_break (0); + return TALER_MHD_reply_with_ec (connection, + ec, + NULL); + } + return TALER_MHD_REPLY_JSON_PACK ( + connection, + MHD_HTTP_OK, + GNUNET_JSON_pack_data_auto ("wtid", + &ctx->wtid), + GNUNET_JSON_pack_timestamp ("execution_time", + ctx->execution_time), + TALER_JSON_pack_amount ("coin_contribution", + &ctx->coin_delta), + GNUNET_JSON_pack_data_auto ("exchange_sig", + &sig), + GNUNET_JSON_pack_data_auto ("exchange_pub", + &pub)); +} + + +/** * Execute a "deposits" GET. Returns the transfer information * associated with the given deposit. * @@ -177,17 +250,17 @@ deposits_get_transaction (void *cls, struct TALER_Amount fee; qs = TEH_plugin->lookup_transfer_by_deposit (TEH_plugin->cls, - &ctx->tps->h_contract_terms, - &ctx->tps->h_wire, - &ctx->tps->coin_pub, - ctx->merchant_pub, - + &ctx->h_contract_terms, + &ctx->h_wire, + &ctx->coin_pub, + &ctx->merchant, &pending, &ctx->wtid, &ctx->execution_time, &ctx->coin_contribution, &fee, - &ctx->kyc); + &ctx->kyc, + &ctx->aml_decision); if (0 > qs) { if (GNUNET_DB_STATUS_HARD_ERROR == qs) @@ -224,54 +297,140 @@ deposits_get_transaction (void *cls, /** + * Function called on events received from Postgres. + * Wakes up long pollers. + * + * @param cls the `struct DepositWtidContext *` + * @param extra additional event data provided + * @param extra_size number of bytes in @a extra + */ +static void +db_event_cb (void *cls, + const void *extra, + size_t extra_size) +{ + struct DepositWtidContext *ctx = cls; + struct GNUNET_AsyncScopeSave old_scope; + + (void) extra; + (void) extra_size; + if (GNUNET_YES != ctx->suspended) + return; /* might get multiple wake-up events */ + GNUNET_CONTAINER_DLL_remove (dwc_head, + dwc_tail, + ctx); + GNUNET_async_scope_enter (&ctx->rc->async_scope_id, + &old_scope); + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Resuming request handling\n"); + TEH_check_invariants (); + ctx->suspended = GNUNET_NO; + MHD_resume_connection (ctx->rc->connection); + TALER_MHD_daemon_trigger (); + TEH_check_invariants (); + GNUNET_async_scope_restore (&old_scope); +} + + +/** * Lookup and return the wire transfer identifier. * - * @param connection the MHD connection to handle - * @param tps signed request to execute - * @param merchant_pub public key from the merchant + * @param ctx context of the signed request to execute * @return MHD result code */ static MHD_RESULT handle_track_transaction_request ( - struct MHD_Connection *connection, - const struct TALER_DepositTrackPS *tps, - const struct TALER_MerchantPublicKeyP *merchant_pub) + struct DepositWtidContext *ctx) { - MHD_RESULT mhd_ret; - struct DepositWtidContext ctx = { - .tps = tps, - .merchant_pub = merchant_pub - }; - - if (GNUNET_OK != - TEH_DB_run_transaction (connection, - "handle deposits GET", - &mhd_ret, - &deposits_get_transaction, - &ctx)) - return mhd_ret; - if (GNUNET_SYSERR == ctx.pending) + struct MHD_Connection *connection = ctx->rc->connection; + + if ( (GNUNET_TIME_absolute_is_future (ctx->timeout)) && + (NULL == ctx->eh) ) + { + struct TALER_CoinDepositEventP rep = { + .header.size = htons (sizeof (rep)), + .header.type = htons (TALER_DBEVENT_EXCHANGE_DEPOSIT_STATUS_CHANGED), + .merchant_pub = ctx->merchant + }; + + ctx->eh = TEH_plugin->event_listen ( + TEH_plugin->cls, + GNUNET_TIME_absolute_get_remaining (ctx->timeout), + &rep.header, + &db_event_cb, + ctx); + GNUNET_break (NULL != ctx->eh); + } + { + MHD_RESULT mhd_ret; + + if (GNUNET_OK != + TEH_DB_run_transaction (connection, + "handle deposits GET", + TEH_MT_REQUEST_OTHER, + &mhd_ret, + &deposits_get_transaction, + ctx)) + return mhd_ret; + } + if (GNUNET_SYSERR == ctx->pending) return TALER_MHD_reply_with_error (connection, MHD_HTTP_INTERNAL_SERVER_ERROR, TALER_EC_GENERIC_DB_INVARIANT_FAILURE, "wire fees exceed aggregate in database"); - if (GNUNET_YES == ctx.pending) + if (GNUNET_YES == ctx->pending) + { + if ( (GNUNET_TIME_absolute_is_future (ctx->timeout)) && + (GNUNET_NO == ctx->suspended) ) + { + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Suspending request handling\n"); + GNUNET_CONTAINER_DLL_insert (dwc_head, + dwc_tail, + ctx); + ctx->suspended = GNUNET_YES; + MHD_suspend_connection (connection); + return MHD_YES; + } return TALER_MHD_REPLY_JSON_PACK ( connection, MHD_HTTP_ACCEPTED, - GNUNET_JSON_pack_uint64 ("payment_target_uuid", - ctx.kyc.payment_target_uuid), + GNUNET_JSON_pack_allow_null ( + (0 == ctx->kyc.requirement_row) + ? GNUNET_JSON_pack_string ("requirement_row", + NULL) + : GNUNET_JSON_pack_uint64 ("requirement_row", + ctx->kyc.requirement_row)), + GNUNET_JSON_pack_uint64 ("aml_decision", + (uint32_t) ctx->aml_decision), GNUNET_JSON_pack_bool ("kyc_ok", - ctx.kyc.ok), - GNUNET_JSON_pack_time_abs ("execution_time", - ctx.execution_time)); + ctx->kyc.ok), + GNUNET_JSON_pack_timestamp ("execution_time", + ctx->execution_time)); + } return reply_deposit_details (connection, - &tps->h_contract_terms, - &tps->h_wire, - &tps->coin_pub, - &ctx.coin_delta, - &ctx.wtid, - ctx.execution_time); + ctx); +} + + +/** + * Function called to clean up a context. + * + * @param rc request context with data to clean up + */ +static void +dwc_cleaner (struct TEH_RequestContext *rc) +{ + struct DepositWtidContext *ctx = rc->rh_ctx; + + GNUNET_assert (GNUNET_NO == ctx->suspended); + if (NULL != ctx->eh) + { + TEH_plugin->event_listen_cancel (TEH_plugin->cls, + ctx->eh); + ctx->eh = NULL; + } + GNUNET_free (ctx); } @@ -279,85 +438,87 @@ MHD_RESULT TEH_handler_deposits_get (struct TEH_RequestContext *rc, const char *const args[4]) { - enum GNUNET_GenericReturnValue res; - struct TALER_MerchantSignatureP merchant_sig; - struct TALER_DepositTrackPS tps = { - .purpose.size = htonl (sizeof (tps)), - .purpose.purpose = htonl (TALER_SIGNATURE_MERCHANT_TRACK_TRANSACTION) - }; - - if (GNUNET_OK != - GNUNET_STRINGS_string_to_data (args[0], - strlen (args[0]), - &tps.h_wire, - sizeof (tps.h_wire))) - { - GNUNET_break_op (0); - return TALER_MHD_reply_with_error (rc->connection, - MHD_HTTP_BAD_REQUEST, - TALER_EC_EXCHANGE_DEPOSITS_GET_INVALID_H_WIRE, - args[0]); - } - if (GNUNET_OK != - GNUNET_STRINGS_string_to_data (args[1], - strlen (args[1]), - &tps.merchant, - sizeof (tps.merchant))) - { - GNUNET_break_op (0); - return TALER_MHD_reply_with_error (rc->connection, - MHD_HTTP_BAD_REQUEST, - TALER_EC_EXCHANGE_DEPOSITS_GET_INVALID_MERCHANT_PUB, - args[1]); - } - if (GNUNET_OK != - GNUNET_STRINGS_string_to_data (args[2], - strlen (args[2]), - &tps.h_contract_terms, - sizeof (tps.h_contract_terms))) - { - GNUNET_break_op (0); - return TALER_MHD_reply_with_error (rc->connection, - MHD_HTTP_BAD_REQUEST, - TALER_EC_EXCHANGE_DEPOSITS_GET_INVALID_H_CONTRACT_TERMS, - args[2]); - } - if (GNUNET_OK != - GNUNET_STRINGS_string_to_data (args[3], - strlen (args[3]), - &tps.coin_pub, - sizeof (tps.coin_pub))) - { - GNUNET_break_op (0); - return TALER_MHD_reply_with_error (rc->connection, - MHD_HTTP_BAD_REQUEST, - TALER_EC_EXCHANGE_DEPOSITS_GET_INVALID_COIN_PUB, - args[3]); - } - res = TALER_MHD_parse_request_arg_data (rc->connection, - "merchant_sig", - &merchant_sig, - sizeof (merchant_sig)); - if (GNUNET_SYSERR == res) - return MHD_NO; /* internal error */ - if (GNUNET_NO == res) - return MHD_YES; /* parse error */ - if (GNUNET_OK != - GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MERCHANT_TRACK_TRANSACTION, - &tps, - &merchant_sig.eddsa_sig, - &tps.merchant.eddsa_pub)) + struct DepositWtidContext *ctx = rc->rh_ctx; + + if (NULL == ctx) { - GNUNET_break_op (0); - return TALER_MHD_reply_with_error (rc->connection, - MHD_HTTP_FORBIDDEN, - TALER_EC_EXCHANGE_DEPOSITS_GET_MERCHANT_SIGNATURE_INVALID, - NULL); + ctx = GNUNET_new (struct DepositWtidContext); + ctx->rc = rc; + rc->rh_ctx = ctx; + rc->rh_cleaner = &dwc_cleaner; + + if (GNUNET_OK != + GNUNET_STRINGS_string_to_data (args[0], + strlen (args[0]), + &ctx->h_wire, + sizeof (ctx->h_wire))) + { + GNUNET_break_op (0); + return TALER_MHD_reply_with_error (rc->connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_EXCHANGE_DEPOSITS_GET_INVALID_H_WIRE, + args[0]); + } + if (GNUNET_OK != + GNUNET_STRINGS_string_to_data (args[1], + strlen (args[1]), + &ctx->merchant, + sizeof (ctx->merchant))) + { + GNUNET_break_op (0); + return TALER_MHD_reply_with_error (rc->connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_EXCHANGE_DEPOSITS_GET_INVALID_MERCHANT_PUB, + args[1]); + } + if (GNUNET_OK != + GNUNET_STRINGS_string_to_data (args[2], + strlen (args[2]), + &ctx->h_contract_terms, + sizeof (ctx->h_contract_terms))) + { + GNUNET_break_op (0); + return TALER_MHD_reply_with_error (rc->connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_EXCHANGE_DEPOSITS_GET_INVALID_H_CONTRACT_TERMS, + args[2]); + } + if (GNUNET_OK != + GNUNET_STRINGS_string_to_data (args[3], + strlen (args[3]), + &ctx->coin_pub, + sizeof (ctx->coin_pub))) + { + GNUNET_break_op (0); + return TALER_MHD_reply_with_error (rc->connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_EXCHANGE_DEPOSITS_GET_INVALID_COIN_PUB, + args[3]); + } + TALER_MHD_parse_request_arg_auto_t (rc->connection, + "merchant_sig", + &ctx->merchant_sig); + TALER_MHD_parse_request_timeout (rc->connection, + &ctx->timeout); + TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++; + { + if (GNUNET_OK != + TALER_merchant_deposit_verify (&ctx->merchant, + &ctx->coin_pub, + &ctx->h_contract_terms, + &ctx->h_wire, + &ctx->merchant_sig)) + { + GNUNET_break_op (0); + return TALER_MHD_reply_with_error (rc->connection, + MHD_HTTP_FORBIDDEN, + TALER_EC_EXCHANGE_DEPOSITS_GET_MERCHANT_SIGNATURE_INVALID, + NULL); + } + } } - return handle_track_transaction_request (rc->connection, - &tps, - &tps.merchant); + return handle_track_transaction_request (ctx); } |