exchange

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

commit 53613e2fe786575a5efa9890ccf39e85005e47f5
parent d442697a1f276471a15012c8eba802e725acfebc
Author: Christian Grothoff <christian@grothoff.org>
Date:   Thu,  2 Jan 2025 10:30:11 +0100

fix FIXMEs in taler-helper-auditor-purses: check deletion/refund status and deletion signature

Diffstat:
Msrc/auditor/taler-helper-auditor-purses.c | 231+++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------------
Msrc/exchangedb/Makefile.am | 1+
Msrc/exchangedb/pg_select_all_purse_decisions_above_serial_id.c | 4++--
Asrc/exchangedb/pg_select_all_purse_deletions_above_serial_id.c | 146+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/exchangedb/pg_select_all_purse_deletions_above_serial_id.h | 46++++++++++++++++++++++++++++++++++++++++++++++
Msrc/exchangedb/plugin_exchangedb_postgres.c | 3+++
Msrc/include/taler_exchangedb_plugin.h | 38+++++++++++++++++++++++++++++++++++++-
7 files changed, 395 insertions(+), 74 deletions(-)

diff --git a/src/auditor/taler-helper-auditor-purses.c b/src/auditor/taler-helper-auditor-purses.c @@ -49,6 +49,7 @@ static int test_mode; */ static TALER_ARL_DEF_PP (purse_account_merge_serial_id); static TALER_ARL_DEF_PP (purse_decision_serial_id); +static TALER_ARL_DEF_PP (purse_deletion_serial_id); static TALER_ARL_DEF_PP (purse_deposits_serial_id); static TALER_ARL_DEF_PP (purse_merges_serial_id); static TALER_ARL_DEF_PP (purse_request_serial_id); @@ -325,15 +326,16 @@ struct PurseSummary bool had_pi; /** - * Was the purse deleted? FIXME: Not yet handled (do we need to? purse - * might just appear as expired eventually; but in the meantime, exchange - * may seem to have refunded the coins for no good reason...), also we do - * not yet check the deletion signature. + * Was the purse deleted? Note: as this is set via an UPDATE, it + * may be false at the auditor even if the purse was deleted. Thus, + * this value is only meaningful for *internal* checks. */ bool purse_deleted; /** - * Was the purse refunded? FIXME: Not yet handled (do we need to?) + * Was the purse refunded? Note: as this is set via an UPDATE, it + * may be false at the auditor even if the purse was deleted. Thus, + * this value is only meaningful for *internal* checks. */ bool purse_refunded; @@ -401,7 +403,7 @@ struct PurseContext /** - * Create a new reserve for @a reserve_pub in @a rc. + * Create a new purse for @a purse_pub in @a pc. * * @param[in,out] pc context to update * @param purse_pub key for which to create a purse @@ -987,6 +989,20 @@ handle_purse_decision ( return GNUNET_SYSERR; } } + if ( (internal_checks) && + (! ps->purse_refunded) ) + { + qs = report_row_inconsistency ( + "purse-decision", + rowid, + "purse not marked as refunded (internal check)"); + if (qs < 0) + { + GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); + pc->qs = qs; + return GNUNET_SYSERR; + } + } } else { @@ -1033,6 +1049,67 @@ handle_purse_decision ( /** + * Function called on explicitly deleted purses. + * + * @param cls closure + * @param deletion_serial_id row ID with the deletion data + * @param purse_pub public key of the purse + * @param purse_sig signature affirming deletion of the purse + * @return #GNUNET_OK to continue to iterate + */ +static enum GNUNET_GenericReturnValue +handle_purse_deletion ( + void *cls, + uint64_t deletion_serial_id, + const struct TALER_PurseContractPublicKeyP *purse_pub, + const struct TALER_PurseContractSignatureP *purse_sig) +{ + struct PurseContext *pc = cls; + struct PurseSummary *ps; + + ps = setup_purse (pc, + purse_pub); + if (GNUNET_OK != + TALER_wallet_purse_delete_verify (purse_pub, + purse_sig)) + { + enum GNUNET_DB_QueryStatus qs; + + qs = report_row_inconsistency ( + "purse-delete", + deletion_serial_id, + "purse deletion signature invalid"); + if (qs < 0) + { + GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); + pc->qs = qs; + return GNUNET_SYSERR; + } + } + else + { + if ( (internal_checks) && + (! ps->purse_deleted) ) + { + enum GNUNET_DB_QueryStatus qs; + + qs = report_row_inconsistency ( + "purse-delete", + deletion_serial_id, + "purse not marked as deleted (internal check)"); + if (qs < 0) + { + GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); + pc->qs = qs; + return GNUNET_SYSERR; + } + } + } + return GNUNET_OK; +} + + +/** * Function called on expired purses. * * @param cls closure @@ -1056,7 +1133,6 @@ handle_purse_expired ( .purse_pub = purse_pub->eddsa_pub }; - (void) pc; qs = TALER_ARL_adb->insert_purse_not_closed_inconsistencies ( TALER_ARL_adb->cls, &pnci); @@ -1203,6 +1279,8 @@ analyze_purses (void *cls) { struct PurseContext pc; enum GNUNET_DB_QueryStatus qs; + bool had_pp; + bool had_bal; (void) cls; GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, @@ -1211,6 +1289,7 @@ analyze_purses (void *cls) TALER_ARL_adb->cls, TALER_ARL_GET_PP (purse_account_merge_serial_id), TALER_ARL_GET_PP (purse_decision_serial_id), + TALER_ARL_GET_PP (purse_deletion_serial_id), TALER_ARL_GET_PP (purse_deposits_serial_id), TALER_ARL_GET_PP (purse_merges_serial_id), TALER_ARL_GET_PP (purse_request_serial_id), @@ -1221,26 +1300,29 @@ analyze_purses (void *cls) GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); return qs; } - if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) - { - GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE, - "First analysis using this auditor, starting audit from scratch\n"); - } - else + had_pp = (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs); + if (had_pp) { GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Resuming purse audit at %llu/%llu/%llu/%llu/%llu\n", + "Resuming purse audit at %llu/%llu/%llu/%llu/%llu/%llu\n", (unsigned long long) TALER_ARL_USE_PP ( purse_request_serial_id), (unsigned long long) TALER_ARL_USE_PP ( purse_decision_serial_id), (unsigned long long) TALER_ARL_USE_PP ( + purse_deletion_serial_id), + (unsigned long long) TALER_ARL_USE_PP ( purse_merges_serial_id), (unsigned long long) TALER_ARL_USE_PP ( purse_deposits_serial_id), (unsigned long long) TALER_ARL_USE_PP ( purse_account_merge_serial_id)); } + else + { + GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE, + "First analysis using this auditor, starting audit from scratch\n"); + } pc.qs = GNUNET_DB_STATUS_SUCCESS_ONE_RESULT; qs = TALER_ARL_adb->get_balance ( TALER_ARL_adb->cls, @@ -1257,6 +1339,7 @@ analyze_purses (void *cls) GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); return qs; } + had_bal = (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs); pc.purses = GNUNET_CONTAINER_multihashmap_create (512, GNUNET_NO); @@ -1340,6 +1423,22 @@ analyze_purses (void *cls) return pc.qs; } + qs = TALER_ARL_edb->select_all_purse_deletions_above_serial_id ( + TALER_ARL_edb->cls, + TALER_ARL_USE_PP (purse_deletion_serial_id), + &handle_purse_deletion, + &pc); + if (qs < 0) + { + GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); + return qs; + } + if (pc.qs < 0) + { + GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == pc.qs); + return pc.qs; + } + qs = TALER_ARL_adb->select_purse_expired ( TALER_ARL_adb->cls, &handle_purse_expired, @@ -1369,52 +1468,28 @@ analyze_purses (void *cls) GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == pc.qs); return pc.qs; } - - qs = TALER_ARL_adb->insert_balance ( - TALER_ARL_adb->cls, - TALER_ARL_SET_AB (purse_global_balance), - TALER_ARL_SET_AB (purse_total_balance_insufficient_loss), - TALER_ARL_SET_AB (purse_total_delayed_decisions), - TALER_ARL_SET_AB (purse_total_balance_purse_not_closed), - TALER_ARL_SET_AB (purse_total_arithmetic_delta_plus), - TALER_ARL_SET_AB (purse_total_arithmetic_delta_minus), - TALER_ARL_SET_AB (purse_total_bad_sig_loss), - NULL); - if (0 > qs) - { - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Failed to update auditor DB, not recording progress\n"); - GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); - return qs; - } - - qs = TALER_ARL_adb->update_balance ( - TALER_ARL_adb->cls, - TALER_ARL_SET_AB (purse_global_balance), - TALER_ARL_SET_AB (purse_total_balance_insufficient_loss), - TALER_ARL_SET_AB (purse_total_delayed_decisions), - TALER_ARL_SET_AB (purse_total_balance_purse_not_closed), - TALER_ARL_SET_AB (purse_total_arithmetic_delta_plus), - TALER_ARL_SET_AB (purse_total_arithmetic_delta_minus), - TALER_ARL_SET_AB (purse_total_bad_sig_loss), - NULL); - if (0 > qs) - { - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Failed to update auditor DB, not recording progress\n"); - GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); - return qs; - } - - qs = TALER_ARL_adb->insert_auditor_progress ( - TALER_ARL_adb->cls, - TALER_ARL_SET_PP (purse_account_merge_serial_id), - TALER_ARL_SET_PP (purse_decision_serial_id), - TALER_ARL_SET_PP (purse_deposits_serial_id), - TALER_ARL_SET_PP (purse_merges_serial_id), - TALER_ARL_SET_PP (purse_request_serial_id), - TALER_ARL_SET_PP (purse_open_counter), - NULL); + if (had_bal) + qs = TALER_ARL_adb->update_balance ( + TALER_ARL_adb->cls, + TALER_ARL_SET_AB (purse_global_balance), + TALER_ARL_SET_AB (purse_total_balance_insufficient_loss), + TALER_ARL_SET_AB (purse_total_delayed_decisions), + TALER_ARL_SET_AB (purse_total_balance_purse_not_closed), + TALER_ARL_SET_AB (purse_total_arithmetic_delta_plus), + TALER_ARL_SET_AB (purse_total_arithmetic_delta_minus), + TALER_ARL_SET_AB (purse_total_bad_sig_loss), + NULL); + else + qs = TALER_ARL_adb->insert_balance ( + TALER_ARL_adb->cls, + TALER_ARL_SET_AB (purse_global_balance), + TALER_ARL_SET_AB (purse_total_balance_insufficient_loss), + TALER_ARL_SET_AB (purse_total_delayed_decisions), + TALER_ARL_SET_AB (purse_total_balance_purse_not_closed), + TALER_ARL_SET_AB (purse_total_arithmetic_delta_plus), + TALER_ARL_SET_AB (purse_total_arithmetic_delta_minus), + TALER_ARL_SET_AB (purse_total_bad_sig_loss), + NULL); if (0 > qs) { GNUNET_log (GNUNET_ERROR_TYPE_INFO, @@ -1422,16 +1497,28 @@ analyze_purses (void *cls) GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); return qs; } - - qs = TALER_ARL_adb->update_auditor_progress ( - TALER_ARL_adb->cls, - TALER_ARL_SET_PP (purse_account_merge_serial_id), - TALER_ARL_SET_PP (purse_decision_serial_id), - TALER_ARL_SET_PP (purse_deposits_serial_id), - TALER_ARL_SET_PP (purse_merges_serial_id), - TALER_ARL_SET_PP (purse_request_serial_id), - TALER_ARL_SET_PP (purse_open_counter), - NULL); + if (had_pp) + qs = TALER_ARL_adb->update_auditor_progress ( + TALER_ARL_adb->cls, + TALER_ARL_SET_PP (purse_account_merge_serial_id), + TALER_ARL_SET_PP (purse_decision_serial_id), + TALER_ARL_SET_PP (purse_deletion_serial_id), + TALER_ARL_SET_PP (purse_deposits_serial_id), + TALER_ARL_SET_PP (purse_merges_serial_id), + TALER_ARL_SET_PP (purse_request_serial_id), + TALER_ARL_SET_PP (purse_open_counter), + NULL); + else + qs = TALER_ARL_adb->insert_auditor_progress ( + TALER_ARL_adb->cls, + TALER_ARL_SET_PP (purse_account_merge_serial_id), + TALER_ARL_SET_PP (purse_decision_serial_id), + TALER_ARL_SET_PP (purse_deletion_serial_id), + TALER_ARL_SET_PP (purse_deposits_serial_id), + TALER_ARL_SET_PP (purse_merges_serial_id), + TALER_ARL_SET_PP (purse_request_serial_id), + TALER_ARL_SET_PP (purse_open_counter), + NULL); if (0 > qs) { GNUNET_log (GNUNET_ERROR_TYPE_INFO, @@ -1440,12 +1527,14 @@ analyze_purses (void *cls) return qs; } GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Concluded purse audit step at %llu/%llu/%llu/%llu/%llu\n", + "Concluded purse audit step at %llu/%llu/%llu/%llu/%llu/%llu\n", (unsigned long long) TALER_ARL_USE_PP ( purse_request_serial_id), (unsigned long long) TALER_ARL_USE_PP ( purse_decision_serial_id), (unsigned long long) TALER_ARL_USE_PP ( + purse_deletion_serial_id), + (unsigned long long) TALER_ARL_USE_PP ( purse_merges_serial_id), (unsigned long long) TALER_ARL_USE_PP ( purse_deposits_serial_id), diff --git a/src/exchangedb/Makefile.am b/src/exchangedb/Makefile.am @@ -271,6 +271,7 @@ libtaler_plugin_exchangedb_postgres_la_SOURCES = \ pg_select_aggregation_transient.h pg_select_aggregation_transient.c \ pg_select_aggregations_above_serial.h pg_select_aggregations_above_serial.c \ pg_select_all_purse_decisions_above_serial_id.h pg_select_all_purse_decisions_above_serial_id.c \ + pg_select_all_purse_deletions_above_serial_id.h pg_select_all_purse_deletions_above_serial_id.c \ pg_select_aml_attributes.h pg_select_aml_attributes.c \ pg_select_aml_decisions.h pg_select_aml_decisions.c \ pg_select_aml_measures.h pg_select_aml_measures.c \ diff --git a/src/exchangedb/pg_select_all_purse_decisions_above_serial_id.c b/src/exchangedb/pg_select_all_purse_decisions_above_serial_id.c @@ -126,7 +126,7 @@ TEH_PG_select_all_purse_decisions_above_serial_id ( enum GNUNET_DB_QueryStatus qs; PREPARE (pg, - "audit_get_all_purse_decision_incr", + "audit_select_all_purse_decisions_above_serial_id", "SELECT" " purse_pub" ",refunded" @@ -136,7 +136,7 @@ TEH_PG_select_all_purse_decisions_above_serial_id ( " ORDER BY purse_decision_serial_id ASC;"); qs = GNUNET_PQ_eval_prepared_multi_select ( pg->conn, - "audit_get_all_purse_decision_incr", + "audit_select_all_purse_decisions_above_serial_id", params, &all_purse_decision_serial_helper_cb, &dsc); diff --git a/src/exchangedb/pg_select_all_purse_deletions_above_serial_id.c b/src/exchangedb/pg_select_all_purse_deletions_above_serial_id.c @@ -0,0 +1,146 @@ +/* + This file is part of TALER + Copyright (C) 2025 Taler Systems SA + + 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, see <http://www.gnu.org/licenses/> + */ +/** + * @file exchangedb/pg_select_all_purse_deletions_above_serial_id.c + * @brief Implementation of the select_all_purse_deletions_above_serial_id function for Postgres + * @author Christian Grothoff + */ +#include "platform.h" +#include "taler_error_codes.h" +#include "taler_dbevents.h" +#include "taler_pq_lib.h" +#include "pg_select_all_purse_deletions_above_serial_id.h" +#include "pg_helper.h" + + +/** + * Closure for #all_purse_deletion_serial_helper_cb(). + */ +struct AllPurseDeletionSerialContext +{ + + /** + * Callback to call. + */ + TALER_EXCHANGEDB_AllPurseDeletionsCallback cb; + + /** + * Closure for @e cb. + */ + void *cb_cls; + + /** + * Plugin context. + */ + struct PostgresClosure *pg; + + /** + * Status code, set to #GNUNET_SYSERR on hard errors. + */ + enum GNUNET_GenericReturnValue status; +}; + + +/** + * Helper function to be called with the results of a SELECT statement + * that has returned @a num_results results. + * + * @param cls closure of type `struct PurseRefundSerialContext` + * @param result the postgres result + * @param num_results the number of results in @a result + */ +static void +all_purse_deletion_serial_helper_cb (void *cls, + PGresult *result, + unsigned int num_results) +{ + struct AllPurseDeletionSerialContext *dsc = cls; + + for (unsigned int i = 0; i<num_results; i++) + { + struct TALER_PurseContractPublicKeyP purse_pub; + struct TALER_PurseContractSignatureP purse_sig; + uint64_t rowid; + struct GNUNET_PQ_ResultSpec rs[] = { + GNUNET_PQ_result_spec_auto_from_type ("purse_pub", + &purse_pub), + GNUNET_PQ_result_spec_auto_from_type ("purse_sig", + &purse_sig), + GNUNET_PQ_result_spec_uint64 ("purse_deletion_serial_id", + &rowid), + GNUNET_PQ_result_spec_end + }; + enum GNUNET_GenericReturnValue ret; + + if (GNUNET_OK != + GNUNET_PQ_extract_result (result, + rs, + i)) + { + GNUNET_break (0); + dsc->status = GNUNET_SYSERR; + return; + } + ret = dsc->cb (dsc->cb_cls, + rowid, + &purse_pub, + &purse_sig); + GNUNET_PQ_cleanup_result (rs); + if (GNUNET_OK != ret) + break; + } +} + + +enum GNUNET_DB_QueryStatus +TEH_PG_select_all_purse_deletions_above_serial_id ( + void *cls, + uint64_t serial_id, + TALER_EXCHANGEDB_AllPurseDeletionsCallback cb, + void *cb_cls) +{ + struct PostgresClosure *pg = cls; + struct GNUNET_PQ_QueryParam params[] = { + GNUNET_PQ_query_param_uint64 (&serial_id), + GNUNET_PQ_query_param_end + }; + struct AllPurseDeletionSerialContext dsc = { + .cb = cb, + .cb_cls = cb_cls, + .pg = pg, + .status = GNUNET_OK + }; + enum GNUNET_DB_QueryStatus qs; + + PREPARE (pg, + "audit_select_all_purse_deletions_above_serial_id", + "SELECT" + " purse_pub" + ",purse_sig" + ",purse_deletion_serial_id" + " FROM purse_deletion" + " WHERE purse_deletion_serial_id>=$1" + " ORDER BY purse_deletion_serial_id ASC;"); + qs = GNUNET_PQ_eval_prepared_multi_select ( + pg->conn, + "audit_select_all_purse_deletions_above_serial_id", + params, + &all_purse_deletion_serial_helper_cb, + &dsc); + if (GNUNET_OK != dsc.status) + return GNUNET_DB_STATUS_HARD_ERROR; + return qs; +} diff --git a/src/exchangedb/pg_select_all_purse_deletions_above_serial_id.h b/src/exchangedb/pg_select_all_purse_deletions_above_serial_id.h @@ -0,0 +1,46 @@ +/* + This file is part of TALER + Copyright (C) 2025 Taler Systems SA + + 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, see <http://www.gnu.org/licenses/> + */ +/** + * @file exchangedb/pg_select_all_purse_deletions_above_serial_id.h + * @brief implementation of the select_all_purse_deletions_above_serial_id function for Postgres + * @author Christian Grothoff + */ +#ifndef PG_SELECT_ALL_PURSE_DELETIONS_ABOVE_SERIAL_ID_H +#define PG_SELECT_ALL_PURSE_DELETIONS_ABOVE_SERIAL_ID_H + +#include "taler_util.h" +#include "taler_json_lib.h" +#include "taler_exchangedb_plugin.h" + + +/** + * Select all purse deletions above @a serial_id in monotonically increasing + * order. + * + * @param cls closure + * @param serial_id highest serial ID to exclude (select strictly larger) + * @param cb function to call on each result + * @param cb_cls closure for @a cb + * @return transaction status code + */ +enum GNUNET_DB_QueryStatus +TEH_PG_select_all_purse_deletions_above_serial_id ( + void *cls, + uint64_t serial_id, + TALER_EXCHANGEDB_AllPurseDeletionsCallback cb, + void *cb_cls); + +#endif diff --git a/src/exchangedb/plugin_exchangedb_postgres.c b/src/exchangedb/plugin_exchangedb_postgres.c @@ -175,6 +175,7 @@ #include "pg_select_aggregation_transient.h" #include "pg_select_aggregations_above_serial.h" #include "pg_select_all_purse_decisions_above_serial_id.h" +#include "pg_select_all_purse_deletions_above_serial_id.h" #include "pg_select_aml_attributes.h" #include "pg_select_aml_decisions.h" #include "pg_select_aml_measures.h" @@ -414,6 +415,8 @@ libtaler_plugin_exchangedb_postgres_init (void *cls) = &TEH_PG_select_account_merges_above_serial_id; plugin->select_all_purse_decisions_above_serial_id = &TEH_PG_select_all_purse_decisions_above_serial_id; + plugin->select_all_purse_deletions_above_serial_id + = &TEH_PG_select_all_purse_deletions_above_serial_id; plugin->select_purse = &TEH_PG_select_purse; plugin->select_purse_deposits_above_serial_id diff --git a/src/include/taler_exchangedb_plugin.h b/src/include/taler_exchangedb_plugin.h @@ -2829,6 +2829,24 @@ typedef enum GNUNET_GenericReturnValue /** + * Function called with details about purse deletions that have been made, with + * the goal of auditing the purse's execution. + * + * @param cls closure + * @param rowid unique serial ID for the deposit in our DB + * @param purse_pub public key of the purse + * @param purse_sig signature affirming deletion of the purse + * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop + */ +typedef enum GNUNET_GenericReturnValue +(*TALER_EXCHANGEDB_AllPurseDeletionsCallback)( + void *cls, + uint64_t rowid, + const struct TALER_PurseContractPublicKeyP *purse_pub, + const struct TALER_PurseContractSignatureP *purse_sig); + + +/** * Function called with details about purse refunds that have been made, with * the goal of auditing the purse refund's execution. * @@ -5504,7 +5522,7 @@ struct TALER_EXCHANGEDB_Plugin /** - * Select all purse refunds above @a serial_id in monotonically increasing + * Select all purse decisions above @a serial_id in monotonically increasing * order. * * @param cls closure @@ -5522,6 +5540,24 @@ struct TALER_EXCHANGEDB_Plugin /** + * Select all purse deletions above @a serial_id in monotonically increasing + * order. + * + * @param cls closure + * @param serial_id highest serial ID to exclude (select strictly larger) + * @param cb function to call on each result + * @param cb_cls closure for @a cb + * @return transaction status code + */ + enum GNUNET_DB_QueryStatus + (*select_all_purse_deletions_above_serial_id)( + void *cls, + uint64_t serial_id, + TALER_EXCHANGEDB_AllPurseDeletionsCallback cb, + void *cb_cls); + + + /** * Select coins deposited into a purse. * * @param cls closure