exchange

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

commit a44553fb46d44a2f600b1859160000f3f32bcfbd
parent 4a2f87e8e6a25adf70dc74bd230587d4f9647a4a
Author: Christian Grothoff <christian@grothoff.org>
Date:   Sat,  1 Aug 2026 19:15:58 +0200

track KYC auth in revenue in auditor

Diffstat:
Msrc/auditor/taler-helper-auditor-wire-credit.c | 118+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
Msrc/auditor/test-kyc.sh | 28++++++++++++++++++++++++++++
Msrc/exchangedb/meson.build | 1+
3 files changed, 145 insertions(+), 2 deletions(-)

diff --git a/src/auditor/taler-helper-auditor-wire-credit.c b/src/auditor/taler-helper-auditor-wire-credit.c @@ -51,6 +51,8 @@ #include "exchange-database/preflight.h" #include "exchange-database/rollback.h" #include \ + "exchange-database/iterate_kycauth_in_above_serial_id_by_account.h" +#include \ "exchange-database/iterate_reserves_in_above_serial_id_by_account.h" #include "exchange-database/start_read_only.h" @@ -118,6 +120,11 @@ struct WireAccount uint64_t start_reserve_in_serial_id; /** + * Progress point for this account in the kycauths_in table. + */ + uint64_t last_kycauth_in_serial_id; + + /** * Where we are in the inbound transaction history. */ uint64_t wire_off_in; @@ -128,6 +135,11 @@ struct WireAccount char *label_reserve_in_serial_id; /** + * Label under which we store our pp's kycauth_in_serial_id. + */ + char *label_kycauth_in_serial_id; + + /** * Label under which we store our wire_off_in. */ char *label_wire_off_in; @@ -185,16 +197,28 @@ static TALER_ARL_DEF_AB (total_bad_amount_in_minus); static TALER_ARL_DEF_AB (total_misattribution_in); /** - * Total amount credited to exchange accounts. + * Total amount credited to exchange accounts, as claimed by the exchange. + * Includes #total_kycauth_revenue, so that this is the credit side of the + * balance the exchange's bank account is expected to have. */ static TALER_ARL_DEF_AB (total_wire_in); /** - * Total amount credited to exchange accounts via KYCAUTH + * Total amount credited to exchange accounts via KYCAUTH, as reported by + * the bank. */ static TALER_ARL_DEF_AB (total_kycauth_in); /** + * Total amount credited to exchange accounts via KYCAUTH, as booked by the + * exchange in its `kycauths_in` table. KYCAUTH funds are not credited to + * any reserve and are never paid back, so they are revenue of the exchange + * operator, like wire fees and closing fees, and may be drained together + * with those. Counted towards #total_wire_in for that reason. + */ +static TALER_ARL_DEF_AB (total_kycauth_revenue); + +/** * Total wire credit fees charged to the exchange account. */ static TALER_ARL_DEF_AB (total_wire_credit_fees); @@ -325,6 +349,7 @@ do_shutdown (void *cls) wa_tail, wa); GNUNET_free (wa->label_reserve_in_serial_id); + GNUNET_free (wa->label_kycauth_in_serial_id); GNUNET_free (wa->label_wire_off_in); GNUNET_free (wa); } @@ -402,6 +427,7 @@ commit (enum GNUNET_DB_QueryStatus qs) TALER_ARL_adb, TALER_ARL_SET_AB (total_wire_in), TALER_ARL_SET_AB (total_kycauth_in), + TALER_ARL_SET_AB (total_kycauth_revenue), TALER_ARL_SET_AB (total_wire_credit_fees), TALER_ARL_SET_AB (total_bad_amount_in_plus), TALER_ARL_SET_AB (total_bad_amount_in_minus), @@ -413,6 +439,7 @@ commit (enum GNUNET_DB_QueryStatus qs) TALER_ARL_adb, TALER_ARL_SET_AB (total_wire_in), TALER_ARL_SET_AB (total_kycauth_in), + TALER_ARL_SET_AB (total_kycauth_revenue), TALER_ARL_SET_AB (total_wire_credit_fees), TALER_ARL_SET_AB (total_bad_amount_in_plus), TALER_ARL_SET_AB (total_bad_amount_in_minus), @@ -428,6 +455,8 @@ commit (enum GNUNET_DB_QueryStatus qs) TALER_ARL_adb, wa->label_reserve_in_serial_id, wa->last_reserve_in_serial_id, + wa->label_kycauth_in_serial_id, + wa->last_kycauth_in_serial_id, wa->label_wire_off_in, wa->wire_off_in, NULL); @@ -437,6 +466,8 @@ commit (enum GNUNET_DB_QueryStatus qs) TALER_ARL_adb, wa->label_reserve_in_serial_id, wa->last_reserve_in_serial_id, + wa->label_kycauth_in_serial_id, + wa->last_kycauth_in_serial_id, wa->label_wire_off_in, wa->wire_off_in, NULL); @@ -537,6 +568,55 @@ check_equality (const struct TALER_Amount *credit, /** + * Function called with details about incoming wire transfers that + * established an account key for KYC authentication, as claimed by the + * exchange DB. + * + * Such a transfer is not credited to a reserve and is never paid back, so + * the money is simply revenue of the exchange operator. We book it as + * such: it is part of the balance the exchange's bank account is expected + * to have, and the exchange may drain it together with its fee income. + * + * @param cls a `struct WireAccount` we are processing + * @param rowid unique serial ID for the entry in our DB + * @param account_pub public key the transfer associated with the account + * @param credit amount that was received + * @param sender_account_details payto://-URL of the sender's bank account + * @param wire_reference unique identifier for the wire transfer + * @param execution_date when did we receive the funds + * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop + */ +static enum GNUNET_GenericReturnValue +kycauth_in_cb (void *cls, + uint64_t rowid, + const union TALER_AccountPublicKeyP *account_pub, + const struct TALER_Amount *credit, + const struct TALER_FullPayto sender_account_details, + uint64_t wire_reference, + struct GNUNET_TIME_Timestamp execution_date) +{ + struct WireAccount *wa = cls; + + (void) account_pub; + (void) sender_account_details; + (void) wire_reference; + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Analyzing exchange KYCAUTH IN (%llu) at %s of %s\n", + (unsigned long long) rowid, + GNUNET_TIME_timestamp2s (execution_date), + TALER_amount2s (credit)); + wa->last_kycauth_in_serial_id = rowid + 1; + TALER_ARL_amount_add (&TALER_ARL_USE_AB (total_kycauth_revenue), + &TALER_ARL_USE_AB (total_kycauth_revenue), + credit); + TALER_ARL_amount_add (&TALER_ARL_USE_AB (total_wire_in), + &TALER_ARL_USE_AB (total_wire_in), + credit); + return GNUNET_OK; +} + + +/** * Function called with details about incoming wire transfers * as claimed by the exchange DB. * @@ -781,6 +861,11 @@ analyze_credit ( TALER_ARL_amount_add (&TALER_ARL_USE_AB (total_kycauth_in), &TALER_ARL_USE_AB (total_kycauth_in), &credit_details->amount); + /* the bank charges its credit fee on this transfer just like on + any other, so it must be part of the total either way */ + TALER_ARL_amount_add (&TALER_ARL_USE_AB (total_wire_credit_fees), + &TALER_ARL_USE_AB (total_wire_credit_fees), + &credit_details->credit_fee); return true; case TALER_BANK_CT_WAD: GNUNET_break (0); /* FIXME: Wad not yet supported */ @@ -1105,6 +1190,23 @@ process_credits (void *cls) } GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Analyzing exchange's KYCAUTH IN table for account `%s'\n", + wa->ai->section_name); + qs = TALER_EXCHANGEDB_iterate_kycauth_in_above_serial_id_by_account ( + TALER_ARL_edb, + wa->ai->section_name, + wa->last_kycauth_in_serial_id, + &kycauth_in_cb, + wa); + if (0 > qs) + { + GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); + global_ret = EXIT_FAILURE; + GNUNET_SCHEDULER_shutdown (); + return; + } + + GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Starting bank CREDIT history of account `%s'\n", wa->ai->section_name); GNUNET_log (GNUNET_ERROR_TYPE_INFO, @@ -1185,6 +1287,7 @@ begin_transaction (void) TALER_ARL_adb, TALER_ARL_GET_AB (total_wire_in), TALER_ARL_GET_AB (total_kycauth_in), + TALER_ARL_GET_AB (total_kycauth_revenue), TALER_ARL_GET_AB (total_wire_credit_fees), TALER_ARL_GET_AB (total_bad_amount_in_plus), TALER_ARL_GET_AB (total_bad_amount_in_minus), @@ -1206,10 +1309,19 @@ begin_transaction (void) NULL != wa; wa = wa->next) { + /* we are called again by rollback_and_reset() after a serialization + failure, so the labels may already have been computed */ + GNUNET_free (wa->label_reserve_in_serial_id); + GNUNET_free (wa->label_kycauth_in_serial_id); + GNUNET_free (wa->label_wire_off_in); GNUNET_asprintf (&wa->label_reserve_in_serial_id, "wire-%s-%s", wa->ai->section_name, "reserve_in_serial_id"); + GNUNET_asprintf (&wa->label_kycauth_in_serial_id, + "wire-%s-%s", + wa->ai->section_name, + "kycauth_in_serial_id"); GNUNET_asprintf (&wa->label_wire_off_in, "wire-%s-%s", wa->ai->section_name, @@ -1218,6 +1330,8 @@ begin_transaction (void) TALER_ARL_adb, wa->label_reserve_in_serial_id, &wa->last_reserve_in_serial_id, + wa->label_kycauth_in_serial_id, + &wa->last_kycauth_in_serial_id, wa->label_wire_off_in, &wa->wire_off_in, NULL); diff --git a/src/auditor/test-kyc.sh b/src/auditor/test-kyc.sh @@ -547,6 +547,11 @@ CLEARED_WIRED="TESTKUDOS:8.02" WITHHELD="TESTKUDOS:8.03" # Two KYCAUTH wire transfers of TESTKUDOS:0.1, one per merchant instance. KYCAUTH_IN="TESTKUDOS:0.2" +# Everything the exchange's bank account was credited with: TESTKUDOS:56 of +# reserve top-ups plus the two KYCAUTH transfers above. The KYCAUTH part is +# revenue rather than a liability, but it is money in the account all the +# same, so it belongs in the balance the account is expected to have. +WIRE_IN="TESTKUDOS:56.2" # Assert that all of the auditor's loss, delta and irregularity balances @@ -696,6 +701,20 @@ function check_kyc_state() { "$KYCAUTH_IN" \ "Wrong total for KYCAUTH wire transfers" + # KYCAUTH money is not credited to a reserve and never paid back, so + # the auditor books it as revenue of the exchange operator and counts + # it towards the balance the bank account is expected to have. + echo -n "Checking the KYCAUTH credits were booked as revenue... " + check_balance \ + "total_kycauth_revenue" \ + "$KYCAUTH_IN" \ + "Wrong KYCAUTH revenue" + echo -n "Checking the KYCAUTH credits reached the balance sheet... " + check_balance \ + "total_wire_in" \ + "$WIRE_IN" \ + "Wrong total credited to the exchange" + echo -n "Checking that only the cleared merchant was paid... " check_balance \ "total_wire_out" \ @@ -805,6 +824,15 @@ function test_2() { "total_kycauth_in" \ "TESTKUDOS:0.1" \ "Wrong total for KYCAUTH wire transfers" + # Only the bank's books were touched, so the exchange still claims the + # KYCAUTH revenue for a transfer the bank no longer reports as one. + # The auditor does not (yet) complain about that disagreement; see + # issue 1 in issues.txt. + echo -n "Checking that the exchange still claims the revenue... " + check_balance \ + "total_kycauth_revenue" \ + "$KYCAUTH_IN" \ + "Wrong KYCAUTH revenue" echo -n "Checking that the unbooked credit was flagged... " check_report_any \ "reserve-in-inconsistency" \ diff --git a/src/exchangedb/meson.build b/src/exchangedb/meson.build @@ -208,6 +208,7 @@ libtalerexchangedb = library( 'iterate_exchange_kycauth_transfers.c', 'iterate_kyc_accounts.c', 'iterate_kyc_attributes.c', + 'iterate_kycauth_in_above_serial_id_by_account.c', 'iterate_merge_amounts_for_kyc_check.c', 'get_purse_by_merge_pub.c', 'get_purse.c',