exchange

Base system with REST service to issue digital coins, run by the payment service provider
Log | Files | Refs | Submodules | README | LICENSE

commit 46da69710427458d402a55ce617ba28286923dba
parent fd8519d7cb869a5574cec6e686ef9c56505a73d4
Author: Christian Grothoff <grothoff@gnunet.org>
Date:   Sat, 15 Aug 2026 21:15:36 +0200

properly handle DB status on get_serial_by_table

Diffstat:
Msrc/auditor/taler-auditor-sync.c | 73++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------
1 file changed, 64 insertions(+), 9 deletions(-)

diff --git a/src/auditor/taler-auditor-sync.c b/src/auditor/taler-auditor-sync.c @@ -580,6 +580,57 @@ do_insert (struct InsertContext *ctx, /** + * Determine the last serial number of each table in @a db. + * + * Failing to determine a serial must abort the pass: the previous + * value of the field is stale (#tables is file-scope), and continuing + * with it would silently skip records or entire tables. + * + * @param db database to query + * @param source true to store the result in the `end_serial` of each + * table (data source), false for `start_serial` (destination) + * @return #GNUNET_OK on success, #GNUNET_SYSERR to rollback + */ +static enum GNUNET_GenericReturnValue +lookup_serials (struct TALER_EXCHANGEDB_PostgresContext *db, + bool source) +{ + for (unsigned int i = 0; ! tables[i].end; i++) + { + uint64_t *serial = source + ? &tables[i].end_serial + : &tables[i].start_serial; + enum GNUNET_DB_QueryStatus qs; + + qs = TALER_EXCHANGEDB_get_serial_by_table (db, + tables[i].rt, + serial); + switch (qs) + { + case GNUNET_DB_STATUS_HARD_ERROR: + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Failed to determine last serial of table `%s': hard error\n", + rt_name (tables[i].rt)); + global_ret = EXIT_FAILURE; + return GNUNET_SYSERR; + case GNUNET_DB_STATUS_SOFT_ERROR: + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Serialization error determining last serial of table `%s' (will retry)\n", + rt_name (tables[i].rt)); + return GNUNET_SYSERR; + case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: + /* Table is empty; the statement did not write *serial. */ + *serial = 0; + break; + case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: + break; + } + } + return GNUNET_OK; +} + + +/** * Run one replication transaction. * * @return #GNUNET_OK on success, #GNUNET_SYSERR to rollback @@ -595,19 +646,19 @@ transact (void) TALER_EXCHANGEDB_start (src, "lookup src serials")) return GNUNET_SYSERR; - for (unsigned int i = 0; ! tables[i].end; i++) - TALER_EXCHANGEDB_get_serial_by_table (src, - tables[i].rt, - &tables[i].end_serial); + if (GNUNET_OK != + lookup_serials (src, + true)) + return GNUNET_SYSERR; TALER_EXCHANGEDB_rollback (src); if (GNUNET_OK != TALER_EXCHANGEDB_start (dst, "lookup dst serials")) return GNUNET_SYSERR; - for (unsigned int i = 0; ! tables[i].end; i++) - TALER_EXCHANGEDB_get_serial_by_table (dst, - tables[i].rt, - &tables[i].start_serial); + if (GNUNET_OK != + lookup_serials (dst, + false)) + return GNUNET_SYSERR; TALER_EXCHANGEDB_rollback (dst); for (unsigned int i = 0; ! tables[i].end; i++) { @@ -673,7 +724,11 @@ transact (void) GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Serialization error committing transaction on table `%s' (will retry)\n", rt_name (table->rt)); - continue; + /* The rows of the failed commit are gone, but start_serial was + already advanced past them by do_insert(); continuing here + would leave a permanent hole. Restart the pass instead, which + re-reads the serials actually present in the destination. */ + return GNUNET_SYSERR; } if (GNUNET_DB_STATUS_HARD_ERROR == qs) {