exchange

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

commit f9bf9cf471c6ad5abb8fcc46843ca7922e78c8c0
parent 1b172b75b61293a57c517b68cefcf79362b4eb34
Author: Christian Grothoff <christian@grothoff.org>
Date:   Thu, 19 Feb 2026 21:01:55 +0100

add support for #11118 to exchange backend

Diffstat:
Msrc/bank-lib/Makefile.am | 2+-
Msrc/bank-lib/bank_api_transfer.c | 36++++++++++++++++++++++++++++++++----
Msrc/bank-lib/taler-exchange-wire-gateway-client.c | 13++++++++++++-
Msrc/exchange/taler-exchange-aggregator.c | 14++++++++++++--
Msrc/exchange/taler-exchange-closer.c | 1+
Msrc/exchange/taler-exchange-drain.c | 1+
Msrc/exchange/taler-exchange-httpd_batch-deposit.c | 59+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/exchangedb/exchange_do_deposit.sql | 7+++++--
Msrc/exchangedb/perf_deposits_get_ready.c | 5++++-
Msrc/exchangedb/pg_do_deposit.c | 9+++++++--
Msrc/exchangedb/pg_get_ready_deposit.c | 9++++++++-
Msrc/exchangedb/pg_get_ready_deposit.h | 4+++-
Msrc/exchangedb/pg_store_wire_transfer_out.c | 9+++++++--
Msrc/exchangedb/pg_store_wire_transfer_out.h | 4+++-
Msrc/include/taler/taler_bank_service.h | 8+++++---
Msrc/include/taler/taler_exchangedb_plugin.h | 14++++++++++++--
Msrc/testing/testing_api_cmd_bank_transfer.c | 1+
17 files changed, 173 insertions(+), 23 deletions(-)

diff --git a/src/bank-lib/Makefile.am b/src/bank-lib/Makefile.am @@ -34,7 +34,7 @@ lib_LTLIBRARIES = \ libtalerfakebank.la libtalerbank_la_LDFLAGS = \ - -version-info 3:1:0 \ + -version-info 4:0:0 \ -no-undefined libtalerbank_la_SOURCES = \ bank_api_account_token.c \ diff --git a/src/bank-lib/bank_api_transfer.c b/src/bank-lib/bank_api_transfer.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2015--2023 Taler Systems SA + Copyright (C) 2015--2023, 2026 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 @@ -72,6 +72,7 @@ TALER_BANK_prepare_transfer ( const struct TALER_Amount *amount, const char *exchange_base_url, const struct TALER_WireTransferIdentifierRawP *wtid, + const char *extra_wire_transfer_subject, void **buf, size_t *buf_size) { @@ -79,18 +80,22 @@ TALER_BANK_prepare_transfer ( struct WirePackP *wp; size_t d_len = strlen (payto) + 1; size_t u_len = strlen (exchange_base_url) + 1; + size_t x_len = (NULL == extra_wire_transfer_subject) + ? 0 + : strlen (extra_wire_transfer_subject) + 1; char *end; if ( (d_len >= (size_t) GNUNET_MAX_MALLOC_CHECKED) || (u_len >= (size_t) GNUNET_MAX_MALLOC_CHECKED) || - (d_len + u_len + sizeof (*wp) >= GNUNET_MAX_MALLOC_CHECKED) ) + (x_len >= (size_t) GNUNET_MAX_MALLOC_CHECKED) || + (d_len + u_len + x_len + sizeof (*wp) >= GNUNET_MAX_MALLOC_CHECKED) ) { GNUNET_break (0); /* that's some long URL... */ *buf = NULL; *buf_size = 0; return; } - *buf_size = sizeof (*wp) + d_len + u_len; + *buf_size = sizeof (*wp) + d_len + u_len + x_len; wp = GNUNET_malloc (*buf_size); GNUNET_CRYPTO_hash_create_random (GNUNET_CRYPTO_QUALITY_NONCE, &wp->request_uid); @@ -106,6 +111,9 @@ TALER_BANK_prepare_transfer ( GNUNET_memcpy (end + d_len, exchange_base_url, u_len); + GNUNET_memcpy (end + d_len + u_len, + extra_wire_transfer_subject, + x_len); *buf = (char *) wp; } @@ -249,8 +257,10 @@ TALER_BANK_transfer ( const struct WirePackP *wp = buf; uint32_t d_len; uint32_t u_len; + uint32_t x_len; const char *destination_account_uri; const char *exchange_base_url; + const char *extra_metadata; struct TALER_Amount amount; if (sizeof (*wp) > buf_size) @@ -260,7 +270,7 @@ TALER_BANK_transfer ( } d_len = ntohl (wp->account_len); u_len = ntohl (wp->exchange_url_len); - if ( (sizeof (*wp) + d_len + u_len != buf_size) || + if ( (sizeof (*wp) + d_len + u_len > buf_size) || (d_len > buf_size) || (u_len > buf_size) || (d_len + u_len > buf_size) ) @@ -268,6 +278,7 @@ TALER_BANK_transfer ( GNUNET_break (0); return NULL; } + x_len = buf_size - (sizeof (*wp) + d_len + u_len); destination_account_uri = (const char *) &wp[1]; exchange_base_url = destination_account_uri + d_len; if ( ('\0' != destination_account_uri[d_len - 1]) || @@ -276,6 +287,20 @@ TALER_BANK_transfer ( GNUNET_break (0); return NULL; } + if (0 != x_len) + { + extra_metadata = destination_account_uri + d_len + u_len; + if ('\0' != extra_metadata[x_len - 1]) + { + GNUNET_break (0); + return NULL; + } + } + else + { + extra_metadata = NULL; + } + if (NULL == auth->wire_gateway_url) { GNUNET_break (0); @@ -302,6 +327,9 @@ TALER_BANK_transfer ( &amount), GNUNET_JSON_pack_string ("exchange_base_url", exchange_base_url), + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_string ("metadata", + extra_metadata)), GNUNET_JSON_pack_data_auto ("wtid", &wp->wtid), GNUNET_JSON_pack_string ("credit_account", diff --git a/src/bank-lib/taler-exchange-wire-gateway-client.c b/src/bank-lib/taler-exchange-wire-gateway-client.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2017-2023 Taler Systems SA + Copyright (C) 2017-2023, 2026 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 @@ -63,6 +63,11 @@ static char *subject; static char *account_section; /** + * -x command-line option. + */ +static char *metadata; + +/** * Starting row. */ static unsigned long long start_row = UINT64_MAX; @@ -464,6 +469,7 @@ execute_wire_transfer (void) &amount, "http://exchange.example.com/", &wtid, + metadata, &buf, &buf_size); eh = TALER_BANK_transfer (ctx, @@ -834,6 +840,11 @@ main (int argc, "ROW", "When asking the bank for transactions history, this option commands that all the results should have IDs settled after SW. If not given, then the 10 youngest transactions are returned.", &start_row), + GNUNET_GETOPT_option_string ('x', + "extra", + "METADATA", + "additional metadata text to pass to the core banking system for an outgoing wire transfer", + &metadata), GNUNET_GETOPT_OPTION_END }; enum GNUNET_GenericReturnValue ret; diff --git a/src/exchange/taler-exchange-aggregator.c b/src/exchange/taler-exchange-aggregator.c @@ -102,6 +102,12 @@ struct AggregationUnit const struct TALER_EXCHANGEDB_AccountInfo *wa; /** + * Additional metadata to include in the wire transfer subject, + * can be NULL. + */ + char *extra_wire_subject_metadata; + + /** * Shard this aggregation unit is part of. */ struct Shard *shard; @@ -263,6 +269,7 @@ static void cleanup_au (struct AggregationUnit *au) { GNUNET_assert (NULL != au); + GNUNET_free (au->extra_wire_subject_metadata); if (NULL != au->ru) { GNUNET_break (0); @@ -701,6 +708,7 @@ trigger_wire_transfer (struct AggregationUnit *au) &au->final_amount, exchange_base_url, &au->wtid, + au->extra_wire_subject_metadata, &buf, &buf_size); GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, @@ -727,7 +735,8 @@ trigger_wire_transfer (struct AggregationUnit *au) &au->wtid, &au->h_full_payto, au->wa->section_name, - &au->final_amount); + &au->final_amount, + au->extra_wire_subject_metadata); GNUNET_log (qs >= 0 ? GNUNET_ERROR_TYPE_DEBUG : GNUNET_ERROR_TYPE_WARNING, @@ -1159,7 +1168,8 @@ run_aggregation (void *cls) s->shard_start, s->shard_end, &au->merchant_pub, - &au->payto_uri); + &au->payto_uri, + &au->extra_wire_subject_metadata); switch (qs) { case GNUNET_DB_STATUS_HARD_ERROR: diff --git a/src/exchange/taler-exchange-closer.c b/src/exchange/taler-exchange-closer.c @@ -375,6 +375,7 @@ expired_reserve_cb (void *cls, &amount_without_fee, exchange_base_url, &wtid, + NULL, /* no extra meta data */ &buf, &buf_size); /* Commit our intention to execute the wire transfer! */ diff --git a/src/exchange/taler-exchange-drain.c b/src/exchange/taler-exchange-drain.c @@ -305,6 +305,7 @@ run_drain (void *cls) &amount, exchange_base_url, &wtid, + NULL, /* no extra metadata */ &buf, &buf_size); method = TALER_payto_get_method (payto_uri.full_payto); diff --git a/src/exchange/taler-exchange-httpd_batch-deposit.c b/src/exchange/taler-exchange-httpd_batch-deposit.c @@ -884,6 +884,49 @@ parse_coin (const struct BatchDepositContext *bdc, /** + * We allow [a-zA-Z0-9-.:] in extra_wire_subject_metadata. + * Test @a c for it. + * + * @param c character to test + * @return true if OK + */ +static inline bool +is_allowed_metachar (char c) +{ + return (c >= 'a' && c <= 'z') || + (c >= 'A' && c <= 'Z') || + (c >= '0' && c <= '9') || + c == '-' || + c == '.' || + c == ':'; +} + + +/** + * Check if @a src matches ``[a-zA-Z0-9-.:]{1, 40}`` + * + * @param src string to check + * @return true if it is an allowed metadata string. + */ +static bool +is_valid_meta_string (const char *src) +{ + unsigned int len = 0; + if (NULL == src) + return true; + + while (*src) + { + if (! is_allowed_metachar (*src++)) + return false; + if (++len > 40) + return false; + } + return true; +} + + +/** * Run processing phase that parses the request. * * @param[in,out] bdc request context @@ -921,6 +964,10 @@ bdc_phase_parse (struct BatchDepositContext *bdc, GNUNET_JSON_spec_timestamp ("timestamp", &bd->wallet_timestamp), GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_string ("extra_wire_subject_metadata", + &bd->extra_wire_subject_metadata), + &bd->no_wallet_data_hash), + GNUNET_JSON_spec_mark_optional ( GNUNET_JSON_spec_timestamp ("refund_deadline", &bd->refund_deadline), &no_refund_deadline), @@ -952,6 +999,18 @@ bdc_phase_parse (struct BatchDepositContext *bdc, return; } } + if (! is_valid_meta_string (bd->extra_wire_subject_metadata)) + { + GNUNET_break_op (0); + GNUNET_JSON_parse_free (spec); + finish_loop (bdc, + TALER_MHD_reply_with_error ( + bdc->rc->connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_GENERIC_PARAMETER_MALFORMED, + "extra_wire_subject_metadata")); + return; + } if (GNUNET_OK != TALER_merchant_contract_verify ( &bd->h_contract_terms, diff --git a/src/exchangedb/exchange_do_deposit.sql b/src/exchangedb/exchange_do_deposit.sql @@ -1,6 +1,6 @@ -- -- This file is part of TALER --- Copyright (C) 2014--2025 Taler Systems SA +-- Copyright (C) 2014--2026 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 @@ -39,6 +39,7 @@ CREATE FUNCTION exchange_do_deposit( IN ina_amount_with_fee taler_amount[], IN in_total_amount taler_amount, IN in_is_wallet BOOL, + IN in_extra_wire_subject_metadata TEXT, OUT out_exchange_timestamp INT8, OUT out_insufficient_balance_coin_index INT4, -- index of coin with bad balance, NULL if none OUT out_conflict BOOL @@ -109,6 +110,7 @@ INSERT INTO batch_deposits ,policy_details_serial_id ,policy_blocked ,total_amount + ,extra_wire_subject_metadata ) VALUES ( in_shard ,in_merchant_pub @@ -123,7 +125,8 @@ INSERT INTO batch_deposits ,in_wire_target_h_payto ,in_policy_details_serial_id ,in_policy_blocked - ,in_total_amount) + ,in_total_amount + ,in_extra_wire_subject_metadata) ON CONFLICT DO NOTHING -- for CONFLICT ON (merchant_pub, h_contract_terms) RETURNING batch_deposit_serial_id diff --git a/src/exchangedb/perf_deposits_get_ready.c b/src/exchangedb/perf_deposits_get_ready.c @@ -456,13 +456,15 @@ run (void *cls) struct TALER_MerchantPublicKeyP merchant_pub; struct TALER_FullPayto payto_uri; enum GNUNET_DB_QueryStatus qs; + char *metadata; time = GNUNET_TIME_absolute_get (); qs = plugin->get_ready_deposit (plugin->cls, 0, INT32_MAX, &merchant_pub, - &payto_uri); + &payto_uri, + &metadata); FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs); duration = GNUNET_TIME_absolute_get_duration (time); times = GNUNET_TIME_relative_add (times, @@ -473,6 +475,7 @@ run (void *cls) GNUNET_assert (sqrs + duration_sq >= sqrs); sqrs += duration_sq; GNUNET_free (payto_uri.full_payto); + GNUNET_free (metadata); } /* evaluation of performance */ diff --git a/src/exchangedb/pg_do_deposit.c b/src/exchangedb/pg_do_deposit.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2022-2024 Taler Systems SA + Copyright (C) 2022-2026 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 @@ -79,6 +79,9 @@ TEH_PG_do_deposit ( &total_amount), GNUNET_PQ_query_param_bool (TALER_payto_is_wallet ( bd->receiver_wire_account.full_payto)), + NULL == bd->extra_wire_subject_metadata + ? GNUNET_PQ_query_param_null () + : GNUNET_PQ_query_param_string (bd->extra_wire_subject_metadata), GNUNET_PQ_query_param_end }; bool no_time; @@ -131,7 +134,9 @@ TEH_PG_do_deposit ( ",out_insufficient_balance_coin_index AS insufficient_balance_coin_index" ",out_conflict AS conflicted" " FROM exchange_do_deposit" - " ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17,$18,$19,$20);"); + " ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10" + ",$11,$12,$13,$14,$15,$16,$17,$18,$19,$20" + ",$21);"); qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn, "call_deposit", params, diff --git a/src/exchangedb/pg_get_ready_deposit.c b/src/exchangedb/pg_get_ready_deposit.c @@ -31,7 +31,8 @@ TEH_PG_get_ready_deposit (void *cls, uint64_t start_shard_row, uint64_t end_shard_row, struct TALER_MerchantPublicKeyP *merchant_pub, - struct TALER_FullPayto *payto_uri) + struct TALER_FullPayto *payto_uri, + char **extra_wire_subject_metadata) { struct PostgresClosure *pg = cls; struct GNUNET_TIME_Absolute now @@ -47,15 +48,21 @@ TEH_PG_get_ready_deposit (void *cls, merchant_pub), GNUNET_PQ_result_spec_string ("payto_uri", &payto_uri->full_payto), + GNUNET_PQ_result_spec_allow_null ( + GNUNET_PQ_result_spec_string ("extra_wire_subject_metadata", + extra_wire_subject_metadata), + NULL), GNUNET_PQ_result_spec_end }; const char *query = "deposits_get_ready"; + *extra_wire_subject_metadata = NULL; PREPARE (pg, query, "SELECT" " wts.payto_uri" ",bdep.merchant_pub" + ",bdep.extra_wire_subject_metadata" " FROM batch_deposits bdep" " JOIN wire_targets wts" " USING (wire_target_h_payto)" diff --git a/src/exchangedb/pg_get_ready_deposit.h b/src/exchangedb/pg_get_ready_deposit.h @@ -36,6 +36,7 @@ * @param end_shard_row maximum shard row to select (inclusive) * @param[out] merchant_pub set to the public key of a merchant with a ready deposit * @param[out] payto_uri set to the account of the merchant, to be freed by caller + * @param[out] extra_wire_subject_metadata set to additional metadata to include in the wire subject, or NULL for none * @return transaction status code */ enum GNUNET_DB_QueryStatus @@ -43,6 +44,7 @@ TEH_PG_get_ready_deposit (void *cls, uint64_t start_shard_row, uint64_t end_shard_row, struct TALER_MerchantPublicKeyP *merchant_pub, - struct TALER_FullPayto *payto_uri); + struct TALER_FullPayto *payto_uri, + char **extra_wire_subject_metadata); #endif diff --git a/src/exchangedb/pg_store_wire_transfer_out.c b/src/exchangedb/pg_store_wire_transfer_out.c @@ -33,7 +33,8 @@ TEH_PG_store_wire_transfer_out ( const struct TALER_WireTransferIdentifierRawP *wtid, const struct TALER_FullPaytoHashP *h_payto, const char *exchange_account_section, - const struct TALER_Amount *amount) + const struct TALER_Amount *amount, + const char *extra_wire_subject_metadata) { struct PostgresClosure *pg = cls; struct GNUNET_PQ_QueryParam params[] = { @@ -43,6 +44,9 @@ TEH_PG_store_wire_transfer_out ( GNUNET_PQ_query_param_string (exchange_account_section), TALER_PQ_query_param_amount (pg->conn, amount), + NULL == extra_wire_subject_metadata + ? GNUNET_PQ_query_param_null () + : GNUNET_PQ_query_param_string (extra_wire_subject_metadata), GNUNET_PQ_query_param_end }; @@ -54,8 +58,9 @@ TEH_PG_store_wire_transfer_out ( ",wire_target_h_payto" ",exchange_account_section" ",amount" + ",extra_wire_subject_metadata" ") VALUES " - "($1, $2, $3, $4, $5);"); + "($1, $2, $3, $4, $5, $6);"); return GNUNET_PQ_eval_prepared_non_select (pg->conn, "insert_wire_out", params); diff --git a/src/exchangedb/pg_store_wire_transfer_out.h b/src/exchangedb/pg_store_wire_transfer_out.h @@ -34,6 +34,7 @@ * @param exchange_account_section configuration section of the exchange specifying the * exchange's bank account being used * @param amount amount that was transmitted + * @param extra_wire_subject_metadata additional meta data for the wire transfer subject, can be NULL * @return transaction status code */ enum GNUNET_DB_QueryStatus @@ -43,6 +44,7 @@ TEH_PG_store_wire_transfer_out ( const struct TALER_WireTransferIdentifierRawP *wtid, const struct TALER_FullPaytoHashP *h_payto, const char *exchange_account_section, - const struct TALER_Amount *amount); + const struct TALER_Amount *amount, + const char *extra_wire_subject_metadata); #endif diff --git a/src/include/taler/taler_bank_service.h b/src/include/taler/taler_bank_service.h @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2015-2024 Taler Systems SA + Copyright (C) 2015-2026 Taler Systems SA TALER is free software; you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software @@ -29,9 +29,9 @@ /** * Version of the Bank API, in hex. - * Thus 0.12.0-0 = 0x000C0000. + * Thus 1.5.0-0 = 0x01050000. */ -#define TALER_BANK_SERVICE_API_VERSION 0x000C0000 +#define TALER_BANK_SERVICE_API_VERSION 0x01050000 /** * Authentication method types. @@ -492,6 +492,7 @@ TALER_BANK_admin_add_kycauth_cancel ( * @param exchange_base_url base URL of this exchange (included in subject * to facilitate use of tracking API by merchant backend) * @param wtid wire transfer identifier to use + * @param extra_wire_transfer_subject additional meta data to include * @param[out] buf set to transaction data to persist, NULL on error * @param[out] buf_size set to number of bytes in @a buf, 0 on error */ @@ -501,6 +502,7 @@ TALER_BANK_prepare_transfer ( const struct TALER_Amount *amount, const char *exchange_base_url, const struct TALER_WireTransferIdentifierRawP *wtid, + const char *extra_wire_transfer_subject, void **buf, size_t *buf_size); diff --git a/src/include/taler/taler_exchangedb_plugin.h b/src/include/taler/taler_exchangedb_plugin.h @@ -1895,6 +1895,12 @@ struct TALER_EXCHANGEDB_BatchDeposit struct TALER_FullPayto receiver_wire_account; /** + * Optional extra information to include in the wire transfer + * subject. + */ + const char *extra_wire_subject_metadata; + + /** * Array about the coins that are being deposited. */ const struct TALER_EXCHANGEDB_CoinDepositInformation *cdis; @@ -4920,6 +4926,7 @@ struct TALER_EXCHANGEDB_Plugin * @param end_shard_row maximum shard row to select (inclusive) * @param[out] merchant_pub set to the public key of a merchant with a ready deposit * @param[out] payto_uri set to the account of the merchant, to be freed by caller + * @param[out] extra_wire_subject_metadata set to additional metadata to include in the wire subject, or NULL for none * @return transaction status code */ enum GNUNET_DB_QueryStatus @@ -4927,7 +4934,8 @@ struct TALER_EXCHANGEDB_Plugin uint64_t start_shard_row, uint64_t end_shard_row, struct TALER_MerchantPublicKeyP *merchant_pub, - struct TALER_FullPayto *payto_uri); + struct TALER_FullPayto *payto_uri, + char **extra_wire_subject_metadata); /** @@ -5521,6 +5529,7 @@ struct TALER_EXCHANGEDB_Plugin * @param amount amount that was transmitted * @param exchange_account_section configuration section of the exchange specifying the * exchange's bank account being used + * @param extra_wire_subject_metadata additional meta data for the wire transfer subject, can be NULL * @return transaction status code */ enum GNUNET_DB_QueryStatus @@ -5530,7 +5539,8 @@ struct TALER_EXCHANGEDB_Plugin const struct TALER_WireTransferIdentifierRawP *wtid, const struct TALER_FullPaytoHashP *h_payto, const char *exchange_account_section, - const struct TALER_Amount *amount); + const struct TALER_Amount *amount, + const char *extra_wire_subject_metadata); /** diff --git a/src/testing/testing_api_cmd_bank_transfer.c b/src/testing/testing_api_cmd_bank_transfer.c @@ -236,6 +236,7 @@ transfer_run (void *cls, &fts->amount, fts->exchange_base_url, &fts->wtid, + NULL, /* no additional meta data */ &buf, &buf_size); fts->is = is;