merchant

Merchant backend to process payments, run by merchants
Log | Files | Refs | Submodules | README | LICENSE

commit b04ee597bb545670e2429bd3fce2f9c644fa1ce8
parent 019c04add75050e4c98e406ccbc1682ab2e7243e
Author: Christian Grothoff <christian@grothoff.org>
Date:   Wed,  5 Aug 2026 23:27:48 +0200

remove dead ON CONFLICT clause, ensure we initialize report_serial_id, always

Diffstat:
Msrc/backend/taler-merchant-httpd_post-private-reports.c | 6++++--
Msrc/backenddb/insert_report.c | 1-
Msrc/backenddb/test_merchantdb.c | 50++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/include/merchant-database/insert_report.h | 3++-
4 files changed, 56 insertions(+), 4 deletions(-)

diff --git a/src/backend/taler-merchant-httpd_post-private-reports.c b/src/backend/taler-merchant-httpd_post-private-reports.c @@ -61,7 +61,7 @@ TMH_private_post_reports (const struct TMH_RequestHandler *rh, NULL), GNUNET_JSON_spec_end () }; - uint64_t report_id; + uint64_t report_id = 0; (void) rh; @@ -120,8 +120,10 @@ TMH_private_post_reports (const struct TMH_RequestHandler *rh, frequency, frequency_shift, &report_id); - if (qs < 0) + if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs) { + /* The INSERT either stores the row and returns its serial, or it + fails; anything else would leave @e report_id unset. */ GNUNET_break (0); return TALER_MHD_reply_with_error (connection, MHD_HTTP_INTERNAL_SERVER_ERROR, diff --git a/src/backenddb/insert_report.c b/src/backenddb/insert_report.c @@ -86,7 +86,6 @@ TALER_MERCHANTDB_insert_report ( ",next_transmission)" " VALUES ($1, $2, $3, $4," " $5, $6, $7, $8, $9)" - " ON CONFLICT DO NOTHING" " RETURNING report_serial;"); return GNUNET_PQ_eval_prepared_singleton_select (pg->conn, "", diff --git a/src/backenddb/test_merchantdb.c b/src/backenddb/test_merchantdb.c @@ -57,6 +57,7 @@ #include "merchant-database/insert_pending_webhook.h" #include "merchant-database/insert_product.h" #include "merchant-database/insert_refund_proof.h" +#include "merchant-database/insert_report.h" #include "merchant-database/insert_template.h" #include "merchant-database/insert_transfer.h" #include "merchant-database/insert_transfer_details.h" @@ -858,6 +859,53 @@ cleanup: /** + * Tests that storing a report always yields a fresh serial. + * + * 'merchant_reports' has no unique key other than the identity + * column 'report_serial' that the INSERT never supplies, so the + * 'ON CONFLICT DO NOTHING' the INSERT used to carry could never + * fire. Should a deduplicating key ever be introduced, the INSERT + * must report it (as 'no results') rather than silently return no + * serial: POST /private/reports would otherwise answer 200 OK with + * an uninitialised 'report_serial_id'. + * + * @param instance the instance to store the reports for. + * @return 0 on success, 1 otherwise. + */ +static int +test_insert_report_serial (const struct InstanceData *instance) +{ + uint64_t report_ids[3]; + + TEST_SET_INSTANCE (instance->instance.id, + GNUNET_DB_STATUS_SUCCESS_ONE_RESULT); + for (unsigned int i = 0; i<3; i++) + { + report_ids[i] = 0; + TEST_COND_RET_ON_FAIL (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == + TALER_MERCHANTDB_insert_report ( + pg, + instance->instance.id, + "report-generator-test", + "a test report", + "text/plain", + "/private/orders", + "test@example.com", + GNUNET_TIME_UNIT_DAYS, + GNUNET_TIME_UNIT_ZERO, + &report_ids[i]), + "Insert report failed\n"); + TEST_COND_RET_ON_FAIL (0 != report_ids[i], + "Insert report did not return a serial\n"); + for (unsigned int j = 0; j<i; j++) + TEST_COND_RET_ON_FAIL (report_ids[i] != report_ids[j], + "Insert report returned a duplicate serial\n"); + } + return 0; +} + + +/** * Function that tests instances. * * @param cls closure with config @@ -968,6 +1016,8 @@ run_test_instances (struct TestInstances_Closure *cls) } /* Test that a reconnect does not lose the per-instance search_path */ TEST_RET_ON_FAIL (test_reconnect_search_path (&cls->instances[0])); + /* Test that every stored report gets its own serial */ + TEST_RET_ON_FAIL (test_insert_report_serial (&cls->instances[0])); /* Test instance private key deletion */ TEST_RET_ON_FAIL (test_delete_instance_private_key (&cls->instances[0], GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); diff --git a/src/include/merchant-database/insert_report.h b/src/include/merchant-database/insert_report.h @@ -42,7 +42,8 @@ struct TALER_MERCHANTDB_PostgresContext; * @param frequency report frequency * @param frequency_shift how much to shift the report time from a * multiple of the report @a frequency - * @param[out] report_id serial number of the new pot + * @param[out] report_id serial number of the new pot, only set + * on #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT * @return database result code */ enum GNUNET_DB_QueryStatus