diff options
m--------- | contrib/merchant-backoffice | 0 | ||||
-rw-r--r-- | src/backend/taler-merchant-httpd_private-delete-transfers-ID.c | 2 | ||||
-rw-r--r-- | src/backend/taler-merchant-httpd_private-post-transfers.c | 154 | ||||
-rw-r--r-- | src/backenddb/merchant-0001.sql | 2 | ||||
-rw-r--r-- | src/backenddb/merchant-0002.sql | 22 | ||||
-rw-r--r-- | src/backenddb/plugin_merchantdb_postgres.c | 152 | ||||
-rw-r--r-- | src/backenddb/test_merchantdb.c | 4 | ||||
-rw-r--r-- | src/include/taler_merchantdb_plugin.h | 12 | ||||
-rwxr-xr-x | src/testing/initialize_taler_system.sh | 13 | ||||
-rwxr-xr-x | src/testing/test_merchant_order_creation.sh | 121 |
10 files changed, 389 insertions, 93 deletions
diff --git a/contrib/merchant-backoffice b/contrib/merchant-backoffice -Subproject fc9a68c973386afe4c83cf196555b6d01f10a4f +Subproject 1732185ac1d1dcc783b8f2489f2ce333b5254d9 diff --git a/src/backend/taler-merchant-httpd_private-delete-transfers-ID.c b/src/backend/taler-merchant-httpd_private-delete-transfers-ID.c index fd5c968a..93656f4a 100644 --- a/src/backend/taler-merchant-httpd_private-delete-transfers-ID.c +++ b/src/backend/taler-merchant-httpd_private-delete-transfers-ID.c @@ -24,7 +24,7 @@ /** - * Handle a DELETE "/transfers/$ID" request. + * Handle a DELETE "/private/transfers/$ID" request. * * @param rh context of the handler * @param connection the MHD connection to handle diff --git a/src/backend/taler-merchant-httpd_private-post-transfers.c b/src/backend/taler-merchant-httpd_private-post-transfers.c index 4fd0819d..5ac4ec9d 100644 --- a/src/backend/taler-merchant-httpd_private-post-transfers.c +++ b/src/backend/taler-merchant-httpd_private-post-transfers.c @@ -146,6 +146,10 @@ struct PostTransfersContext */ bool downloaded; + /** + * Are we currently suspended? + */ + bool suspended; }; @@ -170,6 +174,7 @@ TMH_force_post_transfers_resume () GNUNET_CONTAINER_DLL_remove (ptc_head, ptc_tail, ptc); + ptc->suspended = false; MHD_resume_connection (ptc->connection); if (NULL != ptc->timeout_task) { @@ -207,6 +212,7 @@ resume_transfer_with_response (struct PostTransfersContext *ptc, GNUNET_CONTAINER_DLL_remove (ptc_head, ptc_tail, ptc); + ptc->suspended = false; MHD_resume_connection (ptc->connection); TMH_trigger_daemon (); /* we resumed, kick MHD */ } @@ -468,8 +474,22 @@ wire_transfer_cb (void *cls, GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Got response code %u from exchange for GET /transfers/$WTID\n", hr->http_status); - if (MHD_HTTP_OK != hr->http_status) + switch (hr->http_status) { + case MHD_HTTP_OK: + break; + case MHD_HTTP_NOT_FOUND: + resume_transfer_with_response ( + ptc, + MHD_HTTP_BAD_GATEWAY, + TALER_MHD_make_json_pack ( + "{s:I, s:I, s:I}", + "code", + (json_int_t) TALER_EC_MERCHANT_PRIVATE_POST_TRANSFERS_EXCHANGE_UNKNOWN, + "exchange_code", (json_int_t) hr->ec, + "exchange_http_status", (json_int_t) hr->http_status)); + return; + default: resume_transfer_with_response ( ptc, MHD_HTTP_BAD_GATEWAY, @@ -482,7 +502,6 @@ wire_transfer_cb (void *cls, "exchange_reply", hr->reply)); return; } - TMH_db->preflight (TMH_db->cls); /* Ok, exchange answer is acceptable, store it */ qs = TMH_db->insert_transfer_details (TMH_db->cls, @@ -504,6 +523,29 @@ wire_transfer_cb (void *cls, NULL); return; } + if (0 == qs) + { + GNUNET_break (0); + resume_transfer_with_error ( + ptc, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_STORE_FAILED, + "insert-transfer-details"); + return; + } + if (0 != + TALER_amount_cmp (&td->total_amount, + &ptc->amount)) + { + resume_transfer_with_error ( + ptc, + MHD_HTTP_CONFLICT, + TALER_EC_MERCHANT_PRIVATE_POST_TRANSFERS_CONFLICTING_TRANSFERS, + NULL); + return; + } + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Transfer details inserted, resuming request...\n"); /* resume processing, main function will build the response */ resume_transfer_with_response (ptc, 0, @@ -536,6 +578,8 @@ process_transfer_with_exchange (void *cls, ptc->fo = NULL; if (NULL == hr) { + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Exchange failed to respond!\n"); resume_transfer_with_response ( ptc, MHD_HTTP_GATEWAY_TIMEOUT, @@ -584,6 +628,8 @@ process_transfer_with_exchange (void *cls, ptc->master_pub = keys->master_pub; } + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Requesting transfer details from exchange\n"); ptc->wdh = TALER_EXCHANGE_transfers_get (eh, &ptc->wtid, &wire_transfer_cb, @@ -664,7 +710,7 @@ verify_exchange_claim_cb (void *cls, ptc->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR; ptc->response = TALER_MHD_make_error (TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE, - "check_transfer_result must not be NULL"); + "check_transfer_result must not be GNUNET_NO"); return; case GNUNET_SYSERR: /* #check_transfer() failed, report conflict! */ @@ -864,6 +910,7 @@ download (struct PostTransfersContext *ptc) GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Suspending POST /private/transfers handling while working with exchange\n"); MHD_suspend_connection (ptc->connection); + ptc->suspended = true; GNUNET_CONTAINER_DLL_insert (ptc_head, ptc_tail, ptc); @@ -895,12 +942,14 @@ TMH_private_post_transfers (const struct TMH_RequestHandler *rh, hc->ctx = ptc; hc->cc = &transfer_cleanup; } + /* resume logic: did we get resumed after a reply was built? */ if (0 != ptc->response_code) return queue (ptc); if ( (NULL != ptc->fo) || (NULL != ptc->wdh) ) { /* likely old MHD version causing spurious wake-up */ + GNUNET_break (ptc->suspended); GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Not sure why we are here, should be suspended\n"); return MHD_YES; /* still work in progress */ @@ -939,6 +988,11 @@ TMH_private_post_transfers (const struct TMH_RequestHandler *rh, TALER_EC_GENERIC_CURRENCY_MISMATCH, TMH_currency); } + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "New inbound wire transfer over %s to %s from %s\n", + TALER_amount2s (&ptc->amount), + ptc->payto_uri, + ptc->exchange_url); } /* Check if transfer data is in database, if not, add it. */ @@ -946,8 +1000,10 @@ TMH_private_post_transfers (const struct TMH_RequestHandler *rh, { struct GNUNET_TIME_Absolute execution_time; struct TALER_Amount total_amount; + struct TALER_Amount exchange_amount; struct TALER_Amount wire_fee; bool verified; + bool have_exchange_sig; TMH_db->preflight (TMH_db->cls); if (GNUNET_OK != @@ -966,7 +1022,9 @@ TMH_private_post_transfers (const struct TMH_RequestHandler *rh, &ptc->wtid, &total_amount, &wire_fee, + &exchange_amount, &execution_time, + &have_exchange_sig, &verified); switch (qs) { @@ -982,7 +1040,9 @@ TMH_private_post_transfers (const struct TMH_RequestHandler *rh, TMH_db->rollback (TMH_db->cls); continue; case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: - /* Transfer so far unknown */ + /* Transfer so far unknown; try to persist the wire transfer information + we have received in the database (it is not yet present). Upon + success, try to download the transfer details from the exchange. */ { uint64_t account_serial; @@ -1019,6 +1079,8 @@ TMH_private_post_transfers (const struct TMH_RequestHandler *rh, break; } + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Inserting new transfer\n"); qs = TMH_db->insert_transfer (TMH_db->cls, ptc->hc->instance->settings.id, ptc->exchange_url, @@ -1030,6 +1092,8 @@ TMH_private_post_transfers (const struct TMH_RequestHandler *rh, { case GNUNET_DB_STATUS_SOFT_ERROR: TMH_db->rollback (TMH_db->cls); + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Soft error, retrying...\n"); continue; case GNUNET_DB_STATUS_HARD_ERROR: GNUNET_break (0); @@ -1039,12 +1103,13 @@ TMH_private_post_transfers (const struct TMH_RequestHandler *rh, TALER_EC_GENERIC_DB_STORE_FAILED, "transfer"); case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: - /* transfer present, but transfer _details_ - missing (hence the SELECT returned an empty - set after intersection via USING part of the - query). Fine, we still need to talk to the - exchange! */ - break; + TMH_db->rollback (TMH_db->cls); + /* Should not happen: we checked earlier! */ + GNUNET_break (0); + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_CONFLICT, + TALER_EC_GENERIC_DB_STORE_FAILED, + "not unique"); case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: break; } @@ -1069,21 +1134,36 @@ TMH_private_post_transfers (const struct TMH_RequestHandler *rh, break; } download (ptc); - return MHD_YES; + return MHD_YES; /* download() always suspends */ } case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: /* Transfer exists */ GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Transfer exists in DB\n"); + "Transfer exists in DB (verified: %s, exchange signature: %s)\n", + verified ? "true" : "false", + have_exchange_sig ? "true" : "false"); if (! verified) { - if (! ptc->downloaded) + if ( (! ptc->downloaded) && + (! have_exchange_sig) ) { /* We may have previously attempted and failed to download the exchange data, do it again! */ TMH_db->rollback (TMH_db->cls); download (ptc); - return MHD_YES; + return MHD_YES; /* download always suspends */ + } + if (! have_exchange_sig) + { + /* We tried to download and still failed to get + an exchange signture. Still, that should have + been handled there. */ + TMH_db->rollback (TMH_db->cls); + GNUNET_break (0); + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE, + "download but no exchange signature and no error"); } /* verify */ if (GNUNET_SYSERR == @@ -1135,6 +1215,52 @@ TMH_private_post_transfers (const struct TMH_RequestHandler *rh, TMH_db->rollback (TMH_db->cls); continue; } + + { + struct TALER_Amount delta; + + if (0 > + TALER_amount_subtract (&delta, + &total_amount, + &wire_fee)) + { + GNUNET_break (0); + return TALER_MHD_reply_with_error ( + connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE, + NULL); + } + if (0 != + TALER_amount_cmp (&exchange_amount, + &delta)) + { + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Amount of expected was %s\n", + TALER_amount2s (&delta)); + return TALER_MHD_reply_with_error ( + connection, + MHD_HTTP_CONFLICT, + TALER_EC_MERCHANT_PRIVATE_POST_TRANSFERS_CONFLICTING_TRANSFERS, + TALER_amount2s (&exchange_amount)); + } + if ( (GNUNET_OK != + TALER_amount_cmp_currency (&ptc->amount, + &delta)) || + (0 != + TALER_amount_cmp (&ptc->amount, + &delta)) ) + { + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Amount submitted was %s\n", + TALER_amount2s (&ptc->amount)); + return TALER_MHD_reply_with_error ( + connection, + MHD_HTTP_CONFLICT, + TALER_EC_MERCHANT_PRIVATE_POST_TRANSFERS_CONFLICTING_SUBMISSION, + TALER_amount2s (&exchange_amount)); + } + } verified = true; qs = TMH_db->set_transfer_status_to_verified (TMH_db->cls, ptc->exchange_url, diff --git a/src/backenddb/merchant-0001.sql b/src/backenddb/merchant-0001.sql index 6e79c7ff..74144e86 100644 --- a/src/backenddb/merchant-0001.sql +++ b/src/backenddb/merchant-0001.sql @@ -368,7 +368,7 @@ COMMENT ON COLUMN merchant_transfers.verified COMMENT ON COLUMN merchant_transfers.confirmed IS 'true once the merchant confirmed that this transfer was received'; COMMENT ON COLUMN merchant_transfers.credit_amount_val - IS 'actual value of the (aggregated) wire transfer, excluding the wire fee'; + IS 'actual value of the (aggregated) wire transfer, excluding the wire fee, according to the merchant'; CREATE TABLE IF NOT EXISTS merchant_transfer_signatures (credit_serial BIGINT PRIMARY KEY diff --git a/src/backenddb/merchant-0002.sql b/src/backenddb/merchant-0002.sql index 13c1f797..46428dd9 100644 --- a/src/backenddb/merchant-0002.sql +++ b/src/backenddb/merchant-0002.sql @@ -31,6 +31,7 @@ COMMENT ON COLUMN merchant_instances.auth_salt IS 'salt to use when hashing Authorization header before comparing with auth_hash'; + -- need to preserve payto_uri for extended reserve API (easier than to reconstruct) ALTER TABLE merchant_tip_reserve_keys ADD COLUMN payto_uri VARCHAR; @@ -38,5 +39,26 @@ COMMENT ON COLUMN merchant_tip_reserve_keys.payto_uri IS 'payto:// URI used to fund the reserve, may be NULL once reserve is funded'; +-- need serial IDs on various tables for exchange-auditor replication +ALTER TABLE merchant_transfer_signatures + ADD COLUMN credit_amount_val INT8, + ADD COLUMN credit_amount_frac INT4; +COMMENT ON COLUMN merchant_transfers.credit_amount_val + IS 'actual value of the (aggregated) wire transfer, excluding the wire fee, according to the exchange'; + + +-- support different amounts claimed by exchange and merchant about wire transfers, +-- add column to tell when this happens; but "believe" existing amounts match, as +-- otherwise earlier version of the code would have failed hard. +UPDATE merchant_transfer_signatures + SET credit_amount_val=mt.credit_amount_val, + credit_amount_frac=mt.credit_amount_frac + FROM merchant_transfer_signatures mts + INNER JOIN merchant_transfers mt USING(credit_serial); +ALTER TABLE merchant_transfer_signatures + ALTER COLUMN credit_amount_val SET NOT NULL, + ALTER COLUMN credit_amount_frac SET NOT NULL; + + -- Complete transaction COMMIT; diff --git a/src/backenddb/plugin_merchantdb_postgres.c b/src/backenddb/plugin_merchantdb_postgres.c index d7621224..e8ba1b44 100644 --- a/src/backenddb/plugin_merchantdb_postgres.c +++ b/src/backenddb/plugin_merchantdb_postgres.c @@ -3525,7 +3525,9 @@ postgres_lookup_account (void *cls, * @param payto_uri what is the merchant's bank account that received the transfer * @param wtid identifier of the wire transfer * @param td transfer details to store - * @return transaction status + * @return transaction status, + * #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS if the @a wtid and @a exchange_uri are not known for this @a instance_id + * #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT on success */ static enum GNUNET_DB_QueryStatus postgres_insert_transfer_details ( @@ -3561,7 +3563,6 @@ RETRY: GNUNET_PQ_query_param_string (payto_uri), GNUNET_PQ_query_param_string (instance_id), GNUNET_PQ_query_param_auto_from_type (wtid), - TALER_PQ_query_param_amount (&td->total_amount), /* excludes wire fee */ GNUNET_PQ_query_param_end }; struct GNUNET_PQ_ResultSpec rs[] = { @@ -3580,12 +3581,19 @@ RETRY: postgres_rollback (pg); if (GNUNET_DB_STATUS_SOFT_ERROR == qs) goto RETRY; + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "'lookup_credit_serial' for account %s and amount %s failed with status %d\n", + payto_uri, + TALER_amount2s (&td->total_amount), + qs); return qs; } if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) { - /* account does not exists, fail! */ postgres_rollback (pg); + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "'lookup_credit_serial' for account %s failed with transfer unknown\n", + payto_uri); return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS; } } @@ -3594,6 +3602,7 @@ RETRY: { struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_uint64 (&credit_serial), + TALER_PQ_query_param_amount (&td->total_amount), TALER_PQ_query_param_amount (&td->wire_fee), GNUNET_PQ_query_param_absolute_time (&td->execution_time), GNUNET_PQ_query_param_auto_from_type (&td->exchange_sig), @@ -3610,11 +3619,24 @@ RETRY: postgres_rollback (pg); if (GNUNET_DB_STATUS_SOFT_ERROR == qs) goto RETRY; + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "'insert_transfer_signature' failed with status %d\n", + qs); return qs; } + if (0 == qs) + { + postgres_rollback (pg); + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "'insert_transfer_signature' failed with status %d\n", + qs); + return GNUNET_DB_STATUS_HARD_ERROR; + } } /* Update transfer-coin association table */ + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Updating transfer-coin association table\n"); for (unsigned int i = 0; i<td->details_length; i++) { const struct TALER_TrackTransferDetails *d = &td->details[i]; @@ -3638,6 +3660,9 @@ RETRY: postgres_rollback (pg); if (GNUNET_DB_STATUS_SOFT_ERROR == qs) goto RETRY; + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "'insert_transfer_to_coin_mapping' failed with status %d\n", + qs); return qs; } } @@ -3645,6 +3670,8 @@ RETRY: that were wired, set the respective order's "wired" status to true, *if* all other deposited coins associated with that order have also been wired (this time or earlier) */ + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Updating contract terms 'wired' status\n"); for (unsigned int i = 0; i<td->details_length; i++) { const struct TALER_TrackTransferDetails *d = &td->details[i]; @@ -3662,9 +3689,14 @@ RETRY: postgres_rollback (pg); if (GNUNET_DB_STATUS_SOFT_ERROR == qs) goto RETRY; + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "'update_wired_by_coin_pub' failed with status %d\n", + qs); return qs; } } + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Committing transaction...\n"); qs = postgres_commit (pg); if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT; @@ -3898,8 +3930,10 @@ postgres_lookup_deposits_by_contract_and_coin ( * @param[out] total_amount amount that was debited from our * aggregate balance at the exchange (in total, sum of * the wire transfer amount and the @a wire_fee) - * @param[out] wire_fee the wire fee the exchange charged - * @param[out] execution_time when the transfer was executed by the exchange + * @param[out] wire_fee the wire fee the exchange charged (only set if @a have_exchange_sig is true) + * @param[out] exchange_amount the amount the exchange claims was transferred (only set if @a have_exchange_sig is true) + * @param[out] execution_time when the transfer was executed by the exchange (only set if @a have_exchange_sig is true) + * @param[out] have_exchange_sig do we have a response from the exchange about this transfer * @param[out] verified did we confirm the transfer was OK * @return transaction status */ @@ -3911,7 +3945,9 @@ postgres_lookup_transfer ( const struct TALER_WireTransferIdentifierRawP *wtid, struct TALER_Amount *total_amount, struct TALER_Amount *wire_fee, + struct TALER_Amount *exchange_amount, struct GNUNET_TIME_Absolute *execution_time, + bool *have_exchange_sig, bool *verified) { struct PostgresClosure *pg = cls; @@ -3923,14 +3959,23 @@ postgres_lookup_transfer ( }; uint8_t verified8; /** Amount we got actually credited, _excludes_ the wire fee */ + bool no_sig; struct TALER_Amount credit_amount; struct GNUNET_PQ_ResultSpec rs[] = { TALER_PQ_RESULT_SPEC_AMOUNT ("credit_amount", &credit_amount), - TALER_PQ_RESULT_SPEC_AMOUNT ("wire_fee", - wire_fee), - GNUNET_PQ_result_spec_absolute_time ("execution_time", - execution_time), + GNUNET_PQ_result_spec_allow_null ( + TALER_PQ_RESULT_SPEC_AMOUNT ("wire_fee", + wire_fee), + &no_sig), + GNUNET_PQ_result_spec_allow_null ( + TALER_PQ_RESULT_SPEC_AMOUNT ("exchange_amount", + exchange_amount), + NULL), + GNUNET_PQ_result_spec_allow_null ( + GNUNET_PQ_result_spec_absolute_time ("execution_time", + execution_time), + NULL), GNUNET_PQ_result_spec_auto_from_type ("verified", &verified8), GNUNET_PQ_result_spec_end @@ -3938,22 +3983,33 @@ postgres_lookup_transfer ( enum GNUNET_DB_QueryStatus qs; check_connection (pg); + *execution_time = GNUNET_TIME_UNIT_ZERO_ABS; qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn, "lookup_transfer", params, rs); + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Lookup transfer returned %d\n", + qs); if (qs > 0) { + *have_exchange_sig = ! no_sig; *verified = (0 != verified8); - if (0 > - TALER_amount_add (total_amount, - &credit_amount, - wire_fee)) + if ( (! no_sig) && + (0 > + TALER_amount_add (total_amount, + &credit_amount, + wire_fee)) ) { GNUNET_break (0); return GNUNET_DB_STATUS_HARD_ERROR; } } + else + { + *verified = false; + *have_exchange_sig = false; + } return qs; } @@ -7814,8 +7870,6 @@ postgres_connect (void *cls) " FROM merchant_transfers" " WHERE exchange_url=$1" " AND wtid=$4" - " AND credit_amount_val=$5" - " AND credit_amount_frac=$6" " AND account_serial=" " (SELECT account_serial" " FROM merchant_accounts" @@ -7825,22 +7879,24 @@ postgres_connect (void *cls) " (SELECT merchant_serial" " FROM merchant_instances" " WHERE merchant_id=$3))", - 2), + 4), /* for postgres_insert_transfer_details() */ GNUNET_PQ_make_prepare ("insert_transfer_signature", "INSERT INTO merchant_transfer_signatures" "(credit_serial" ",signkey_serial" + ",credit_amount_val" + ",credit_amount_frac" ",wire_fee_val" ",wire_fee_frac" ",execution_time" ",exchange_sig) " - "SELECT $1, signkey_serial, $2, $3, $4, $5" + "SELECT $1, signkey_serial, $2, $3, $4, $5, $6, $7" " FROM merchant_exchange_signing_keys" - " WHERE exchange_pub=$6" + " WHERE exchange_pub=$8" " ORDER BY start_date DESC" " LIMIT 1", - 6), + 8), /* for postgres_insert_transfer_details() */ GNUNET_PQ_make_prepare ("insert_transfer_to_coin_mapping", "INSERT INTO merchant_transfer_to_coin" @@ -7925,16 +7981,18 @@ postgres_connect (void *cls) /* for postgres_lookup_transfer() */ GNUNET_PQ_make_prepare ("lookup_transfer", "SELECT" - " credit_amount_val" - ",credit_amount_frac" + " mt.credit_amount_val AS credit_amount_val" + ",mt.credit_amount_frac AS credit_amount_frac" + ",mts.credit_amount_val AS exchange_amount_val" + ",mts.credit_amount_frac AS exchange_amount_frac" ",wire_fee_val" ",wire_fee_frac" ",execution_time" ",verified" - " FROM merchant_transfers" - " JOIN merchant_transfer_signatures USING (credit_serial)" + " FROM merchant_transfers mt" " JOIN merchant_accounts USING (account_serial)" " JOIN merchant_instances USING (merchant_serial)" + " LEFT JOIN merchant_transfer_signatures mts USING (credit_serial)" " WHERE wtid=$2" " AND exchange_url=$1" " AND merchant_id=$3;", @@ -7981,8 +8039,8 @@ postgres_connect (void *cls) /* for postgres_lookup_transfers() */ GNUNET_PQ_make_prepare ("lookup_transfers_time_payto_asc", "SELECT" - " credit_amount_val" - ",credit_amount_frac" + " mt.credit_amount_val" + ",mt.credit_amount_frac" ",wtid" ",merchant_accounts.payto_uri" ",exchange_url" @@ -7990,7 +8048,7 @@ postgres_connect (void *cls) ",merchant_transfer_signatures.execution_time" ",verified" ",confirmed" - " FROM merchant_transfers" + " FROM merchant_transfers mt" " JOIN merchant_accounts USING (account_serial)" " JOIN merchant_transfer_signatures USING (credit_serial)" " WHERE execution_time < $2" @@ -8007,8 +8065,8 @@ postgres_connect (void *cls) /* for postgres_lookup_transfers() */ GNUNET_PQ_make_prepare ("lookup_transfers_time_asc", "SELECT" - " credit_amount_val" - ",credit_amount_frac" + " mt.credit_amount_val" + ",mt.credit_amount_frac" ",wtid" ",merchant_accounts.payto_uri" ",exchange_url" @@ -8016,7 +8074,7 @@ postgres_connect (void *cls) ",merchant_transfer_signatures.execution_time" ",verified" ",confirmed" - " FROM merchant_transfers" + " FROM merchant_transfers mt" " JOIN merchant_accounts USING (account_serial)" " JOIN merchant_transfer_signatures USING (credit_serial)" " WHERE execution_time < $2" @@ -8032,8 +8090,8 @@ postgres_connect (void *cls) /* for postgres_lookup_transfers() */ GNUNET_PQ_make_prepare ("lookup_transfers_payto_asc", "SELECT" - " credit_amount_val" - ",credit_amount_frac" + " mt.credit_amount_val" + ",mt.credit_amount_frac" ",wtid" ",merchant_accounts.payto_uri" ",exchange_url" @@ -8044,7 +8102,7 @@ postgres_connect (void *cls) " END AS execution_time" ",verified" ",confirmed" - " FROM merchant_transfers" + " FROM merchant_transfers mt" " JOIN merchant_accounts USING (account_serial)" " LEFT JOIN merchant_transfer_signatures USING (credit_serial)" " WHERE credit_serial > $2" @@ -8059,8 +8117,8 @@ postgres_connect (void *cls) /* for postgres_lookup_transfers() */ GNUNET_PQ_make_prepare ("lookup_transfers_asc", "SELECT" - " credit_amount_val" - ",credit_amount_frac" + " mt.credit_amount_val" + ",mt.credit_amount_frac" ",wtid" ",merchant_accounts.payto_uri" ",exchange_url" @@ -8071,7 +8129,7 @@ postgres_connect (void *cls) " END AS execution_time" ",verified" ",confirmed" - " FROM merchant_transfers" + " FROM merchant_transfers mt" " JOIN merchant_accounts USING (account_serial)" " LEFT JOIN merchant_transfer_signatures USING (credit_serial)" " WHERE credit_serial > $2" @@ -8085,8 +8143,8 @@ postgres_connect (void *cls) /* for postgres_lookup_transfers() */ GNUNET_PQ_make_prepare ("lookup_transfers_time_payto_desc", "SELECT" - " credit_amount_val" - ",credit_amount_frac" + " mt.credit_amount_val" + ",mt.credit_amount_frac" ",wtid" ",merchant_accounts.payto_uri" ",exchange_url" @@ -8094,7 +8152,7 @@ postgres_connect (void *cls) ",merchant_transfer_signatures.execution_time" ",verified" ",confirmed" - " FROM merchant_transfers" + " FROM merchant_transfers mt" " JOIN merchant_accounts USING (account_serial)" " JOIN merchant_transfer_signatures USING (credit_serial)" " WHERE execution_time < $2" @@ -8111,8 +8169,8 @@ postgres_connect (void *cls) /* for postgres_lookup_transfers() */ GNUNET_PQ_make_prepare ("lookup_transfers_time_desc", "SELECT" - " credit_amount_val" - ",credit_amount_frac" + " mt.credit_amount_val" + ",mt.credit_amount_frac" ",wtid" ",merchant_accounts.payto_uri" ",exchange_url" @@ -8120,7 +8178,7 @@ postgres_connect (void *cls) ",merchant_transfer_signatures.execution_time" ",verified" ",confirmed" - " FROM merchant_transfers" + " FROM merchant_transfers mt" " JOIN merchant_accounts USING (account_serial)" " JOIN merchant_transfer_signatures USING (credit_serial)" " WHERE execution_time < $2" @@ -8136,8 +8194,8 @@ postgres_connect (void *cls) /* for postgres_lookup_transfers() */ GNUNET_PQ_make_prepare ("lookup_transfers_payto_desc", "SELECT" - " credit_amount_val" - ",credit_amount_frac" + " mt.credit_amount_val" + ",mt.credit_amount_frac" ",wtid" ",merchant_accounts.payto_uri" ",exchange_url" @@ -8148,7 +8206,7 @@ postgres_connect (void *cls) " END AS execution_time" ",verified" ",confirmed" - " FROM merchant_transfers" + " FROM merchant_transfers mt" " JOIN merchant_accounts USING (account_serial)" " LEFT JOIN merchant_transfer_signatures USING (credit_serial)" " WHERE credit_serial < $2" @@ -8163,8 +8221,8 @@ postgres_connect (void *cls) /* for postgres_lookup_transfers() */ GNUNET_PQ_make_prepare ("lookup_transfers_desc", "SELECT" - " credit_amount_val" - ",credit_amount_frac" + " mt.credit_amount_val" + ",mt.credit_amount_frac" ",wtid" ",merchant_accounts.payto_uri" ",exchange_url" @@ -8175,7 +8233,7 @@ postgres_connect (void *cls) " END AS execution_time" ",verified" ",confirmed" - " FROM merchant_transfers" + " FROM merchant_transfers mt" " JOIN merchant_accounts USING (account_serial)" " LEFT JOIN merchant_transfer_signatures USING (credit_serial)" " WHERE credit_serial < $2" diff --git a/src/backenddb/test_merchantdb.c b/src/backenddb/test_merchantdb.c index aeb4fca3..b3b43062 100644 --- a/src/backenddb/test_merchantdb.c +++ b/src/backenddb/test_merchantdb.c @@ -3382,7 +3382,9 @@ test_lookup_transfer ( struct TALER_Amount total_with_fee; struct TALER_Amount total; struct TALER_Amount fee; + struct TALER_Amount exchange_amount; struct GNUNET_TIME_Absolute time; + bool esig; bool verified; if (1 != plugin->lookup_transfer (plugin->cls, @@ -3391,7 +3393,9 @@ test_lookup_transfer ( &transfer->wtid, &total, &fee, + &exchange_amount, &time, + &esig, &verified)) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, diff --git a/src/include/taler_merchantdb_plugin.h b/src/include/taler_merchantdb_plugin.h index 594e3876..beeee462 100644 --- a/src/include/taler_merchantdb_plugin.h +++ b/src/include/taler_merchantdb_plugin.h @@ -1613,7 +1613,9 @@ struct TALER_MERCHANTDB_Plugin * @param payto_uri what is the merchant's bank account that received the transfer * @param wtid identifier of the wire transfer * @param td transfer details to store - * @return transaction status + * @return transaction status, + * #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS if the @a wtid and @a exchange_uri are not known for this @a instance_id + * #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT on success */ enum GNUNET_DB_QueryStatus (*insert_transfer_details)( @@ -1686,8 +1688,10 @@ struct TALER_MERCHANTDB_Plugin * @param[out] total_amount amount that was debited from our * aggregate balance at the exchange (in total, sum of * the wire transfer amount and the @a wire_fee) - * @param[out] wire_fee the wire fee the exchange charged - * @param[out] execution_time when the transfer was executed by the exchange + * @param[out] wire_fee the wire fee the exchange charged (only set if @a have_exchange_sig is true) + * @param[out] exchange_amount the amount the exchange claims was transferred (only set if @a have_exchange_sig is true) + * @param[out] execution_time when the transfer was executed by the exchange (only set if @a have_exchange_sig is true) + * @param[out] have_exchange_sig do we have a response from the exchange about this transfer * @param[out] verified did we confirm the transfer was OK * @return transaction status */ @@ -1699,7 +1703,9 @@ struct TALER_MERCHANTDB_Plugin const struct TALER_WireTransferIdentifierRawP *wtid, struct TALER_Amount *total_amount, struct TALER_Amount *wire_fee, + struct TALER_Amount *exchange_amount, struct GNUNET_TIME_Absolute *execution_time, + bool *have_exchange_sig, bool *verified); diff --git a/src/testing/initialize_taler_system.sh b/src/testing/initialize_taler_system.sh index 277d55e6..c4127684 100755 --- a/src/testing/initialize_taler_system.sh +++ b/src/testing/initialize_taler_system.sh @@ -20,6 +20,9 @@ function exit_fail() { # Cleanup to run whenever we exit function cleanup() { + # kill main HTTP servers first + kill ${MERCHANT_HTTPD_PID:-X} &> /dev/null || true + kill ${EXCHANGE_HTTPD_PID:-X} &> /dev/null || true for n in `jobs -p` do kill $n 2> /dev/null || true @@ -108,10 +111,12 @@ echo " OK" # Launch services echo -n "Launching taler services ..." taler-bank-manage-testing $CONF postgres:///$TALER_DB serve > taler-bank.log 2> taler-bank.err & -taler-exchange-secmod-eddsa -c $CONF 2> taler-exchange-secmod-eddsa.log & -taler-exchange-secmod-rsa -c $CONF 2> taler-exchange-secmod-rsa.log & -taler-exchange-httpd -c $CONF 2> taler-exchange-httpd.log & -taler-merchant-httpd -c $CONF -L INFO 2> taler-merchant-httpd.log & +taler-exchange-secmod-eddsa -c $CONF -L DEBUG 2> taler-exchange-secmod-eddsa.log & +taler-exchange-secmod-rsa -c $CONF -L DEBUG 2> taler-exchange-secmod-rsa.log & +taler-exchange-httpd -c $CONF -L DEBUG 2> taler-exchange-httpd.log & +EXCHANGE_HTTPD_PID=$! +taler-merchant-httpd -c $CONF -L DEBUG 2> taler-merchant-httpd.log & +MERCHANT_HTTPD_PID=$! taler-exchange-wirewatch -c $CONF 2> taler-exchange-wirewatch.log & taler-auditor-httpd -L INFO -c $CONF 2> taler-auditor-httpd.log & diff --git a/src/testing/test_merchant_order_creation.sh b/src/testing/test_merchant_order_creation.sh index bbababf0..f596666d 100755 --- a/src/testing/test_merchant_order_creation.sh +++ b/src/testing/test_merchant_order_creation.sh @@ -234,11 +234,11 @@ TO_SLEEP=`echo $(( ($WIRE_DEADLINE /1000) - $NOW ))` echo waiting $TO_SLEEP secs for wire transfer echo -n "Perform wire transfers ..." -taler-exchange-aggregator -c $CONF -T ${TO_SLEEP}000000 -t -L INFO -taler-exchange-transfer -c $CONF -t -L INFO +taler-exchange-aggregator -c $CONF -T ${TO_SLEEP}000000 -t -L INFO &> aggregator.log +taler-exchange-transfer -c $CONF -t -L INFO &> transfer.log echo " DONE" -echo -n "Notifying merchant of wire transfer ..." +echo -n "Obtaining wire transfer details from bank..." # First, extract the wire transfer data from the bank. # As there is no "nice" API, we do this by dumping the @@ -260,20 +260,85 @@ then exit_fail "Wrong exchange URL in subject '$SUBJECT', expected $EXCHANGE_URL" fi +echo " OK" + set +e +export TARGET_PAYTO +export WURL +export WTID +export CREDIT_AMOUNT +export LAST_RESPONSE + +echo -n "Notifying merchant of bogus wire transfer ..." + STATUS=$(curl 'http://localhost:9966/instances/default/private/transfers' \ -d '{"credit_amount":"'$CREDIT_AMOUNT'1","wtid":"'$WTID'","payto_uri":"'$TARGET_PAYTO'","exchange_url":"'$WURL'"}' \ -m 3 \ -w "%{http_code}" -s -o $LAST_RESPONSE) -set -e +if [ "$STATUS" != "409" ] +then + jq . < $LAST_RESPONSE + exit_fail "Expected to fail since the amount is not valid. got: $STATUS" +fi + +echo "OK" +echo -n "Notifying merchant of bogus wire transfer AGAIN ..." + +STATUS=$(curl 'http://localhost:9966/instances/default/private/transfers' \ + -d '{"credit_amount":"'$CREDIT_AMOUNT'1","wtid":"'$WTID'","payto_uri":"'$TARGET_PAYTO'","exchange_url":"'$WURL'"}' \ + -m 3 \ + -w "%{http_code}" -s -o $LAST_RESPONSE) -if [ "$STATUS" != "000" ] +if [ "$STATUS" != "409" ] then + jq . < $LAST_RESPONSE exit_fail "Expected to fail since the amount is not valid. got: $STATUS" fi +echo " OK" +echo -n "Notifying merchant of correct wire transfer (conflicting with old data)..." + +STATUS=$(curl 'http://localhost:9966/instances/default/private/transfers' \ + -d '{"credit_amount":"'$CREDIT_AMOUNT'","wtid":"'$WTID'","payto_uri":"'$TARGET_PAYTO'","exchange_url":"'$WURL'"}' \ + -m 3 \ + -w "%{http_code}" -s -o $LAST_RESPONSE) + +if [ "$STATUS" != "409" ] +then + jq . < $LAST_RESPONSE + exit_fail "Expected response conflict, after providing conflicting transfer data. got: $STATUS" +fi + +echo " OK" + +echo -n "Deleting bogus wire transfer ..." + +TID=`curl -s http://localhost:9966/instances/default/private/transfers | jq -r .transfers[0].transfer_serial_id` +STATUS=$(curl -H "Content-Type: application/json" -X DELETE \ + "http://localhost:9966/instances/default/private/transfers/$TID" \ + -w "%{http_code}" -s -o $LAST_RESPONSE) + +if [ "$STATUS" != "204" ] +then + jq . < $LAST_RESPONSE + exit_fail "Expected response 204 No Content, after deleting valid TID. got: $STATUS" +fi + +STATUS=$(curl -H "Content-Type: application/json" -X DELETE \ + "http://localhost:9966/instances/default/private/transfers/$TID" \ + -w "%{http_code}" -s -o $LAST_RESPONSE) +if [ "$STATUS" != "404" ] +then + jq . < $LAST_RESPONSE + exit_fail "Expected response 404 Not found, after deleting TID again. got: $STATUS" +fi + +echo " OK" + +echo -n "Notifying merchant of correct wire transfer (now working)..." + STATUS=$(curl 'http://localhost:9966/instances/default/private/transfers' \ -d '{"credit_amount":"'$CREDIT_AMOUNT'","wtid":"'$WTID'","payto_uri":"'$TARGET_PAYTO'","exchange_url":"'$WURL'"}' \ -m 3 \ @@ -281,10 +346,14 @@ STATUS=$(curl 'http://localhost:9966/instances/default/private/transfers' \ if [ "$STATUS" != "200" ] then - echo `cat $LAST_RESPONSE` + jq . < $LAST_RESPONSE exit_fail "Expected response ok, after providing transfer data. got: $STATUS" fi +echo " OK" +echo -n "Testing idempotence ..." +set -e + # Test idempotence: do it again! STATUS=$(curl 'http://localhost:9966/instances/default/private/transfers' \ @@ -293,10 +362,12 @@ STATUS=$(curl 'http://localhost:9966/instances/default/private/transfers' \ if [ "$STATUS" != "200" ] then - echo `cat $LAST_RESPONSE` + jq . < $LAST_RESPONSE exit_fail "Expected response ok, after providing transfer data. got: $STATUS" fi +echo " OK" +echo -n "Sending bogus WTID ..." # # CHECK TRANSFER API # @@ -305,44 +376,44 @@ STATUS=$(curl 'http://localhost:9966/instances/default/private/transfers' \ -d '{"credit_amount":"'$CREDIT_AMOUNT'","wtid":"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA","payto_uri":"'$TARGET_PAYTO'","exchange_url":"'$WURL'"}' \ -w "%{http_code}" -s -o $LAST_RESPONSE) -jq . < $LAST_RESPONSE - if [ "$STATUS" != "502" ] then - echo `cat $LAST_RESPONSE` + jq . < $LAST_RESPONSE exit_fail "Expected response invalid since the WTID is fake. got: $STATUS" fi +echo "OK" +echo -n "Fetching wire transfers ..." + STATUS=$(curl 'http://localhost:9966/instances/default/private/transfers' \ -w "%{http_code}" -s -o $LAST_RESPONSE) -jq . < $LAST_RESPONSE - if [ "$STATUS" != "200" ] then - echo `cat $LAST_RESPONSE` - exit_fail "Expected response ok. got: $STATUS" + jq . < $LAST_RESPONSE + exit_fail "Expected response 200 Ok. got: $STATUS" fi TRANSFERS_LIST_SIZE=`jq -r '.transfers | length' < $LAST_RESPONSE` if [ "$TRANSFERS_LIST_SIZE" != "2" ] then - echo `cat $LAST_RESPONSE` + jq . < $LAST_RESPONSE exit_fail "Expected response ok. got: $STATUS" fi +echo "OK" +echo -n "Fetching wire transfer details of bogus WTID ..." + # Test for #6854: use a bogus WTID, causing the exchange to fail to # find the WTID. STATUS=$(curl 'http://localhost:9966/instances/default/private/transfers' \ -d '{"credit_amount":"'$CREDIT_AMOUNT'","wtid":"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA","payto_uri":"'$TARGET_PAYTO'","exchange_url":"'$WURL'"}' \ -w "%{http_code}" -s -o $LAST_RESPONSE) -jq . < $LAST_RESPONSE - if [ "$STATUS" != "502" ] then - echo `cat $LAST_RESPONSE` + jq . < $LAST_RESPONSE exit_fail "Expected response invalid since the WTID is fake. got: $STATUS" fi @@ -353,8 +424,11 @@ echo -n "Checking order status ..." STATUS=$(curl "http://localhost:9966/instances/default/private/orders/${ORDER_ID}?transfer=YES" \ -w "%{http_code}" -s -o $LAST_RESPONSE) + + if [ "$STATUS" != "200" ] then + jq . < $LAST_RESPONSE exit_fail 'should response ok, after order inquiry. got:' $STATUS `cat $LAST_RESPONSE` exit 1 fi @@ -364,13 +438,12 @@ DEPOSIT_TOTAL=`jq -r .deposit_total < $LAST_RESPONSE` if [ "$DEPOSIT_TOTAL" == "TESTKUDOS:0" ] then echo 'deposit total is zero, expected greater than zero. got:' $DEPOSIT_TOTAL `cat $LAST_RESPONSE` - bash exit 1 fi echo " OK" -echo Removing password from account 43 +echo -n "Removing password from account 43 ..." taler-bank-manage -c $CONF --with-db postgres:///$TALER_DB django changepassword_unsafe 43 x >/dev/null 2>/dev/null ACCOUNT_PASSWORD="43:x" @@ -381,7 +454,8 @@ STATUS=$(curl "http://$ACCOUNT_PASSWORD@$BANK_HOST/accounts/43" \ if [ "$STATUS" != "200" ] then - echo 'should response ok, getting account status. got:' $STATUS `cat $LAST_RESPONSE` + jq . < $LAST_RESPONSE + echo "Expected response 200 Ok, getting account status. Got: $STATUS" exit 1 fi @@ -389,10 +463,11 @@ BALANCE=`jq -r .balance.amount < $LAST_RESPONSE` if [ "$BALANCE" == "TESTKUDOS:0" ] then - echo 'Wire transfer did not happen. Got:' $BALANCE `cat $LAST_RESPONSE` + jq . < $LAST_RESPONSE + echo "Wire transfer did not happen. Got: $BALANCE" exit 1 fi - +echo " OK" exit 0 |