diff --git a/src/include/taler_mint_service.h b/src/include/taler_mint_service.h index a7b6afd11..b151cb009 100644 --- a/src/include/taler_mint_service.h +++ b/src/include/taler_mint_service.h @@ -1059,5 +1059,165 @@ void TALER_MINT_admin_add_incoming_cancel (struct TALER_MINT_AdminAddIncomingHandle *aai); +/* ********************* /wire/deposits *********************** */ + +/** + * @brief A /wire/deposits Handle + */ +struct TALER_MINT_WireDepositsHandle; + + +/** + * Details for one of the /deposit operations that the + * mint combined into a single wire transfer. + */ +struct TALER_WireDepositDetails +{ + /** + * Hash of the contract. + */ + struct GNUNET_HashCode h_contract; + + /** + * Which coin was deposited? + */ + struct TALER_CoinSpendPublicKeyP coin_pub; + + /** + * Value of the deposit (including fee). + */ + struct TALER_Amount coin_value; + + /** + * Fee charged by the mint for the deposit. + */ + struct TALER_Amount coin_fee; + + /** + * Merchant's transaction identifier. + */ + uint64_t transaction_id; + +}; + + +/** + * Function called with detailed wire transfer data, including all + * of the coin transactions that were combined into the wire transfer. + * + * @param cls closure + * @param http_status HTTP status code we got, 0 on mint protocol violation + * @param json original json reply (may include signatures, those have then been + * validated already) + * @param wtid extracted wire transfer identifier, or NULL if the mint could + * not provide any (set only if @a http_status is #MHD_HTTP_OK) + * @param total_amount total amount of the wire transfer, or NULL if the mint could + * not provide any @a wtid (set only if @a http_status is #MHD_HTTP_OK) + * @param details_length length of the @a details array + * @param details array with details about the combined transactions + */ +typedef void +(*TALER_MINT_WireDepositsCallback)(void *cls, + unsigned int http_status, + json_t *json, + const struct GNUNET_HashCode *h_wire, + const struct TALER_Amount *total_amount, + unsigned int details_length, + const struct TALER_WireDepositDetails *details); + + +/** + * Query the mint about which transactions were combined + * to create a wire transfer. + * + * @param mint mint to query + * @param wtid raw wire transfer identifier to get information about + * @param cb callback to call + * @param cb_cls closure for @a cb + * @return handle to cancel operation + */ +struct TALER_MINT_WireDepositsHandle * +TALER_MINT_wire_deposits (struct TALER_MINT_Handle *mint, + const struct TALER_WireTransferIdentifierRawP *wtid, + TALER_MINT_WireDepositsCallback cb, + void *cb_cls); + + +/** + * Cancel wire deposits request. This function cannot be used on a request + * handle if a response is already served for it. + * + * @param wdh the wire deposits request handle + */ +void +TALER_MINT_wire_deposits_cancel (struct TALER_MINT_WireDepositsHandle *wdh); + + +/* ********************* /deposit/wtid *********************** */ + + +/** + * @brief A /deposit/wtid Handle + */ +struct TALER_MINT_DepositWtidHandle; + + +/** + * Function called with detailed wire transfer data. + * + * @param cls closure + * @param http_status HTTP status code we got, 0 on mint protocol violation + * @param json original json reply (may include signatures, those have then been + * validated already) + * @param wtid wire transfer identifier used by the mint, NULL if mint did not + * yet execute the transaction + * @param execution_time actual or planned execution time for the wire transfer + * @param coin_contribution contribution to the @a total_amount of the deposited coin (may be NULL) + * @param total_amount total amount of the wire transfer, or NULL if the mint could + * not provide any @a wtid (set only if @a http_status is #MHD_HTTP_OK) + */ +typedef void +(*TALER_MINT_DepositWtidCallback)(void *cls, + unsigned int http_status, + json_t *json, + const struct TALER_WireTransferIdentifierRawP *wtid, + struct GNUNET_TIME_Absolute execution_time, + const struct TALER_Amount *coin_contribution, + const struct TALER_Amount *total_amount); + + +/** + * Obtain the wire transfer details for a given deposit. + * + * @param mint the mint to query + * @param merchant_priv the merchant's private key + * @param h_wire hash of merchant's wire transfer details + * @param h_contract hash of the contract + * @param coin_pub public key of the coin + * @param transaction_id transaction identifier + * @param cb function to call with the result + * @param cb_cls closure for @a cb + * @return handle to abort request + */ +struct TALER_MINT_DepositWtidHandle * +TALER_MINT_deposit_wtid (struct TALER_MINT_Handle *mint, + const struct TALER_MerchantPrivateKeyP *merchant_priv, + const struct GNUNET_HashCode *h_wire, + const struct GNUNET_HashCode *h_contract, + const struct TALER_CoinSpendPublicKeyP *coin_pub, + uint64_t transaction_id, + TALER_MINT_DepositWtidCallback cb, + void *cb_cls); + + +/** + * Cancel deposit wtid request. This function cannot be used on a request + * handle if a response is already served for it. + * + * @param dwh the wire deposits request handle + */ +void +TALER_MINT_deposit_wtid_cancel (struct TALER_MINT_DepositWtidHandle *dwh); + #endif /* _TALER_MINT_SERVICE_H */ diff --git a/src/include/taler_mintdb_plugin.h b/src/include/taler_mintdb_plugin.h index baf587886..e5cf6d6f1 100644 --- a/src/include/taler_mintdb_plugin.h +++ b/src/include/taler_mintdb_plugin.h @@ -570,23 +570,24 @@ typedef void /** * Function called with the results of the lookup of the - * wire transfer identifier information. + * wire transfer identifier information. Only called if + * we are at least aware of the transaction existing. * * @param cls closure * @param wtid wire transfer identifier, NULL * if the transaction was not yet done * @param coin_contribution how much did the coin we asked about - * contribute to the total transfer value? (deposit value minus fee) + * contribute to the total transfer value? (deposit value including fee) + * @param coin_fee how much did the mint charge for the deposit fee * @param total_amount how much was the total wire transfer? * @param execution_time when was the transaction done, or - * when we expect it to be done (if @a wtid was NULL); - * #GNUNET_TIME_UNIT_FOREVER_ABS if the /deposit is unknown - * to the mint + * when we expect it to be done (if @a wtid was NULL) */ typedef void (*TALER_MINTDB_DepositWtidCallback)(void *cls, const struct TALER_WireTransferIdentifierRawP *wtid, const struct TALER_Amount *coin_contribution, + const struct TALER_Amount *coin_fee, const struct TALER_Amount *total_amount, struct GNUNET_TIME_Absolute execution_time); @@ -601,18 +602,20 @@ typedef void * @param h_contract which contract was this payment about * @param transaction_id merchant's transaction ID for the payment * @param coin_pub which public key was this payment about - * @param deposit_value amount contributed by this coin in total - * @param deposit_fee deposit fee charged by mint for this coin + * @param coin_value amount contributed by this coin in total (with fee) + * @param coin_fee applicable fee for this coin + * @param transfer_value total amount of the wire transfer */ typedef void -(*TALER_MINTDB_TransactionDataCallback)(void *cls, - const struct TALER_MerchantPublicKeyP *merchant_pub, - const struct GNUNET_HashCode *h_wire, - const struct GNUNET_HashCode *h_contract, - uint64_t transaction_id, - const struct TALER_CoinSpendPublicKeyP *coin_pub, - const struct TALER_Amount *deposit_value, - const struct TALER_Amount *deposit_fee); +(*TALER_MINTDB_WireTransferDataCallback)(void *cls, + const struct TALER_MerchantPublicKeyP *merchant_pub, + const struct GNUNET_HashCode *h_wire, + const struct GNUNET_HashCode *h_contract, + uint64_t transaction_id, + const struct TALER_CoinSpendPublicKeyP *coin_pub, + const struct TALER_Amount *coin_value, + const struct TALER_Amount *coin_fee, + const struct TALER_Amount *transfer_value); /** @@ -1229,16 +1232,19 @@ struct TALER_MINTDB_Plugin * into a wire transfer by the respective @a raw_wtid. * * @param cls the @e cls of this struct with the plugin-specific state + * @param session database connection * @param wtid the raw wire transfer identifier we used * @param cb function to call on each transaction found * @param cb_cls closure for @a cb - * @return #GNUNET_OK on success, #GNUNET_SYSERR on database errors + * @return #GNUNET_OK on success, #GNUNET_SYSERR on database errors, + * #GNUNET_NO if we found no results */ int - (*lookup_wire_transactions) (void *cls, - const struct TALER_WireTransferIdentifierRawP *wtid, - TALER_MINTDB_TransactionDataCallback cb, - void *cb_cls); + (*lookup_wire_transfer) (void *cls, + struct TALER_MINTDB_Session *session, + const struct TALER_WireTransferIdentifierRawP *wtid, + TALER_MINTDB_WireTransferDataCallback cb, + void *cb_cls); /** @@ -1247,6 +1253,7 @@ struct TALER_MINTDB_Plugin * to be executed. * * @param cls closure + * @param session database connection * @param h_contract hash of the contract * @param h_wire hash of merchant wire details * @param coin_pub public key of deposited coin @@ -1254,10 +1261,12 @@ struct TALER_MINTDB_Plugin * @param transaction_id transaction identifier * @param cb function to call with the result * @param cb_cls closure to pass to @a cb - * @return #GNUNET_OK on success, #GNUNET_SYSERR on DB errors + * @return #GNUNET_OK on success, #GNUNET_SYSERR on DB errors, + * #GNUNET_NO if nothing was found */ int (*wire_lookup_deposit_wtid)(void *cls, + struct TALER_MINTDB_Session *session, const struct GNUNET_HashCode *h_contract, const struct GNUNET_HashCode *h_wire, const struct TALER_CoinSpendPublicKeyP *coin_pub, @@ -1271,26 +1280,32 @@ struct TALER_MINTDB_Plugin * Function called to insert aggregation information into the DB. * * @param cls closure + * @param session database connection * @param wtid the raw wire transfer identifier we used * @param merchant_pub public key of the merchant (should be same for all callbacks with the same @e cls) * @param h_wire hash of wire transfer details of the merchant (should be same for all callbacks with the same @e cls) * @param h_contract which contract was this payment about * @param transaction_id merchant's transaction ID for the payment + * @param execution_time when did we execute the transaction * @param coin_pub which public key was this payment about - * @param deposit_value amount contributed by this coin in total - * @param deposit_fee deposit fee charged by mint for this coin + * @param coin_value amount contributed by this coin in total + * @param coin_fee deposit fee charged by mint for this coin + * @param transfer_value total amount of the wire transfer * @return #GNUNET_OK on success, #GNUNET_SYSERR on DB errors */ int (*insert_aggregation_tracking)(void *cls, + struct TALER_MINTDB_Session *session, const struct TALER_WireTransferIdentifierRawP *wtid, const struct TALER_MerchantPublicKeyP *merchant_pub, const struct GNUNET_HashCode *h_wire, const struct GNUNET_HashCode *h_contract, uint64_t transaction_id, + struct GNUNET_TIME_Absolute execution_time, const struct TALER_CoinSpendPublicKeyP *coin_pub, - const struct TALER_Amount *deposit_value, - const struct TALER_Amount *deposit_fee); + const struct TALER_Amount *coin_value, + const struct TALER_Amount *coin_fee, + const struct TALER_Amount *transfer_value); }; diff --git a/src/mint-lib/Makefile.am b/src/mint-lib/Makefile.am index ccea4ec58..171a42464 100644 --- a/src/mint-lib/Makefile.am +++ b/src/mint-lib/Makefile.am @@ -20,10 +20,12 @@ libtalermint_la_SOURCES = \ mint_api_handle.c mint_api_handle.h \ mint_api_admin.c \ mint_api_deposit.c \ + mint_api_deposit_wtid.c \ mint_api_refresh.c \ mint_api_refresh_link.c \ mint_api_reserve.c \ - mint_api_wire.c + mint_api_wire.c \ + mint_api_wire_deposits.c libtalermint_la_LIBADD = \ -lgnunetutil \ diff --git a/src/mint-lib/mint_api_context.c b/src/mint-lib/mint_api_context.c index 3a86efb64..2767906b5 100644 --- a/src/mint-lib/mint_api_context.c +++ b/src/mint-lib/mint_api_context.c @@ -288,7 +288,7 @@ TALER_MINT_perform (struct TALER_MINT_Context *ctx) GNUNET_assert (CURLE_OK == curl_easy_getinfo (cmsg->easy_handle, CURLINFO_PRIVATE, - (char *) &job)); + (char **) &job)); GNUNET_assert (job->ctx == ctx); job->jcc (job->jcc_cls, cmsg->easy_handle); diff --git a/src/mint-lib/mint_api_deposit_wtid.c b/src/mint-lib/mint_api_deposit_wtid.c new file mode 100644 index 000000000..50f9c55d8 --- /dev/null +++ b/src/mint-lib/mint_api_deposit_wtid.c @@ -0,0 +1,386 @@ +/* + This file is part of TALER + Copyright (C) 2014, 2015, 2016 GNUnet e.V. + + 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, If not, see + +*/ +/** + * @file mint-lib/mint_api_deposit_wtid.c + * @brief Implementation of the /deposit/wtid request of the mint's HTTP API + * @author Christian Grothoff + */ +#include "platform.h" +#include +#include +#include /* just for HTTP status codes */ +#include +#include "taler_mint_service.h" +#include "mint_api_common.h" +#include "mint_api_json.h" +#include "mint_api_context.h" +#include "mint_api_handle.h" +#include "taler_signatures.h" + + +/** + * @brief A Deposit Wtid Handle + */ +struct TALER_MINT_DepositWtidHandle +{ + + /** + * The connection to mint this request handle will use + */ + struct TALER_MINT_Handle *mint; + + /** + * The url for this request. + */ + char *url; + + /** + * JSON encoding of the request to POST. + */ + char *json_enc; + + /** + * Handle for the request. + */ + struct MAC_Job *job; + + /** + * Function to call with the result. + */ + TALER_MINT_DepositWtidCallback cb; + + /** + * Closure for @a cb. + */ + void *cb_cls; + + /** + * Download buffer + */ + struct MAC_DownloadBuffer db; + + /** + * Information the mint should sign in response. + * (with pre-filled fields from the request). + */ + struct TALER_ConfirmWirePS depconf; + +}; + + +/** + * Verify that the signature on the "200 OK" response + * from the mint is valid. + * + * @param dwh deposit wtid handle + * @param json json reply with the signature + * @return #GNUNET_OK if the signature is valid, #GNUNET_SYSERR if not + */ +static int +verify_deposit_wtid_signature_ok (const struct TALER_MINT_DepositWtidHandle *dwh, + json_t *json) +{ + struct TALER_MintSignatureP mint_sig; + struct TALER_MintPublicKeyP mint_pub; + const struct TALER_MINT_Keys *key_state; + struct MAJ_Specification spec[] = { + MAJ_spec_fixed_auto ("mint_sig", &mint_sig), + MAJ_spec_fixed_auto ("mint_pub", &mint_pub), + MAJ_spec_end + }; + + if (GNUNET_OK != + MAJ_parse_json (json, + spec)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + key_state = TALER_MINT_get_keys (dwh->mint); + if (GNUNET_OK != + TALER_MINT_test_signing_key (key_state, + &mint_pub)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + if (GNUNET_OK != + GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MINT_CONFIRM_WIRE, + &dwh->depconf.purpose, + &mint_sig.eddsa_signature, + &mint_pub.eddsa_pub)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + return GNUNET_OK; +} + + +/** + * Function called when we're done processing the + * HTTP /deposit/wtid request. + * + * @param cls the `struct TALER_MINT_DepositWtidHandle` + * @param eh the curl request handle + */ +static void +handle_deposit_wtid_finished (void *cls, + CURL *eh) +{ + struct TALER_MINT_DepositWtidHandle *dwh = cls; + long response_code; + json_t *json; + const struct TALER_WireTransferIdentifierRawP *wtid = NULL; + struct GNUNET_TIME_Absolute execution_time = GNUNET_TIME_UNIT_FOREVER_ABS; + const struct TALER_Amount *coin_contribution = NULL; + const struct TALER_Amount *total_amount = NULL; + struct TALER_Amount coin_contribution_s; + struct TALER_Amount total_amount_s; + + dwh->job = NULL; + json = MAC_download_get_result (&dwh->db, + eh, + &response_code); + switch (response_code) + { + case 0: + break; + case MHD_HTTP_OK: + { + struct MAJ_Specification spec[] = { + MAJ_spec_fixed_auto ("wtid", &dwh->depconf.wtid), + MAJ_spec_absolute_time ("execution_time", &execution_time), + MAJ_spec_amount ("coin_contribution", &coin_contribution_s), + MAJ_spec_amount ("total_amount", &total_amount_s), + MAJ_spec_end + }; + + if (GNUNET_OK != + MAJ_parse_json (json, + spec)) + { + GNUNET_break_op (0); + response_code = 0; + break; + } + wtid = &dwh->depconf.wtid; + dwh->depconf.execution_time = GNUNET_TIME_absolute_hton (execution_time); + TALER_amount_hton (&dwh->depconf.coin_contribution, + &coin_contribution_s); + coin_contribution = &coin_contribution_s; + TALER_amount_hton (&dwh->depconf.total_amount, + &total_amount_s); + total_amount = &total_amount_s; + if (GNUNET_OK != + verify_deposit_wtid_signature_ok (dwh, + json)) + { + GNUNET_break_op (0); + response_code = 0; + } + } + break; + case MHD_HTTP_ACCEPTED: + { + /* Transaction known, but not executed yet */ + struct MAJ_Specification spec[] = { + MAJ_spec_absolute_time ("execution_time", &execution_time), + MAJ_spec_end + }; + + if (GNUNET_OK != + MAJ_parse_json (json, + spec)) + { + GNUNET_break_op (0); + response_code = 0; + break; + } + } + break; + case MHD_HTTP_BAD_REQUEST: + /* This should never happen, either us or the mint is buggy + (or API version conflict); just pass JSON reply to the application */ + break; + case MHD_HTTP_UNAUTHORIZED: + /* Nothing really to verify, mint says one of the signatures is + invalid; as we checked them, this should never happen, we + should pass the JSON reply to the application */ + break; + case MHD_HTTP_NOT_FOUND: + /* Mint does not know about transaction; + we should pass the reply to the application */ + break; + case MHD_HTTP_INTERNAL_SERVER_ERROR: + /* Server had an internal issue; we should retry, but this API + leaves this to the application */ + break; + default: + /* unexpected response code */ + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u\n", + response_code); + GNUNET_break (0); + response_code = 0; + break; + } + dwh->cb (dwh->cb_cls, + response_code, + json, + wtid, + execution_time, + coin_contribution, + total_amount); + json_decref (json); + TALER_MINT_deposit_wtid_cancel (dwh); +} + + +/** + * Obtain wire transfer details about an existing deposit operation. + * + * @param mint the mint to query + * @param merchant_priv the merchant's private key + * @param h_wire hash of merchant's wire transfer details + * @param h_contract hash of the contract + * @param coin_pub public key of the coin + * @param transaction_id transaction identifier + * @param cb function to call with the result + * @param cb_cls closure for @a cb + * @return handle to abort request + */ +struct TALER_MINT_DepositWtidHandle * +TALER_MINT_deposit_wtid (struct TALER_MINT_Handle *mint, + const struct TALER_MerchantPrivateKeyP *merchant_priv, + const struct GNUNET_HashCode *h_wire, + const struct GNUNET_HashCode *h_contract, + const struct TALER_CoinSpendPublicKeyP *coin_pub, + uint64_t transaction_id, + TALER_MINT_DepositWtidCallback cb, + void *cb_cls) +{ + struct TALER_DepositTrackPS dtp; + struct TALER_MerchantSignatureP merchant_sig; + struct TALER_MINT_DepositWtidHandle *dwh; + struct TALER_MINT_Context *ctx; + json_t *deposit_wtid_obj; + CURL *eh; + + if (GNUNET_YES != + MAH_handle_is_ready (mint)) + { + GNUNET_break (0); + return NULL; + } + dtp.purpose.purpose = htonl (TALER_SIGNATURE_MERCHANT_DEPOSIT_WTID); + dtp.purpose.size = htonl (sizeof (dtp)); + dtp.h_contract = *h_contract; + dtp.h_wire = *h_wire; + dtp.transaction_id = GNUNET_htonll (transaction_id); + GNUNET_CRYPTO_eddsa_key_get_public (&merchant_priv->eddsa_priv, + &dtp.merchant.eddsa_pub); + + dtp.coin_pub = *coin_pub; + GNUNET_assert (GNUNET_OK == + GNUNET_CRYPTO_eddsa_sign (&merchant_priv->eddsa_priv, + &dtp.purpose, + &merchant_sig.eddsa_sig)); + deposit_wtid_obj = json_pack ("{s:o, s:o," /* H_wire, H_contract */ + " s:o, s:I," /* coin_pub, transaction_id */ + " s:o, s:o}", /* merchant_pub, merchant_sig */ + "H_wire", TALER_json_from_data (h_wire, + sizeof (struct GNUNET_HashCode)), + "H_contract", TALER_json_from_data (h_contract, + sizeof (struct GNUNET_HashCode)), + "coin_pub", TALER_json_from_data (coin_pub, + sizeof (*coin_pub)), + "transaction_id", (json_int_t) transaction_id, + "merchant_pub", TALER_json_from_data (&dtp.merchant, + sizeof (struct TALER_MerchantPublicKeyP)), + "merchant_sig", TALER_json_from_data (&merchant_sig, + sizeof (merchant_sig))); + + dwh = GNUNET_new (struct TALER_MINT_DepositWtidHandle); + dwh->mint = mint; + dwh->cb = cb; + dwh->cb_cls = cb_cls; + dwh->url = MAH_path_to_url (mint, "/deposit/wtid"); + dwh->depconf.purpose.size = htonl (sizeof (struct TALER_DepositConfirmationPS)); + dwh->depconf.purpose.purpose = htonl (TALER_SIGNATURE_MINT_CONFIRM_WIRE); + dwh->depconf.h_wire = *h_wire; + dwh->depconf.h_contract = *h_contract; + dwh->depconf.coin_pub = *coin_pub; + dwh->depconf.transaction_id = GNUNET_htonll (transaction_id); + + eh = curl_easy_init (); + GNUNET_assert (NULL != (dwh->json_enc = + json_dumps (deposit_wtid_obj, + JSON_COMPACT))); + json_decref (deposit_wtid_obj); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_URL, + dwh->url)); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_POSTFIELDS, + dwh->json_enc)); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_POSTFIELDSIZE, + strlen (dwh->json_enc))); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_WRITEFUNCTION, + &MAC_download_cb)); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_WRITEDATA, + &dwh->db)); + ctx = MAH_handle_to_context (mint); + dwh->job = MAC_job_add (ctx, + eh, + GNUNET_YES, + &handle_deposit_wtid_finished, + dwh); + return dwh; +} + + +/** + * Cancel deposit wtid request. This function cannot be used on a request + * handle if a response is already served for it. + * + * @param dwh the wire deposits request handle + */ +void +TALER_MINT_deposit_wtid_cancel (struct TALER_MINT_DepositWtidHandle *dwh) +{ + if (NULL != dwh->job) + { + MAC_job_cancel (dwh->job); + dwh->job = NULL; + } + GNUNET_free_non_null (dwh->db.buf); + GNUNET_free (dwh->url); + GNUNET_free (dwh->json_enc); + GNUNET_free (dwh); +} + + +/* end of mint_api_deposit_wtid.c */ diff --git a/src/mint-lib/mint_api_handle.c b/src/mint-lib/mint_api_handle.c index 077e42c24..c8898d5c3 100644 --- a/src/mint-lib/mint_api_handle.c +++ b/src/mint-lib/mint_api_handle.c @@ -757,7 +757,7 @@ TALER_MINT_connect (struct TALER_MINT_Context *ctx, GNUNET_assert (CURLE_OK == curl_easy_setopt (c, CURLOPT_VERBOSE, - 1)); + 0)); GNUNET_assert (CURLE_OK == curl_easy_setopt (c, CURLOPT_STDERR, diff --git a/src/mint-lib/mint_api_json.c b/src/mint-lib/mint_api_json.c index a728a5495..7de33e5eb 100644 --- a/src/mint-lib/mint_api_json.c +++ b/src/mint-lib/mint_api_json.c @@ -232,6 +232,20 @@ parse_json (json_t *root, } break; + case MAJ_CMD_UINT64: + { + json_int_t val; + + if (! json_is_integer (pos)) + { + GNUNET_break_op (0); + return i; + } + val = json_integer_value (pos); + *spec[i].details.u64 = (uint64_t) val; + } + break; + case MAJ_CMD_JSON_OBJECT: { if (! (json_is_object (pos) || json_is_array (pos)) ) @@ -428,6 +442,26 @@ MAJ_spec_uint16 (const char *name, } +/** + * 64-bit integer. + * + * @param name name of the JSON field + * @param[out] u64 where to store the integer found under @a name + */ +struct MAJ_Specification +MAJ_spec_uint64 (const char *name, + uint64_t *u64) +{ + struct MAJ_Specification ret = + { + .cmd = MAJ_CMD_UINT64, + .field = name, + .details.u64 = u64 + }; + return ret; +} + + /** * JSON object. * diff --git a/src/mint-lib/mint_api_json.h b/src/mint-lib/mint_api_json.h index 68809059e..6bc3a5572 100644 --- a/src/mint-lib/mint_api_json.h +++ b/src/mint-lib/mint_api_json.h @@ -78,6 +78,11 @@ enum MAJ_Command */ MAJ_CMD_UINT16, + /** + * Parse `uint64_t` integer at the current position. + */ + MAJ_CMD_UINT64, + /** * Parse JSON object at the current position. */ @@ -191,6 +196,11 @@ struct MAJ_Specification */ uint16_t *u16; + /** + * Where to store 64-bit integer. + */ + uint64_t *u64; + /** * Where to store a JSON object. */ @@ -282,6 +292,17 @@ MAJ_spec_uint16 (const char *name, uint16_t *u16); +/** + * 64-bit integer. + * + * @param name name of the JSON field + * @param[out] u64 where to store the integer found under @a name + */ +struct MAJ_Specification +MAJ_spec_uint64 (const char *name, + uint64_t *u64); + + /** * JSON object. * diff --git a/src/mint-lib/mint_api_refresh_link.c b/src/mint-lib/mint_api_refresh_link.c index 9ae55b2ae..dcd2326ca 100644 --- a/src/mint-lib/mint_api_refresh_link.c +++ b/src/mint-lib/mint_api_refresh_link.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2015 GNUnet e.V. + Copyright (C) 2015, 2016 GNUnet e.V. 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 @@ -185,6 +185,17 @@ parse_refresh_link_ok (struct TALER_MINT_RefreshLinkHandle *rlh, return GNUNET_SYSERR; } num_coins = 0; + /* Theoretically, a coin may have been melted repeatedly + into different sessions; so the response is an array + which contains information by melting session. That + array contains another array. However, our API returns + a single 1d array, so we flatten the 2d array that is + returned into a single array. Note that usually a coin + is melted at most once, and so we'll only run this + loop once for 'session=0' in most cases. + + num_coins tracks the size of the 1d array we return, + whilst 'i' and 'session' track the 2d array. */ for (session=0;session +*/ +/** + * @file mint-lib/mint_api_wire_deposits.c + * @brief Implementation of the /wire/deposits request of the mint's HTTP API + * @author Christian Grothoff + */ +#include "platform.h" +#include +#include +#include /* just for HTTP status codes */ +#include +#include "taler_mint_service.h" +#include "mint_api_common.h" +#include "mint_api_json.h" +#include "mint_api_context.h" +#include "mint_api_handle.h" +#include "taler_signatures.h" + + +/** + * @brief A /wire/deposits Handle + */ +struct TALER_MINT_WireDepositsHandle +{ + + /** + * The connection to mint this request handle will use + */ + struct TALER_MINT_Handle *mint; + + /** + * The url for this request. + */ + char *url; + + /** + * Handle for the request. + */ + struct MAC_Job *job; + + /** + * Function to call with the result. + */ + TALER_MINT_WireDepositsCallback cb; + + /** + * Closure for @a cb. + */ + void *cb_cls; + + /** + * Download buffer + */ + struct MAC_DownloadBuffer db; + +}; + + +/** + * Function called when we're done processing the + * HTTP /wire/deposits request. + * + * @param cls the `struct TALER_MINT_WireDepositsHandle` + * @param eh the curl request handle + */ +static void +handle_wire_deposits_finished (void *cls, + CURL *eh) +{ + struct TALER_MINT_WireDepositsHandle *wdh = cls; + long response_code; + json_t *json; + + wdh->job = NULL; + json = MAC_download_get_result (&wdh->db, + eh, + &response_code); + switch (response_code) + { + case 0: + break; + case MHD_HTTP_OK: + { + json_t *details_j; + struct GNUNET_HashCode h_wire; + struct TALER_Amount total_amount; + struct TALER_MerchantPublicKeyP merchant_pub; + unsigned int num_details; + struct MAJ_Specification spec[] = { + MAJ_spec_fixed_auto ("H_wire", &h_wire), + MAJ_spec_fixed_auto ("merchant_pub", &merchant_pub), + MAJ_spec_amount ("total_amount", &total_amount), + MAJ_spec_json ("details", &details_j), + MAJ_spec_end + }; + + if (GNUNET_OK != + MAJ_parse_json (json, + spec)) + { + GNUNET_break_op (0); + response_code = 0; + break; + } + num_details = json_array_size (details_j); + { + struct TALER_WireDepositDetails details[num_details]; + unsigned int i; + + for (i=0;ih_contract), + MAJ_spec_amount ("deposit_value", &detail->coin_value), + MAJ_spec_amount ("deposit_fee", &detail->coin_fee), + MAJ_spec_uint64 ("transaction_id", &detail->transaction_id), + MAJ_spec_fixed_auto ("coin_pub", &detail->coin_pub), + MAJ_spec_end + }; + + if (GNUNET_OK != + MAJ_parse_json (detail_j, + spec_detail)) + { + GNUNET_break_op (0); + response_code = 0; + break; + } + } + if (0 == response_code) + break; + wdh->cb (wdh->cb_cls, + response_code, + json, + &h_wire, + &total_amount, + num_details, + details); + json_decref (json); + TALER_MINT_wire_deposits_cancel (wdh); + return; + } + } + break; + case MHD_HTTP_BAD_REQUEST: + /* This should never happen, either us or the mint is buggy + (or API version conflict); just pass JSON reply to the application */ + break; + case MHD_HTTP_UNAUTHORIZED: + /* Nothing really to verify, mint says one of the signatures is + invalid; as we checked them, this should never happen, we + should pass the JSON reply to the application */ + break; + case MHD_HTTP_NOT_FOUND: + /* Mint does not know about transaction; + we should pass the reply to the application */ + break; + case MHD_HTTP_INTERNAL_SERVER_ERROR: + /* Server had an internal issue; we should retry, but this API + leaves this to the application */ + break; + default: + /* unexpected response code */ + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u\n", + response_code); + GNUNET_break (0); + response_code = 0; + break; + } + wdh->cb (wdh->cb_cls, + response_code, + json, + NULL, NULL, 0, NULL); + json_decref (json); + TALER_MINT_wire_deposits_cancel (wdh); +} + + +/** + * Query the mint about which transactions were combined + * to create a wire transfer. + * + * @param mint mint to query + * @param wtid raw wire transfer identifier to get information about + * @param cb callback to call + * @param cb_cls closure for @a cb + * @return handle to cancel operation + */ +struct TALER_MINT_WireDepositsHandle * +TALER_MINT_wire_deposits (struct TALER_MINT_Handle *mint, + const struct TALER_WireTransferIdentifierRawP *wtid, + TALER_MINT_WireDepositsCallback cb, + void *cb_cls) +{ + struct TALER_MINT_WireDepositsHandle *wdh; + struct TALER_MINT_Context *ctx; + char *buf; + char *path; + CURL *eh; + + if (GNUNET_YES != + MAH_handle_is_ready (mint)) + { + GNUNET_break (0); + return NULL; + } + + wdh = GNUNET_new (struct TALER_MINT_WireDepositsHandle); + wdh->mint = mint; + wdh->cb = cb; + wdh->cb_cls = cb_cls; + + buf = GNUNET_STRINGS_data_to_string_alloc (wtid, + sizeof (struct TALER_WireTransferIdentifierRawP)); + GNUNET_asprintf (&path, + "/wire/deposits?wtid=%s", + buf); + wdh->url = MAH_path_to_url (wdh->mint, + path); + GNUNET_free (buf); + GNUNET_free (path); + + eh = curl_easy_init (); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_URL, + wdh->url)); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_WRITEFUNCTION, + &MAC_download_cb)); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_WRITEDATA, + &wdh->db)); + ctx = MAH_handle_to_context (mint); + wdh->job = MAC_job_add (ctx, + eh, + GNUNET_YES, + &handle_wire_deposits_finished, + wdh); + return wdh; +} + + +/** + * Cancel wire deposits request. This function cannot be used on a request + * handle if a response is already served for it. + * + * @param wdh the wire deposits request handle + */ +void +TALER_MINT_wire_deposits_cancel (struct TALER_MINT_WireDepositsHandle *wdh) +{ + if (NULL != wdh->job) + { + MAC_job_cancel (wdh->job); + wdh->job = NULL; + } + GNUNET_free_non_null (wdh->db.buf); + GNUNET_free (wdh->url); + GNUNET_free (wdh); +} + + +/* end of mint_api_wire_deposits.c */ diff --git a/src/mint-lib/test_mint_api.c b/src/mint-lib/test_mint_api.c index 0e3b2bed8..e41d01805 100644 --- a/src/mint-lib/test_mint_api.c +++ b/src/mint-lib/test_mint_api.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2014, 2015 GNUnet e.V. + Copyright (C) 2014, 2015, 2016 GNUnet e.V. 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 @@ -111,7 +111,17 @@ enum OpCode /** * Verify the mint's /wire-method. */ - OC_WIRE + OC_WIRE, + + /** + * Verify mint's /wire/deposits method. + */ + OC_WIRE_DEPOSITS, + + /** + * Verify mint's /deposit/wtid method. + */ + OC_DEPOSIT_WTID }; @@ -470,6 +480,60 @@ struct Command } wire; + /** + * Information for the /wire/deposits's command. + */ + struct { + + /** + * Handle to the wire deposits request. + */ + struct TALER_MINT_WireDepositsHandle *wdh; + + /** + * Reference to a /deposit/wtid command. If set, we use the + * WTID from that command. + */ + const char *wtid_ref; + + /** + * WTID to use (used if @e wtid_ref is NULL). + */ + struct TALER_WireTransferIdentifierRawP wtid; + + /* TODO: may want to add list of deposits we expected + to see aggregated here in the future. */ + + } wire_deposits; + + /** + * Information for the /deposit/wtid command. + */ + struct { + + /** + * Handle to the deposit wtid request. + */ + struct TALER_MINT_DepositWtidHandle *dwh; + + /** + * Which /deposit operation should we obtain WTID data for? + */ + const char *deposit_ref; + + /** + * What is the expected total amount? Only used if + * @e expected_response_code was #MHD_HTTP_OK. + */ + struct TALER_Amount total_amount_expected; + + /** + * Wire transfer identifier, set if #MHD_HTTP_OK was the response code. + */ + struct TALER_WireTransferIdentifierRawP wtid; + + } deposit_wtid; + } details; }; @@ -1219,6 +1283,158 @@ wire_cb (void *cls, } +/** + * Function called with detailed wire transfer data, including all + * of the coin transactions that were combined into the wire transfer. + * + * @param cls closure + * @param http_status HTTP status code we got, 0 on mint protocol violation + * @param json original json reply (may include signatures, those have then been + * validated already) + * @param wtid extracted wire transfer identifier, or NULL if the mint could + * not provide any (set only if @a http_status is #MHD_HTTP_OK) + * @param total_amount total amount of the wire transfer, or NULL if the mint could + * not provide any @a wtid (set only if @a http_status is #MHD_HTTP_OK) + * @param details_length length of the @a details array + * @param details array with details about the combined transactions + */ +static void +wire_deposits_cb (void *cls, + unsigned int http_status, + json_t *json, + const struct GNUNET_HashCode *h_wire, + const struct TALER_Amount *total_amount, + unsigned int details_length, + const struct TALER_WireDepositDetails *details) +{ + struct InterpreterState *is = cls; + struct Command *cmd = &is->commands[is->ip]; + const struct Command *ref; + + cmd->details.wire_deposits.wdh = NULL; + ref = find_command (is, + cmd->details.wire_deposits.wtid_ref); + if (cmd->expected_response_code != http_status) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u to command %s\n", + http_status, + cmd->label); + json_dumpf (json, stderr, 0); + fail (is); + return; + } + switch (http_status) + { + case MHD_HTTP_OK: + if (0 != TALER_amount_cmp (total_amount, + &ref->details.deposit_wtid.total_amount_expected)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Total amount missmatch to command %s\n", + http_status, + cmd->label); + json_dumpf (json, stderr, 0); + fail (is); + return; + } + if (NULL != ref->details.deposit_wtid.deposit_ref) + { + const struct Command *dep; + struct GNUNET_HashCode hw; + + dep = find_command (is, + ref->details.deposit_wtid.deposit_ref); + GNUNET_CRYPTO_hash (dep->details.deposit.wire_details, + strlen (dep->details.deposit.wire_details), + &hw); + if (0 != memcmp (&hw, + h_wire, + sizeof (struct GNUNET_HashCode))) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Wire hash missmatch to command %s\n", + cmd->label); + json_dumpf (json, stderr, 0); + fail (is); + return; + } + } + break; + default: + break; + } + + /* move to next command */ + is->ip++; + is->task = GNUNET_SCHEDULER_add_now (&interpreter_run, + is); +} + + +/** + * Function called with detailed wire transfer data. + * + * @param cls closure + * @param http_status HTTP status code we got, 0 on mint protocol violation + * @param json original json reply (may include signatures, those have then been + * validated already) + * @param wtid wire transfer identifier used by the mint, NULL if mint did not + * yet execute the transaction + * @param execution_time actual or planned execution time for the wire transfer + * @param coin_contribution contribution to the @a total_amount of the deposited coin (may be NULL) + * @param total_amount total amount of the wire transfer, or NULL if the mint could + * not provide any @a wtid (set only if @a http_status is #MHD_HTTP_OK) + */ +static void +deposit_wtid_cb (void *cls, + unsigned int http_status, + json_t *json, + const struct TALER_WireTransferIdentifierRawP *wtid, + struct GNUNET_TIME_Absolute execution_time, + const struct TALER_Amount *coin_contribution, + const struct TALER_Amount *total_amount) +{ + struct InterpreterState *is = cls; + struct Command *cmd = &is->commands[is->ip]; + + cmd->details.deposit_wtid.dwh = NULL; + if (cmd->expected_response_code != http_status) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u to command %s\n", + http_status, + cmd->label); + json_dumpf (json, stderr, 0); + fail (is); + return; + } + switch (http_status) + { + case MHD_HTTP_OK: + cmd->details.deposit_wtid.wtid = *wtid; + if (0 != TALER_amount_cmp (total_amount, + &cmd->details.deposit_wtid.total_amount_expected)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Total amount missmatch to command %s\n", + cmd->label); + json_dumpf (json, stderr, 0); + fail (is); + return; + } + break; + default: + break; + } + + /* move to next command */ + is->ip++; + is->task = GNUNET_SCHEDULER_add_now (&interpreter_run, + is); +} + + /** * Run the main interpreter loop that performs mint operations. * @@ -1401,7 +1617,9 @@ interpreter_run (void *cls, struct GNUNET_TIME_Absolute refund_deadline; struct GNUNET_TIME_Absolute wire_deadline; struct GNUNET_TIME_Absolute timestamp; + struct GNUNET_CRYPTO_EddsaPrivateKey *priv; struct TALER_MerchantPublicKeyP merchant_pub; + json_t *contract; json_t *wire; GNUNET_assert (NULL != @@ -1444,37 +1662,51 @@ interpreter_run (void *cls, fail (is); return; } - GNUNET_CRYPTO_hash (cmd->details.deposit.contract, - strlen (cmd->details.deposit.contract), - &h_contract); + contract = json_loads (cmd->details.deposit.contract, + JSON_REJECT_DUPLICATES, + NULL); + if (NULL == contract) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Failed to parse contract details `%s' at %u/%s\n", + cmd->details.deposit.contract, + is->ip, + cmd->label); + fail (is); + return; + } + TALER_hash_json (contract, + &h_contract); wire = json_loads (cmd->details.deposit.wire_details, JSON_REJECT_DUPLICATES, NULL); if (NULL == wire) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Failed to parse wire details `%s' at %u\n", + "Failed to parse wire details `%s' at %u/%s\n", cmd->details.deposit.wire_details, - is->ip); + is->ip, + cmd->label); fail (is); return; } GNUNET_CRYPTO_eddsa_key_get_public (&coin_priv->eddsa_priv, &coin_pub.eddsa_pub); + priv = GNUNET_CRYPTO_eddsa_key_create (); + cmd->details.deposit.merchant_priv.eddsa_priv = *priv; + GNUNET_free (priv); if (0 != cmd->details.deposit.refund_deadline.rel_value_us) { - struct GNUNET_CRYPTO_EddsaPrivateKey *priv; - - priv = GNUNET_CRYPTO_eddsa_key_create (); - cmd->details.deposit.merchant_priv.eddsa_priv = *priv; - GNUNET_free (priv); refund_deadline = GNUNET_TIME_relative_to_absolute (cmd->details.deposit.refund_deadline); } else { refund_deadline = GNUNET_TIME_UNIT_ZERO_ABS; } + GNUNET_CRYPTO_eddsa_key_get_public (&cmd->details.deposit.merchant_priv.eddsa_priv, + &merchant_pub.eddsa_pub); + wire_deadline = GNUNET_TIME_relative_to_absolute (GNUNET_TIME_UNIT_DAYS); timestamp = GNUNET_TIME_absolute_get (); TALER_round_abs_time (×tamp); @@ -1686,6 +1918,85 @@ interpreter_run (void *cls, is); trigger_context_task (); return; + case OC_WIRE_DEPOSITS: + if (NULL != cmd->details.wire_deposits.wtid_ref) + { + ref = find_command (is, + cmd->details.wire_deposits.wtid_ref); + GNUNET_assert (NULL != ref); + cmd->details.wire_deposits.wtid = ref->details.deposit_wtid.wtid; + } + cmd->details.wire_deposits.wdh + = TALER_MINT_wire_deposits (mint, + &cmd->details.wire_deposits.wtid, + &wire_deposits_cb, + is); + trigger_context_task (); + return; + case OC_DEPOSIT_WTID: + { + struct GNUNET_HashCode h_wire; + struct GNUNET_HashCode h_contract; + json_t *wire; + json_t *contract; + const struct Command *coin; + struct TALER_CoinSpendPublicKeyP coin_pub; + + ref = find_command (is, + cmd->details.deposit_wtid.deposit_ref); + GNUNET_assert (NULL != ref); + coin = find_command (is, + ref->details.deposit.coin_ref); + GNUNET_assert (NULL != coin); + switch (coin->oc) + { + case OC_WITHDRAW_SIGN: + GNUNET_CRYPTO_eddsa_key_get_public (&coin->details.reserve_withdraw.coin_priv.eddsa_priv, + &coin_pub.eddsa_pub); + break; + case OC_REFRESH_REVEAL: + { + const struct FreshCoin *fc; + unsigned int idx; + + idx = ref->details.deposit.coin_idx; + GNUNET_assert (idx < coin->details.refresh_reveal.num_fresh_coins); + fc = &coin->details.refresh_reveal.fresh_coins[idx]; + + GNUNET_CRYPTO_eddsa_key_get_public (&fc->coin_priv.eddsa_priv, + &coin_pub.eddsa_pub); + } + break; + default: + GNUNET_assert (0); + } + + wire = json_loads (ref->details.deposit.wire_details, + JSON_REJECT_DUPLICATES, + NULL); + GNUNET_assert (NULL != wire); + TALER_hash_json (wire, + &h_wire); + json_decref (wire); + contract = json_loads (ref->details.deposit.contract, + JSON_REJECT_DUPLICATES, + NULL); + GNUNET_assert (NULL != contract); + TALER_hash_json (contract, + &h_contract); + json_decref (contract); + cmd->details.deposit_wtid.dwh + = TALER_MINT_deposit_wtid (mint, + &ref->details.deposit.merchant_priv, + &h_wire, + &h_contract, + &coin_pub, + ref->details.deposit.transaction_id, + &deposit_wtid_cb, + is); + trigger_context_task (); + } + return; default: GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Unknown instruction %d at %u (%s)\n", @@ -1695,8 +2006,6 @@ interpreter_run (void *cls, fail (is); return; } - is->task = GNUNET_SCHEDULER_add_now (&interpreter_run, - is); } @@ -1829,10 +2138,36 @@ do_shutdown (void *cls, case OC_WIRE: if (NULL != cmd->details.wire.wh) { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Command %u (%s) did not complete\n", + i, + cmd->label); TALER_MINT_wire_cancel (cmd->details.wire.wh); cmd->details.wire.wh = NULL; } break; + case OC_WIRE_DEPOSITS: + if (NULL != cmd->details.wire_deposits.wdh) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Command %u (%s) did not complete\n", + i, + cmd->label); + TALER_MINT_wire_deposits_cancel (cmd->details.wire_deposits.wdh); + cmd->details.wire_deposits.wdh = NULL; + } + break; + case OC_DEPOSIT_WTID: + if (NULL != cmd->details.deposit_wtid.dwh) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Command %u (%s) did not complete\n", + i, + cmd->label); + TALER_MINT_deposit_wtid_cancel (cmd->details.deposit_wtid.dwh); + cmd->details.deposit_wtid.dwh = NULL; + } + break; default: GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Unknown instruction %d at %u (%s)\n", @@ -2047,7 +2382,7 @@ run (void *cls, .details.deposit.amount = "EUR:5", .details.deposit.coin_ref = "withdraw-coin-1", .details.deposit.wire_details = "{ \"type\":\"TEST\", \"bank\":\"dest bank\", \"account\":42 }", - .details.deposit.contract = "{ \"items\"={ \"name\":\"ice cream\", \"value\":1 } }", + .details.deposit.contract = "{ \"items\": [ { \"name\":\"ice cream\", \"value\":1 } ] }", .details.deposit.transaction_id = 1 }, /* Try to overdraw funds ... */ @@ -2064,7 +2399,7 @@ run (void *cls, .details.deposit.amount = "EUR:5", .details.deposit.coin_ref = "withdraw-coin-1", .details.deposit.wire_details = "{ \"type\":\"TEST\", \"bank\":\"dest bank\", \"account\":43 }", - .details.deposit.contract = "{ \"items\"={ \"name\":\"ice cream\", \"value\":1 } }", + .details.deposit.contract = "{ \"items\": [ { \"name\":\"ice cream\", \"value\":1 } ] }", .details.deposit.transaction_id = 1 }, /* Try to double-spend the 5 EUR coin at the same merchant (but different transaction ID) */ @@ -2074,7 +2409,7 @@ run (void *cls, .details.deposit.amount = "EUR:5", .details.deposit.coin_ref = "withdraw-coin-1", .details.deposit.wire_details = "{ \"type\":\"TEST\", \"bank\":\"dest bank\", \"account\":42 }", - .details.deposit.contract = "{ \"items\"={ \"name\":\"ice cream\", \"value\":1 } }", + .details.deposit.contract = "{ \"items\": [ { \"name\":\"ice cream\", \"value\":1 } ] }", .details.deposit.transaction_id = 2 }, /* Try to double-spend the 5 EUR coin at the same merchant (but different contract) */ @@ -2084,7 +2419,7 @@ run (void *cls, .details.deposit.amount = "EUR:5", .details.deposit.coin_ref = "withdraw-coin-1", .details.deposit.wire_details = "{ \"type\":\"TEST\", \"bank\":\"dest bank\", \"account\":42 }", - .details.deposit.contract = "{ \"items\"={ \"name\":\"ice cream\", \"value\":2 } }", + .details.deposit.contract = "{ \"items\":[{ \"name\":\"ice cream\", \"value\":2 } ] }", .details.deposit.transaction_id = 1 }, /* ***************** /refresh testing ******************** */ @@ -2109,7 +2444,7 @@ run (void *cls, .details.deposit.amount = "EUR:1", .details.deposit.coin_ref = "refresh-withdraw-coin-1", .details.deposit.wire_details = "{ \"type\":\"TEST\", \"bank\":\"dest bank\", \"account\":42 }", - .details.deposit.contract = "{ \"items\"={ \"name\":\"ice cream\", \"value\"EUR:1 } }", + .details.deposit.contract = "{ \"items\" : [ { \"name\":\"ice cream\", \"value\":\"EUR:1\" } ] }", .details.deposit.transaction_id = 42421 }, /* Melt the rest of the coin's value (EUR:4.00 = 3x EUR:1.03 + 7x EUR:0.13) */ @@ -2143,7 +2478,7 @@ run (void *cls, .details.deposit.coin_ref = "refresh-reveal-1", .details.deposit.coin_idx = 0, .details.deposit.wire_details = "{ \"type\":\"TEST\", \"bank\":\"dest bank\", \"account\":42 }", - .details.deposit.contract = "{ \"items\"={ \"name\":\"ice cream\", \"value\":3 } }", + .details.deposit.contract = "{ \"items\": [ { \"name\":\"ice cream\", \"value\":3 } ] }", .details.deposit.transaction_id = 2 }, /* Test successfully spending coins from the refresh operation: @@ -2155,7 +2490,7 @@ run (void *cls, .details.deposit.coin_ref = "refresh-reveal-1", .details.deposit.coin_idx = 4, .details.deposit.wire_details = "{ \"type\":\"TEST\", \"bank\":\"dest bank\", \"account\":42 }", - .details.deposit.contract = "{ \"items\"={ \"name\":\"ice cream\", \"value\":3 } }", + .details.deposit.contract = "{ \"items\": [ { \"name\":\"ice cream\", \"value\":3 } ] }", .details.deposit.transaction_id = 2 }, /* Test running a failing melt operation (same operation again must fail) */ @@ -2168,6 +2503,35 @@ run (void *cls, // FIXME: also test with coin that was already melted // (signature differs from coin that was deposited...) /* *************** end of /refresh testing ************** */ + + /* ************** Test tracking API ******************** */ + /* Try resolving a deposit's WTID, as we never triggered + execution of transactions, the answer should be that + the mint knows about the deposit, but has no WTID yet. */ + { .oc = OC_DEPOSIT_WTID, + .label = "deposit-wtid-found", + .expected_response_code = MHD_HTTP_ACCEPTED, + .details.deposit_wtid.deposit_ref = "deposit-simple" }, + /* Try resolving a deposit's WTID for a failed deposit. + As the deposit failed, the answer should be that + the mint does NOT know about the deposit. */ + { .oc = OC_DEPOSIT_WTID, + .label = "deposit-wtid-failing", + .expected_response_code = MHD_HTTP_NOT_FOUND, + .details.deposit_wtid.deposit_ref = "deposit-double-2" }, + /* Try resolving an undefined (all zeros) WTID; this + should fail as obviously the mint didn't use that + WTID value for any transaction. */ + { .oc = OC_WIRE_DEPOSITS, + .label = "wire-deposit-failing", + .expected_response_code = MHD_HTTP_NOT_FOUND }, + + /* TODO: trigger aggregation logic and then check the + cases where tracking succeeds! */ + + /* ************** End of tracking API testing************* */ + + #endif { .oc = OC_END } diff --git a/src/mint/taler-mint-httpd_db.c b/src/mint/taler-mint-httpd_db.c index d31dcd2ba..c39cbbcf7 100644 --- a/src/mint/taler-mint-httpd_db.c +++ b/src/mint/taler-mint-httpd_db.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2014, 2015 GNUnet e.V. + Copyright (C) 2014, 2015, 2016 GNUnet e.V. 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 @@ -1565,6 +1565,12 @@ struct WtidTransactionContext */ struct TALER_Amount total; + /** + * Value we find in the DB for the @e total; only valid if @e is_valid + * is #GNUNET_YES. + */ + struct TALER_Amount db_transaction_value; + /** * Public key of the merchant, only valid if @e is_valid * is #GNUNET_YES. @@ -1606,6 +1612,7 @@ struct WtidTransactionContext * @param coin_pub which public key was this payment about * @param deposit_value amount contributed by this coin in total * @param deposit_fee deposit fee charged by mint for this coin + * @param transaction_value total value of the wire transaction */ static void handle_transaction_data (void *cls, @@ -1615,7 +1622,8 @@ handle_transaction_data (void *cls, uint64_t transaction_id, const struct TALER_CoinSpendPublicKeyP *coin_pub, const struct TALER_Amount *deposit_value, - const struct TALER_Amount *deposit_fee) + const struct TALER_Amount *deposit_fee, + const struct TALER_Amount *transaction_value) { struct WtidTransactionContext *ctx = cls; struct TALER_Amount delta; @@ -1626,6 +1634,7 @@ handle_transaction_data (void *cls, { ctx->merchant_pub = *merchant_pub; ctx->h_wire = *h_wire; + ctx->db_transaction_value = *transaction_value; ctx->is_valid = GNUNET_YES; if (GNUNET_OK != TALER_amount_subtract (&ctx->total, @@ -1644,7 +1653,9 @@ handle_transaction_data (void *cls, sizeof (struct TALER_MerchantPublicKeyP))) || (0 != memcmp (&ctx->h_wire, h_wire, - sizeof (struct GNUNET_HashCode))) ) + sizeof (struct GNUNET_HashCode))) || + (0 != TALER_amount_cmp (transaction_value, + &ctx->db_transaction_value)) ) { GNUNET_break (0); ctx->is_valid = GNUNET_SYSERR; @@ -1693,17 +1704,25 @@ handle_transaction_data (void *cls, */ int TMH_DB_execute_wire_deposits (struct MHD_Connection *connection, - const struct TALER_WireTransferIdentifierP *wtid) + const struct TALER_WireTransferIdentifierRawP *wtid) { int ret; struct WtidTransactionContext ctx; + struct TALER_MINTDB_Session *session; + if (NULL == (session = TMH_plugin->get_session (TMH_plugin->cls, + TMH_test_mode))) + { + GNUNET_break (0); + return TMH_RESPONSE_reply_internal_db_error (connection); + } ctx.is_valid = GNUNET_NO; ctx.deposits = json_array (); - ret = TMH_plugin->lookup_wire_transactions (TMH_plugin->cls, - &wtid->raw, - &handle_transaction_data, - &ctx); + ret = TMH_plugin->lookup_wire_transfer (TMH_plugin->cls, + session, + wtid, + &handle_transaction_data, + &ctx); if (GNUNET_SYSERR == ret) { GNUNET_break (0); @@ -1722,8 +1741,15 @@ TMH_DB_execute_wire_deposits (struct MHD_Connection *connection, return TMH_RESPONSE_reply_arg_unknown (connection, "wtid"); } + if (0 != TALER_amount_cmp (&ctx.total, + &ctx.db_transaction_value)) + { + /* FIXME: this CAN actually differ, due to rounding + down. But we should still check that the values + do match after rounding 'total' down! */ + } return TMH_RESPONSE_reply_wire_deposit_details (connection, - &ctx.total, + &ctx.db_transaction_value, &ctx.merchant_pub, &ctx.h_wire, ctx.deposits); @@ -1776,7 +1802,8 @@ struct DepositWtidContext * @param wtid raw wire transfer identifier, NULL * if the transaction was not yet done * @param coin_contribution how much did the coin we asked about - * contribute to the total transfer value? (deposit value minus fee) + * contribute to the total transfer value? (deposit value including fee) + * @param coin_fee how much did the mint charge for the deposit fee * @param total_amount how much was the total wire transfer? * @param execution_time when was the transaction done, or * when we expect it to be done (if @a wtid was NULL); @@ -1787,31 +1814,40 @@ static void handle_wtid_data (void *cls, const struct TALER_WireTransferIdentifierRawP *wtid, const struct TALER_Amount *coin_contribution, + const struct TALER_Amount *coin_fee, const struct TALER_Amount *total_amount, struct GNUNET_TIME_Absolute execution_time) { struct DepositWtidContext *ctx = cls; + struct TALER_Amount coin_delta; if (NULL == wtid) { - if (GNUNET_TIME_UNIT_FOREVER_ABS.abs_value_us == - execution_time.abs_value_us) - ctx->res = TMH_RESPONSE_reply_deposit_unknown (ctx->connection); - else - ctx->res = TMH_RESPONSE_reply_deposit_pending (ctx->connection, - execution_time); + ctx->res = TMH_RESPONSE_reply_deposit_pending (ctx->connection, + execution_time); } else { - ctx->res = TMH_RESPONSE_reply_deposit_wtid (ctx->connection, - &ctx->h_contract, - &ctx->h_wire, - &ctx->coin_pub, - coin_contribution, - total_amount, - ctx->transaction_id, - wtid, - execution_time); + if (GNUNET_SYSERR == + TALER_amount_subtract (&coin_delta, + coin_contribution, + coin_fee)) + { + GNUNET_break (0); + ctx->res = TMH_RESPONSE_reply_internal_db_error (ctx->connection); + } + else + { + ctx->res = TMH_RESPONSE_reply_deposit_wtid (ctx->connection, + &ctx->h_contract, + &ctx->h_wire, + &ctx->coin_pub, + &coin_delta, + total_amount, + ctx->transaction_id, + wtid, + execution_time); + } } } @@ -1838,14 +1874,22 @@ TMH_DB_execute_deposit_wtid (struct MHD_Connection *connection, { int ret; struct DepositWtidContext ctx; + struct TALER_MINTDB_Session *session; + if (NULL == (session = TMH_plugin->get_session (TMH_plugin->cls, + TMH_test_mode))) + { + GNUNET_break (0); + return TMH_RESPONSE_reply_internal_db_error (connection); + } ctx.connection = connection; ctx.h_contract = *h_contract; ctx.h_wire = *h_wire; ctx.coin_pub = *coin_pub; ctx.transaction_id = transaction_id; - ctx.res = MHD_NO; /* this value should never be read... */ + ctx.res = GNUNET_SYSERR; ret = TMH_plugin->wire_lookup_deposit_wtid (TMH_plugin->cls, + session, h_contract, h_wire, coin_pub, @@ -1856,8 +1900,20 @@ TMH_DB_execute_deposit_wtid (struct MHD_Connection *connection, if (GNUNET_SYSERR == ret) { GNUNET_break (0); + GNUNET_break (GNUNET_SYSERR == ctx.res); return TMH_RESPONSE_reply_internal_db_error (connection); } + if (GNUNET_NO == ret) + { + GNUNET_break (GNUNET_SYSERR == ctx.res); + return TMH_RESPONSE_reply_deposit_unknown (connection); + } + if (GNUNET_SYSERR == ctx.res) + { + GNUNET_break (0); + return TMH_RESPONSE_reply_internal_error (connection, + "bug resolving deposit wtid"); + } return ctx.res; } diff --git a/src/mint/taler-mint-httpd_db.h b/src/mint/taler-mint-httpd_db.h index e366112dd..0327bef2a 100644 --- a/src/mint/taler-mint-httpd_db.h +++ b/src/mint/taler-mint-httpd_db.h @@ -202,7 +202,7 @@ TMH_DB_execute_admin_add_incoming (struct MHD_Connection *connection, */ int TMH_DB_execute_wire_deposits (struct MHD_Connection *connection, - const struct TALER_WireTransferIdentifierP *wtid); + const struct TALER_WireTransferIdentifierRawP *wtid); /** diff --git a/src/mint/taler-mint-httpd_responses.c b/src/mint/taler-mint-httpd_responses.c index 98c362836..041f694bf 100644 --- a/src/mint/taler-mint-httpd_responses.c +++ b/src/mint/taler-mint-httpd_responses.c @@ -1081,7 +1081,7 @@ TMH_RESPONSE_reply_deposit_pending (struct MHD_Connection *connection, struct GNUNET_TIME_Absolute planned_exec_time) { return TMH_RESPONSE_reply_json_pack (connection, - MHD_HTTP_FOUND, + MHD_HTTP_ACCEPTED, "{s:o}", "execution_time", TALER_json_from_abs (planned_exec_time)); } @@ -1117,11 +1117,7 @@ TMH_RESPONSE_reply_deposit_wtid (struct MHD_Connection *connection, struct TALER_ConfirmWirePS cw; struct TALER_MintPublicKeyP pub; struct TALER_MintSignatureP sig; - struct TALER_WireTransferIdentifierP wtid_crc; - char *wtid_s; - int ret; - /* Create signature for the reply */ cw.purpose.purpose = htonl (TALER_SIGNATURE_MINT_CONFIRM_WIRE); cw.purpose.size = htonl (sizeof (struct TALER_ConfirmWirePS)); cw.h_wire = *h_wire; @@ -1137,24 +1133,18 @@ TMH_RESPONSE_reply_deposit_wtid (struct MHD_Connection *connection, TMH_KS_sign (&cw.purpose, &pub, &sig); - /* Compute checksum and crockford encoding if wire transfer subject */ - wtid_crc.raw = *wtid; - wtid_crc.crc8 = GNUNET_CRYPTO_crc8_n (wtid, - sizeof (struct TALER_WireTransferIdentifierRawP)); - - wtid_s = GNUNET_STRINGS_data_to_string_alloc (&wtid_crc, - sizeof (wtid_crc)); - ret = TMH_RESPONSE_reply_json_pack (connection, + return TMH_RESPONSE_reply_json_pack (connection, MHD_HTTP_OK, - "{s:s, s:o, s:o, s:o}", - "wtid", wtid_s, + "{s:o, s:o, s:o, s:o, s:o, s:o}", + "wtid", TALER_json_from_data (wtid, + sizeof (*wtid)), "execution_time", TALER_json_from_abs (exec_time), + "coin_contribution", TALER_json_from_amount (coin_contribution), + "total_amount", TALER_json_from_amount (total_amount), "mint_sig", TALER_json_from_data (&sig, sizeof (sig)), "mint_pub", TALER_json_from_data (&pub, sizeof (pub))); - GNUNET_free (wtid_s); - return ret; } diff --git a/src/mint/taler-mint-httpd_tracking.c b/src/mint/taler-mint-httpd_tracking.c index e61b4bae1..a6b41cf86 100644 --- a/src/mint/taler-mint-httpd_tracking.c +++ b/src/mint/taler-mint-httpd_tracking.c @@ -46,22 +46,17 @@ TMH_TRACKING_handler_wire_deposits (struct TMH_RequestHandler *rh, const char *upload_data, size_t *upload_data_size) { - struct TALER_WireTransferIdentifierP wtid; + struct TALER_WireTransferIdentifierRawP wtid; int res; res = TMH_PARSE_mhd_request_arg_data (connection, "wtid", &wtid, - sizeof (struct TALER_WireTransferIdentifierP)); + sizeof (struct TALER_WireTransferIdentifierRawP)); if (GNUNET_SYSERR == res) return MHD_NO; /* internal error */ if (GNUNET_NO == res) return MHD_YES; /* parse error */ - if (wtid.crc8 != - GNUNET_CRYPTO_crc8_n (&wtid.raw, - sizeof (wtid.raw))) - return TMH_RESPONSE_reply_arg_invalid (connection, - "wtid"); return TMH_DB_execute_wire_deposits (connection, &wtid); } @@ -126,13 +121,12 @@ TMH_TRACKING_handler_deposit_wtid (struct TMH_RequestHandler *rh, struct TALER_DepositTrackPS tps; uint64_t transaction_id; struct TALER_MerchantSignatureP merchant_sig; - struct TALER_MerchantPublicKeyP merchant_pub; struct TMH_PARSE_FieldSpecification spec[] = { TMH_PARSE_member_fixed ("H_wire", &tps.h_wire), TMH_PARSE_member_fixed ("H_contract", &tps.h_contract), TMH_PARSE_member_fixed ("coin_pub", &tps.coin_pub), TMH_PARSE_member_uint64 ("transaction_id", &transaction_id), - TMH_PARSE_member_fixed ("merchant_pub", &merchant_pub), + TMH_PARSE_member_fixed ("merchant_pub", &tps.merchant), TMH_PARSE_member_fixed ("merchant_sig", &merchant_sig), TMH_PARSE_MEMBER_END }; @@ -159,7 +153,7 @@ TMH_TRACKING_handler_deposit_wtid (struct TMH_RequestHandler *rh, tps.transaction_id = GNUNET_htonll (transaction_id); res = check_and_handle_deposit_wtid_request (connection, &tps, - &merchant_pub, + &tps.merchant, &merchant_sig, transaction_id); TMH_PARSE_release_data (spec); diff --git a/src/mintdb/perf_taler_mintdb_init.c b/src/mintdb/perf_taler_mintdb_init.c index 97a1b4c99..ccfc6a05a 100644 --- a/src/mintdb/perf_taler_mintdb_init.c +++ b/src/mintdb/perf_taler_mintdb_init.c @@ -67,9 +67,11 @@ PERF_TALER_MINTDB_denomination_init () properties.expire_withdraw = GNUNET_TIME_absolute_hton (GNUNET_TIME_absolute_get_forever_()); properties.expire_spend = GNUNET_TIME_absolute_hton (GNUNET_TIME_absolute_get_forever_()); properties.expire_legal = GNUNET_TIME_absolute_hton (GNUNET_TIME_absolute_get_forever_()); - TALER_string_to_amount (CURRENCY ":1.1", &amount); + GNUNET_assert (GNUNET_OK == + TALER_string_to_amount (CURRENCY ":1.1", &amount)); TALER_amount_hton (&properties.value, &amount); - TALER_string_to_amount (CURRENCY ":0.1", &amount); + GNUNET_assert (GNUNET_OK == + TALER_string_to_amount (CURRENCY ":0.1", &amount)); TALER_amount_hton (&properties.fee_withdraw, &amount); TALER_amount_hton (&properties.fee_deposit, &amount); TALER_amount_hton (&properties.fee_refresh, &amount); @@ -467,8 +469,8 @@ PERF_TALER_MINTDB_refresh_session_free (struct TALER_MINTDB_RefreshSession *refr { if (NULL == refresh_session) return GNUNET_OK; - return GNUNET_OK; GNUNET_free (refresh_session); + return GNUNET_OK; } @@ -502,10 +504,12 @@ PERF_TALER_MINTDB_refresh_melt_init (struct GNUNET_HashCode *session, &to_sign.purpose, &coin_sig.eddsa_signature); } - GNUNET_assert (GNUNET_OK == TALER_string_to_amount (CURRENCY ":1.1", - &amount)); - GNUNET_assert (GNUNET_OK == TALER_string_to_amount (CURRENCY ":0.1", - &amount_with_fee)); + GNUNET_assert (GNUNET_OK == + TALER_string_to_amount (CURRENCY ":1.1", + &amount)); + GNUNET_assert (GNUNET_OK == + TALER_string_to_amount (CURRENCY ":0.1", + &amount_with_fee)); melt = GNUNET_new (struct TALER_MINTDB_RefreshMelt); melt->coin.coin_pub = coin->public_info.coin_pub; melt->coin.denom_sig.rsa_signature = diff --git a/src/mintdb/plugin_mintdb_postgres.c b/src/mintdb/plugin_mintdb_postgres.c index ca8edcffd..8da986714 100644 --- a/src/mintdb/plugin_mintdb_postgres.c +++ b/src/mintdb/plugin_mintdb_postgres.c @@ -451,8 +451,8 @@ postgres_create_tables (void *cls, /* Table for the tracking API, mapping from wire transfer identifiers to transactions and back */ SQLEXEC("CREATE TABLE IF NOT EXISTS aggregation_tracking " - "(h_contract BYTEA PRIMARY KEY CHECK (LENGTH(h_contract)=64)" - ",h_wire BYTEA PRIMARY KEY CHECK (LENGTH(h_wire)=64)" + "(h_contract BYTEA CHECK (LENGTH(h_contract)=64)" + ",h_wire BYTEA CHECK (LENGTH(h_wire)=64)" ",coin_pub BYTEA NOT NULL CHECK (LENGTH(coin_pub)=32)" ",merchant_pub BYTEA NOT NULL CHECK (LENGTH(merchant_pub)=32)" ",transaction_id INT8 NOT NULL" @@ -461,9 +461,12 @@ postgres_create_tables (void *cls, ",coin_amount_val INT8 NOT NULL" ",coin_amount_frac INT4 NOT NULL" ",coin_amount_curr VARCHAR("TALER_CURRENCY_LEN_STR") NOT NULL" - ",transaction_total_val INT8 NOT NULL" - ",transaction_total_frac INT4 NOT NULL" - ",transaction_total_curr VARCHAR("TALER_CURRENCY_LEN_STR") NOT NULL" + ",coin_fee_val INT8 NOT NULL" + ",coin_fee_frac INT4 NOT NULL" + ",coin_fee_curr VARCHAR("TALER_CURRENCY_LEN_STR") NOT NULL" + ",transfer_total_val INT8 NOT NULL" + ",transfer_total_frac INT4 NOT NULL" + ",transfer_total_curr VARCHAR("TALER_CURRENCY_LEN_STR") NOT NULL" ")"); /* Index for lookup_transactions statement on wtid */ SQLEXEC_INDEX("CREATE INDEX aggregation_tracking_wtid_index " @@ -708,7 +711,7 @@ postgres_prepare (PGconn *db_conn) "SELECT" " denom_pub" ",denom_sig" - " FROM known_coins " + " FROM known_coins" " WHERE coin_pub=$1", 1, NULL); /* Used in #postgres_insert_known_coin() to store @@ -875,6 +878,26 @@ postgres_prepare (PGconn *db_conn) " (merchant_pub=$3)" " )", 3, NULL); + /* Fetch an existing deposit request. + Used in #postgres_wire_lookup_deposit_wtid(). */ + PREPARE ("get_deposit_for_wtid", + "SELECT" + " amount_with_fee_val" + ",amount_with_fee_frac" + ",amount_with_fee_curr" + ",deposit_fee_val" + ",deposit_fee_frac" + ",deposit_fee_curr" + ",wire_deadline" + " FROM deposits" + " WHERE (" + " (coin_pub=$1) AND" + " (transaction_id=$2) AND" + " (merchant_pub=$3) AND" + " (h_contract=$4) AND" + " (h_wire=$5)" + " )", + 5, NULL); /* Used in #postgres_iterate_deposits() */ PREPARE ("deposits_iterate", @@ -972,7 +995,7 @@ postgres_prepare (PGconn *db_conn) " AND rm.oldcoin_index = rcl.oldcoin_index" " AND rcl.cnc_index=rs.noreveal_index", 1, NULL); - /* Used in #postgres_lookup_wire_transactions */ + /* Used in #postgres_lookup_wire_transfer */ PREPARE ("lookup_transactions", "SELECT" " h_contract" @@ -984,9 +1007,12 @@ postgres_prepare (PGconn *db_conn) ",coin_amount_val" ",coin_amount_frac" ",coin_amount_curr" - ",transaction_total_val" - ",transaction_total_frac" - ",transaction_total_curr" + ",coin_fee_val" + ",coin_fee_frac" + ",coin_fee_curr" + ",transfer_total_val" + ",transfer_total_frac" + ",transfer_total_curr" " FROM aggregation_tracking" " WHERE wtid_raw=$1", 1, NULL); @@ -998,9 +1024,12 @@ postgres_prepare (PGconn *db_conn) ",coin_amount_val" ",coin_amount_frac" ",coin_amount_curr" - ",transaction_total_val" - ",transaction_total_frac" - ",transaction_total_curr" + ",coin_fee_val" + ",coin_fee_frac" + ",coin_fee_curr" + ",transfer_total_val" + ",transfer_total_frac" + ",transfer_total_curr" " FROM aggregation_tracking" " WHERE" " coin_pub=$1 AND" @@ -1022,12 +1051,15 @@ postgres_prepare (PGconn *db_conn) ",coin_amount_val" ",coin_amount_frac" ",coin_amount_curr" - ",transaction_total_val" - ",transaction_total_frac" - ",transaction_total_curr" + ",coin_fee_val" + ",coin_fee_frac" + ",coin_fee_curr" + ",transfer_total_val" + ",transfer_total_frac" + ",transfer_total_curr" ") VALUES " - "($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13)", - 13, NULL); + "($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16)", + 16, NULL); return GNUNET_OK; #undef PREPARE @@ -3451,19 +3483,87 @@ postgres_get_coin_transactions (void *cls, * into a wire transfer by the respective @a wtid. * * @param cls closure + * @param session database connection * @param wtid the raw wire transfer identifier we used * @param cb function to call on each transaction found * @param cb_cls closure for @a cb - * @return #GNUNET_OK on success, #GNUNET_SYSERR on database errors + * @return #GNUNET_OK on success, #GNUNET_SYSERR on database errors, + * #GNUNET_NO if we found no results */ static int -postgres_lookup_wire_transactions (void *cls, - const struct TALER_WireTransferIdentifierRawP *wtid, - TALER_MINTDB_TransactionDataCallback cb, - void *cb_cls) +postgres_lookup_wire_transfer (void *cls, + struct TALER_MINTDB_Session *session, + const struct TALER_WireTransferIdentifierRawP *wtid, + TALER_MINTDB_WireTransferDataCallback cb, + void *cb_cls) { - GNUNET_break (0); // not implemented! - return GNUNET_SYSERR; + PGresult *result; + struct TALER_PQ_QueryParam params[] = { + TALER_PQ_query_param_auto_from_type (wtid), + TALER_PQ_query_param_end + }; + int nrows; + int i; + + /* check if the melt record exists and get it */ + result = TALER_PQ_exec_prepared (session->conn, + "lookup_transactions", + params); + if (PGRES_TUPLES_OK != PQresultStatus (result)) + { + BREAK_DB_ERR (result); + PQclear (result); + return GNUNET_SYSERR; + } + nrows = PQntuples (result); + if (0 == nrows) + { + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "lookup_wire_transfer() returned 0 matching rows\n"); + PQclear (result); + return GNUNET_NO; + } + for (i=0;iconn, + "lookup_deposit_wtid", + params); + if (PGRES_TUPLES_OK != PQresultStatus (result)) + { + BREAK_DB_ERR (result); + PQclear (result); + return GNUNET_SYSERR; + } + nrows = PQntuples (result); + if (0 == nrows) + { + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "lookup_deposit_wtid returned 0 matching rows\n"); + PQclear (result); + + /* Check if transaction exists in deposits, so that we just + do not have a WTID yet, if so, do call the CB with a NULL wtid + and return GNUNET_YES! */ + { + struct TALER_PQ_QueryParam params2[] = { + TALER_PQ_query_param_auto_from_type (coin_pub), + TALER_PQ_query_param_uint64 (&transaction_id), + TALER_PQ_query_param_auto_from_type (merchant_pub), + TALER_PQ_query_param_auto_from_type (h_contract), + TALER_PQ_query_param_auto_from_type (h_wire), + TALER_PQ_query_param_end + }; + + result = TALER_PQ_exec_prepared (session->conn, + "get_deposit_for_wtid", + params2); + if (PGRES_TUPLES_OK != PQresultStatus (result)) + { + BREAK_DB_ERR (result); + PQclear (result); + return GNUNET_SYSERR; + } + } + nrows = PQntuples (result); + if (0 == nrows) + { + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "get_deposit_for_wtid returned 0 matching rows\n"); + PQclear (result); + return GNUNET_NO; + } + + /* Ok, we're aware of the transaction, but it has not yet been + executed */ + { + struct GNUNET_TIME_Absolute exec_time; + struct TALER_Amount coin_amount; + struct TALER_Amount coin_fee; + struct TALER_PQ_ResultSpec rs[] = { + TALER_PQ_result_spec_amount ("amount_with_fee", &coin_amount), + TALER_PQ_result_spec_amount ("deposit_fee", &coin_fee), + TALER_PQ_result_spec_absolute_time ("wire_deadline", &exec_time), + TALER_PQ_result_spec_end + }; + + if (GNUNET_OK != TALER_PQ_extract_result (result, rs, 0)) + { + GNUNET_break (0); + PQclear (result); + return GNUNET_SYSERR; + } + cb (cb_cls, + NULL, + &coin_amount, + &coin_fee, + NULL, + exec_time); + PQclear (result); + return GNUNET_YES; + } + } + if (1 != nrows) + { + GNUNET_break (0); + PQclear (result); + return GNUNET_SYSERR; + } + { + struct TALER_WireTransferIdentifierRawP wtid; + struct GNUNET_TIME_Absolute exec_time; + struct TALER_Amount coin_amount; + struct TALER_Amount coin_fee; + struct TALER_Amount transaction_amount; + struct TALER_PQ_ResultSpec rs[] = { + TALER_PQ_result_spec_auto_from_type ("wtid_raw", &wtid), + TALER_PQ_result_spec_absolute_time ("execution_time", &exec_time), + TALER_PQ_result_spec_amount ("coin_amount", &coin_amount), + TALER_PQ_result_spec_amount ("coin_fee", &coin_fee), + TALER_PQ_result_spec_amount ("transfer_total", &transaction_amount), + TALER_PQ_result_spec_end + }; + if (GNUNET_OK != TALER_PQ_extract_result (result, rs, 0)) + { + GNUNET_break (0); + PQclear (result); + return GNUNET_SYSERR; + } + cb (cb_cls, + &wtid, + &coin_amount, + &coin_fee, + &transaction_amount, + exec_time); + } + PQclear (result); + return GNUNET_OK; } @@ -3501,29 +3726,64 @@ postgres_wire_lookup_deposit_wtid (void *cls, * Function called to insert aggregation information into the DB. * * @param cls closure + * @param session database connection * @param wtid the raw wire transfer identifier we used * @param merchant_pub public key of the merchant (should be same for all callbacks with the same @e cls) * @param h_wire hash of wire transfer details of the merchant (should be same for all callbacks with the same @e cls) * @param h_contract which contract was this payment about * @param transaction_id merchant's transaction ID for the payment * @param coin_pub which public key was this payment about - * @param deposit_value amount contributed by this coin in total - * @param deposit_fee deposit fee charged by mint for this coin + * @param coin_value amount contributed by this coin in total + * @param coin_fee deposit fee charged by mint for this coin + * @param transfer_value total amount of the wire transfer * @return #GNUNET_OK on success, #GNUNET_SYSERR on DB errors */ static int postgres_insert_aggregation_tracking (void *cls, + struct TALER_MINTDB_Session *session, const struct TALER_WireTransferIdentifierRawP *wtid, const struct TALER_MerchantPublicKeyP *merchant_pub, const struct GNUNET_HashCode *h_wire, const struct GNUNET_HashCode *h_contract, uint64_t transaction_id, + struct GNUNET_TIME_Absolute execution_time, const struct TALER_CoinSpendPublicKeyP *coin_pub, - const struct TALER_Amount *deposit_value, - const struct TALER_Amount *deposit_fee) + const struct TALER_Amount *coin_value, + const struct TALER_Amount *coin_fee, + const struct TALER_Amount *transfer_value) { - GNUNET_break (0); // not implemented - return GNUNET_SYSERR; + struct TALER_PQ_QueryParam params[] = { + TALER_PQ_query_param_auto_from_type (h_contract), + TALER_PQ_query_param_auto_from_type (h_wire), + TALER_PQ_query_param_auto_from_type (coin_pub), + TALER_PQ_query_param_auto_from_type (merchant_pub), + TALER_PQ_query_param_uint64 (&transaction_id), + TALER_PQ_query_param_auto_from_type (wtid), + TALER_PQ_query_param_absolute_time (&execution_time), + TALER_PQ_query_param_amount (coin_value), + TALER_PQ_query_param_amount (coin_fee), + TALER_PQ_query_param_amount (transfer_value), + TALER_PQ_query_param_end + }; + PGresult *result; + + result = TALER_PQ_exec_prepared (session->conn, + "insert_aggregation_tracking", + params); + if (PGRES_COMMAND_OK != PQresultStatus (result)) + { + BREAK_DB_ERR (result); + PQclear (result); + return GNUNET_SYSERR; + } + if (0 != strcmp ("1", PQcmdTuples (result))) + { + GNUNET_break (0); + PQclear (result); + return GNUNET_SYSERR; + } + PQclear (result); + return GNUNET_OK; } @@ -3596,7 +3856,7 @@ libtaler_plugin_mintdb_postgres_init (void *cls) plugin->get_transfer = &postgres_get_transfer; plugin->get_coin_transactions = &postgres_get_coin_transactions; plugin->free_coin_transaction_list = &common_free_coin_transaction_list; - plugin->lookup_wire_transactions = &postgres_lookup_wire_transactions; + plugin->lookup_wire_transfer = &postgres_lookup_wire_transfer; plugin->wire_lookup_deposit_wtid = &postgres_wire_lookup_deposit_wtid; plugin->insert_aggregation_tracking = &postgres_insert_aggregation_tracking; return plugin; diff --git a/src/mintdb/test_mintdb.c b/src/mintdb/test_mintdb.c index 0cd660a4d..07f5d078a 100644 --- a/src/mintdb/test_mintdb.c +++ b/src/mintdb/test_mintdb.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2014, 2015 GNUnet e.V. + Copyright (C) 2014, 2015, 2016 GNUnet e.V. 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 @@ -14,7 +14,7 @@ TALER; see the file COPYING. If not, If not, see */ /** - * @file mint/test_mintdb.c + * @file mintdb/test_mintdb.c * @brief test cases for DB interaction functions * @author Sree Harsha Totakura */ @@ -305,8 +305,10 @@ test_melting (struct TALER_MINTDB_Session *session) RND_BLK (&refresh_session); RND_BLK (&session_hash); melts = NULL; + dkp = NULL; new_dkp = NULL; new_denom_pubs = NULL; + ret_denom_pubs = NULL; /* create and test a refresh session */ refresh_session.num_oldcoins = MELT_OLD_COINS; refresh_session.num_newcoins = 1; @@ -324,11 +326,11 @@ test_melting (struct TALER_MINTDB_Session *session) sizeof (refresh_session))); /* create a denomination (value: 1; fraction: 100) */ - dkp = create_denom_key_pair(512, session, - &value, - &fee_withdraw, - &fee_deposit, - &fee_refresh); + dkp = create_denom_key_pair (512, session, + &value, + &fee_withdraw, + &fee_deposit, + &fee_refresh); /* create MELT_OLD_COINS number of refresh melts */ melts = GNUNET_new_array (MELT_OLD_COINS, struct TALER_MINTDB_RefreshMelt); for (cnt=0; cnt < MELT_OLD_COINS; cnt++) @@ -416,7 +418,8 @@ test_melting (struct TALER_MINTDB_Session *session) ret = GNUNET_OK; drop: - destroy_denom_key_pair (dkp); + if (NULL != dkp) + destroy_denom_key_pair (dkp); if (NULL != melts) { for (cnt = 0; cnt < MELT_OLD_COINS; cnt++) @@ -439,6 +442,114 @@ test_melting (struct TALER_MINTDB_Session *session) } +/** + * Callback that should never be called. + */ +static void +cb_wt_never (void *cls, + const struct TALER_MerchantPublicKeyP *merchant_pub, + const struct GNUNET_HashCode *h_wire, + const struct GNUNET_HashCode *h_contract, + uint64_t transaction_id, + const struct TALER_CoinSpendPublicKeyP *coin_pub, + const struct TALER_Amount *coin_value, + const struct TALER_Amount *coin_fee, + const struct TALER_Amount *transfer_value) +{ + GNUNET_assert (0); /* this statement should be unreachable */ +} + + +/** + * Callback that should never be called. + */ +static void +cb_wtid_never (void *cls, + const struct TALER_WireTransferIdentifierRawP *wtid, + const struct TALER_Amount *coin_contribution, + const struct TALER_Amount *coin_fee, + const struct TALER_Amount *total_amount, + struct GNUNET_TIME_Absolute execution_time) +{ + GNUNET_assert (0); +} + + +static struct TALER_MerchantPublicKeyP merchant_pub_wt; +static struct GNUNET_HashCode h_wire_wt; +static struct GNUNET_HashCode h_contract_wt; +static uint64_t transaction_id_wt; +static struct TALER_CoinSpendPublicKeyP coin_pub_wt; +static struct TALER_Amount coin_value_wt; +static struct TALER_Amount coin_fee_wt; +static struct TALER_Amount transfer_value_wt; +static struct GNUNET_TIME_Absolute execution_time_wt; +static struct TALER_WireTransferIdentifierRawP wtid_wt; + + +/** + * Callback that should be called with the WT data. + */ +static void +cb_wt_check (void *cls, + const struct TALER_MerchantPublicKeyP *merchant_pub, + const struct GNUNET_HashCode *h_wire, + const struct GNUNET_HashCode *h_contract, + uint64_t transaction_id, + const struct TALER_CoinSpendPublicKeyP *coin_pub, + const struct TALER_Amount *coin_value, + const struct TALER_Amount *coin_fee, + const struct TALER_Amount *transfer_value) +{ + GNUNET_assert (cls == &cb_wt_never); + GNUNET_assert (0 == memcmp (merchant_pub, + &merchant_pub_wt, + sizeof (struct TALER_MerchantPublicKeyP))); + GNUNET_assert (0 == memcmp (h_wire, + &h_wire_wt, + sizeof (struct GNUNET_HashCode))); + GNUNET_assert (0 == memcmp (h_contract, + &h_contract_wt, + sizeof (struct GNUNET_HashCode))); + GNUNET_assert (transaction_id == transaction_id_wt); + GNUNET_assert (0 == memcmp (coin_pub, + &coin_pub_wt, + sizeof (struct TALER_CoinSpendPublicKeyP))); + GNUNET_assert (0 == TALER_amount_cmp (coin_value, + &coin_value_wt)); + GNUNET_assert (0 == TALER_amount_cmp (coin_fee, + &coin_fee_wt)); + GNUNET_assert (0 == TALER_amount_cmp (transfer_value, + &transfer_value_wt)); +} + + +/** + * Callback that should be called with the WT data. + */ +static void +cb_wtid_check (void *cls, + const struct TALER_WireTransferIdentifierRawP *wtid, + const struct TALER_Amount *coin_contribution, + const struct TALER_Amount *coin_fee, + const struct TALER_Amount *total_amount, + struct GNUNET_TIME_Absolute execution_time) +{ + GNUNET_assert (cls == &cb_wtid_never); + GNUNET_assert (0 == memcmp (wtid, + &wtid_wt, + sizeof (struct TALER_WireTransferIdentifierRawP))); + GNUNET_assert (execution_time.abs_value_us == + execution_time_wt.abs_value_us); + GNUNET_assert (0 == TALER_amount_cmp (coin_contribution, + &coin_value_wt)); + GNUNET_assert (0 == TALER_amount_cmp (coin_fee, + &coin_fee_wt)); + GNUNET_assert (0 == TALER_amount_cmp (total_amount, + &transfer_value_wt)); +} + + /** * Main function that will be run by the scheduler. * @@ -455,7 +566,6 @@ run (void *cls, { struct TALER_MINTDB_Session *session; struct TALER_ReservePublicKeyP reserve_pub; - struct TALER_Amount amount; struct DenomKeyPair *dkp; struct TALER_MINTDB_CollectableBlindcoin cbc; struct TALER_MINTDB_CollectableBlindcoin cbc2; @@ -465,6 +575,7 @@ run (void *cls, struct TALER_MINTDB_CollectableBlindcoin *withdraw; struct TALER_MINTDB_Deposit deposit; struct TALER_MINTDB_Deposit deposit2; + struct TALER_WireTransferIdentifierRawP wtid; json_t *wire; json_t *just; const char * const json_wire_str = @@ -563,11 +674,7 @@ run (void *cls, = GNUNET_CRYPTO_rsa_sign (dkp->priv.rsa_private_key, &cbc.h_coin_envelope, sizeof (cbc.h_coin_envelope)); - (void) memcpy (&cbc.reserve_pub, - &reserve_pub, - sizeof (reserve_pub)); - amount.value--; - amount.fraction--; + cbc.reserve_pub = reserve_pub; cbc.amount_with_fee = value; GNUNET_assert (GNUNET_OK == TALER_amount_get_zero (CURRENCY, &cbc.withdraw_fee)); @@ -652,9 +759,7 @@ run (void *cls, plugin->have_deposit (plugin->cls, session, &deposit)); - (void) memcpy (&deposit2, - &deposit, - sizeof (deposit)); + deposit2 = deposit; deposit2.transaction_id++; /* should fail if transaction id is different */ FAILIF (GNUNET_NO != plugin->have_deposit (plugin->cls, @@ -666,15 +771,79 @@ run (void *cls, plugin->have_deposit (plugin->cls, session, &deposit2)); - (void) memcpy (&deposit2.merchant_pub, - &deposit.merchant_pub, - sizeof (deposit.merchant_pub)); + deposit2.merchant_pub = deposit.merchant_pub; RND_BLK (&deposit2.coin.coin_pub); /* should fail if coin is different */ FAILIF (GNUNET_NO != plugin->have_deposit (plugin->cls, session, &deposit2)); FAILIF (GNUNET_OK != test_melting (session)); + + /* setup values for wire transfer aggregation data */ + memset (&wtid, 42, sizeof (wtid)); + memset (&merchant_pub_wt, 43, sizeof (merchant_pub_wt)); + memset (&h_wire_wt, 44, sizeof (h_wire_wt)); + memset (&h_contract_wt, 45, sizeof (h_contract_wt)); + memset (&coin_pub_wt, 46, sizeof (coin_pub_wt)); + transaction_id_wt = 47; + execution_time_wt = GNUNET_TIME_absolute_get (); + memset (&merchant_pub_wt, 48, sizeof (merchant_pub_wt)); + GNUNET_assert (GNUNET_OK == + TALER_string_to_amount (CURRENCY "KUDOS:1.000010", + &coin_value_wt)); + GNUNET_assert (GNUNET_OK == + TALER_string_to_amount (CURRENCY "KUDOS:0.000010", + &coin_fee_wt)); + GNUNET_assert (GNUNET_OK == + TALER_string_to_amount (CURRENCY "KUDOS:1.000000", + &transfer_value_wt)); + + FAILIF (GNUNET_NO != + plugin->lookup_wire_transfer (plugin->cls, + session, + &wtid_wt, + &cb_wt_never, + NULL)); + FAILIF (GNUNET_NO != + plugin->wire_lookup_deposit_wtid (plugin->cls, + session, + &h_contract_wt, + &h_wire_wt, + &coin_pub_wt, + &merchant_pub_wt, + transaction_id_wt, + &cb_wtid_never, + NULL)); + /* insert WT data */ + FAILIF (GNUNET_OK != + plugin->insert_aggregation_tracking (plugin->cls, + session, + &wtid_wt, + &merchant_pub_wt, + &h_wire_wt, + &h_contract_wt, + transaction_id_wt, + execution_time_wt, + &coin_pub_wt, + &coin_value_wt, + &coin_fee_wt, + &transfer_value_wt)); + FAILIF (GNUNET_OK != + plugin->lookup_wire_transfer (plugin->cls, + session, + &wtid_wt, + &cb_wt_check, + &cb_wt_never)); + FAILIF (GNUNET_OK != + plugin->wire_lookup_deposit_wtid (plugin->cls, + session, + &h_contract_wt, + &h_wire_wt, + &coin_pub_wt, + &merchant_pub_wt, + transaction_id_wt, + &cb_wtid_check, + &cb_wtid_never)); result = 0; drop: