commit 57866faede9d014a8410ae0844ff91a9e0116c26
parent d0e0fcaca528979231e254976a88bbc86f45cf3a
Author: Christian Grothoff <christian@grothoff.org>
Date: Thu, 13 Aug 2026 00:41:00 +0200
implement protocol v35: #10713
Diffstat:
12 files changed, 116 insertions(+), 46 deletions(-)
diff --git a/src/backend/taler-merchant-httpd_get-config.c b/src/backend/taler-merchant-httpd_get-config.c
@@ -44,7 +44,7 @@
* #MERCHANT_PROTOCOL_CURRENT and #MERCHANT_PROTOCOL_AGE in
* merchant_api_get_config.c!
*/
-#define MERCHANT_PROTOCOL_VERSION "34:0:22"
+#define MERCHANT_PROTOCOL_VERSION "35:0:23"
/**
diff --git a/src/backend/taler-merchant-httpd_get-private-transfers.c b/src/backend/taler-merchant-httpd_get-private-transfers.c
@@ -35,6 +35,8 @@
* @param credit_amount amount expected to be wired to the merchant (minus fees), NULL if unknown
* @param wtid wire transfer identifier
* @param payto_uri target account that received the wire transfer
+ * @param exchange_payto_uri account of the exchange that was debited,
+ * `full_payto` NULL if the exchange did not tell us
* @param exchange_url base URL of the exchange that made the wire transfer
* @param transfer_serial_id serial number identifying the transfer in the backend
* @param expected_transfer_serial_id serial number identifying the expected transfer in the backend, 0 if not @a expected
@@ -47,6 +49,7 @@ transfer_cb (void *cls,
const struct TALER_Amount *credit_amount,
const struct TALER_WireTransferIdentifierRawP *wtid,
struct TALER_FullPayto payto_uri,
+ struct TALER_FullPayto exchange_payto_uri,
const char *exchange_url,
uint64_t transfer_serial_id,
uint64_t expected_transfer_serial_id,
@@ -63,6 +66,9 @@ transfer_cb (void *cls,
wtid),
TALER_JSON_pack_full_payto ("payto_uri",
payto_uri),
+ GNUNET_JSON_pack_allow_null (
+ TALER_JSON_pack_full_payto ("exchange_payto_uri",
+ exchange_payto_uri)),
GNUNET_JSON_pack_string ("exchange_url",
exchange_url),
GNUNET_JSON_pack_uint64 ("transfer_serial_id",
diff --git a/src/backend/taler-merchant-reconciliation.c b/src/backend/taler-merchant-reconciliation.c
@@ -982,7 +982,8 @@ wire_transfer_cb (struct Inquiry *w,
&td->total_amount,
&td->wire_fee,
&td->exchange_pub,
- &td->exchange_sig);
+ &td->exchange_sig,
+ td->exchange_payto_uri);
if (qs < 0)
{
GNUNET_break (0);
diff --git a/src/backenddb/iterate_transfers.c b/src/backenddb/iterate_transfers.c
@@ -79,6 +79,9 @@ lookup_transfers_cb (void *cls,
struct TALER_Amount credit_amount;
struct TALER_WireTransferIdentifierRawP wtid;
struct TALER_FullPayto payto_uri;
+ struct TALER_FullPayto exchange_payto_uri = {
+ .full_payto = NULL
+ };
char *exchange_url;
uint64_t transfer_serial_id;
uint64_t expected_transfer_serial_id = 0;
@@ -93,6 +96,10 @@ lookup_transfers_cb (void *cls,
&payto_uri.full_payto),
GNUNET_PQ_result_spec_string ("exchange_url",
&exchange_url),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_string ("exchange_payto_uri",
+ &exchange_payto_uri.full_payto),
+ NULL),
GNUNET_PQ_result_spec_uint64 ("credit_serial",
&transfer_serial_id),
GNUNET_PQ_result_spec_allow_null (
@@ -119,6 +126,7 @@ lookup_transfers_cb (void *cls,
&credit_amount,
&wtid,
payto_uri,
+ exchange_payto_uri,
exchange_url,
transfer_serial_id,
expected_transfer_serial_id,
@@ -182,15 +190,24 @@ TALER_MERCHANTDB_iterate_transfers (
",mt.credit_serial"
",mt.execution_time"
",mt.expected"
- ",met.expected_credit_serial"
+ /* 'expected' means the exchange's report and our
+ bank statement agree on the amount; keep gating
+ the serial on it, but not the account, which we
+ know as soon as the exchange reported it. */
+ ",CASE WHEN mt.expected"
+ " THEN met.expected_credit_serial"
+ " ELSE NULL END AS expected_credit_serial"
+ ",met.exchange_payto_uri"
" FROM merchant_transfers mt"
" JOIN merchant_accounts mac"
" USING (account_serial)"
+ /* (wtid, exchange_url, account_serial) is UNIQUE on
+ merchant_expected_transfers, so this matches at
+ most one row and cannot fan out the result. */
" LEFT JOIN merchant_expected_transfers met"
" ON mt.wtid = met.wtid"
" AND mt.account_serial = met.account_serial"
" AND mt.exchange_url = met.exchange_url"
- " AND mt.expected"
" WHERE ( $6 OR "
" (mt.execution_time < $1 AND"
" mt.execution_time >= $2) )"
@@ -214,15 +231,24 @@ TALER_MERCHANTDB_iterate_transfers (
",mt.credit_serial"
",mt.execution_time"
",mt.expected"
- ",met.expected_credit_serial"
+ /* 'expected' means the exchange's report and our
+ bank statement agree on the amount; keep gating
+ the serial on it, but not the account, which we
+ know as soon as the exchange reported it. */
+ ",CASE WHEN mt.expected"
+ " THEN met.expected_credit_serial"
+ " ELSE NULL END AS expected_credit_serial"
+ ",met.exchange_payto_uri"
" FROM merchant_transfers mt"
" JOIN merchant_accounts mac"
" USING (account_serial)"
+ /* (wtid, exchange_url, account_serial) is UNIQUE on
+ merchant_expected_transfers, so this matches at
+ most one row and cannot fan out the result. */
" LEFT JOIN merchant_expected_transfers met"
" ON mt.wtid = met.wtid"
" AND mt.account_serial = met.account_serial"
" AND mt.exchange_url = met.exchange_url"
- " AND mt.expected"
" WHERE ( $6 OR "
" (mt.execution_time < $1 AND"
" mt.execution_time >= $2) )"
diff --git a/src/backenddb/sql-schema/meson.build b/src/backenddb/sql-schema/meson.build
@@ -23,13 +23,15 @@ sql_global_procedures = [
'../gc.sql',
]
-gprocedures_sql = custom_target('sql_global_procedures',
- input: sql_global_procedures,
- output: 'global_procedures.sql',
- capture: true,
- command: ['./gen-procedures.sh', 'merchant', '@INPUT@'],
- install: true,
- install_dir: sqldir)
+gprocedures_sql = custom_target(
+ 'sql_global_procedures',
+ input: sql_global_procedures,
+ output: 'global_procedures.sql',
+ capture: true,
+ command: ['./gen-procedures.sh', 'merchant', '@INPUT@'],
+ install: true,
+ install_dir: sqldir,
+)
sql_instance_procedures = [
'../pg_statistics_helpers.sql',
@@ -70,13 +72,15 @@ sql_instance_procedures = [
'../pg_update_inventory_locked.sql',
]
-iprocedures_sql = custom_target('instance_procedures',
- input: sql_instance_procedures,
- output: 'instance_procedures.sql',
- capture: true,
- command: ['./gen-procedures.sh', 'merchant_instances', '@INPUT@'],
- install: true,
- install_dir: sqldir)
+iprocedures_sql = custom_target(
+ 'instance_procedures',
+ input: sql_instance_procedures,
+ output: 'instance_procedures.sql',
+ capture: true,
+ command: ['./gen-procedures.sh', 'merchant_instances', '@INPUT@'],
+ install: true,
+ install_dir: sqldir,
+)
# gen-procedures.sh wraps each concatenation above in a single
# BEGIN ... COMMIT; make sure no input file closes that transaction early.
@@ -134,6 +138,7 @@ generated_sql = [
['merchant-0041.sql'],
['merchant-0042.sql'],
['merchant-0043.sql'],
+ ['merchant-0044.sql'],
]
foreach g : generated_sql
@@ -150,15 +155,17 @@ endforeach
custom_target(
- 'gen-merchantdb-merchant_0036.sql',
- input: ['merchant-0036.sql.in',
- 'merchant-0036-init.sql.fragment',
- 'merchant-0036-copy.sql.fragment',
- 'merchant-0036-drop.sql.fragment',
- 'merchant-0036-setval.sql.fragment'],
- output: ['merchant-0036.sql'],
- capture: true,
- command: ['./preprocess-sql.sh', '@INPUT@'],
- install: true,
- install_dir: sqldir,
- )
+ 'gen-merchantdb-merchant_0036.sql',
+ input: [
+ 'merchant-0036.sql.in',
+ 'merchant-0036-init.sql.fragment',
+ 'merchant-0036-copy.sql.fragment',
+ 'merchant-0036-drop.sql.fragment',
+ 'merchant-0036-setval.sql.fragment',
+ ],
+ output: ['merchant-0036.sql'],
+ capture: true,
+ command: ['./preprocess-sql.sh', '@INPUT@'],
+ install: true,
+ install_dir: sqldir,
+)
diff --git a/src/backenddb/test_merchantdb.c b/src/backenddb/test_merchantdb.c
@@ -6017,6 +6017,8 @@ struct TestLookupTransfers_Closure
* @param credit_amount how much was wired to the merchant (minus fees)
* @param wtid wire transfer identifier
* @param payto_uri target account that received the wire transfer
+ * @param exchange_payto_uri account of the exchange that was debited,
+ * `full_payto` NULL if unknown; unused here
* @param exchange_url base URL of the exchange that made the wire transfer
* @param transfer_serial_id serial number identifying the transfer in the backend
* @param expected_transfer_serial_id serial number identifying the expected transfer in the backend, 0 if not @a expected
@@ -6029,6 +6031,7 @@ lookup_transfers_cb (void *cls,
const struct TALER_Amount *credit_amount,
const struct TALER_WireTransferIdentifierRawP *wtid,
struct TALER_FullPayto payto_uri,
+ struct TALER_FullPayto exchange_payto_uri,
const char *exchange_url,
uint64_t transfer_serial_id,
uint64_t expected_transfer_serial_id,
diff --git a/src/backenddb/update_to_expected_transfer_finalized.c b/src/backenddb/update_to_expected_transfer_finalized.c
@@ -33,7 +33,8 @@ TALER_MERCHANTDB_update_to_expected_transfer_finalized (
const struct TALER_Amount *total_amount,
const struct TALER_Amount *wire_fee,
const struct TALER_ExchangePublicKeyP *exchange_pub,
- const struct TALER_ExchangeSignatureP *exchange_sig)
+ const struct TALER_ExchangeSignatureP *exchange_sig,
+ const struct TALER_FullPayto exchange_payto_uri)
{
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_auto_from_type (wtid),
@@ -45,6 +46,9 @@ TALER_MERCHANTDB_update_to_expected_transfer_finalized (
GNUNET_PQ_query_param_auto_from_type (h_details),
GNUNET_PQ_query_param_auto_from_type (exchange_pub),
GNUNET_PQ_query_param_auto_from_type (exchange_sig),
+ NULL == exchange_payto_uri.full_payto
+ ? GNUNET_PQ_query_param_null ()
+ : GNUNET_PQ_query_param_string (exchange_payto_uri.full_payto),
GNUNET_PQ_query_param_end
};
@@ -66,6 +70,11 @@ TALER_MERCHANTDB_update_to_expected_transfer_finalized (
",h_details=$5"
",signkey_serial=subquery.signkey_serial"
",exchange_sig=$7"
+ /* COALESCE: a retry against an exchange that stopped
+ reporting the account (downgrade, or a pre-v39
+ exchange) must not erase what we already learned. */
+ ",exchange_payto_uri="
+ "COALESCE(CAST($8 AS TEXT),exchange_payto_uri)"
" FROM subquery"
" WHERE wtid=$1"
" AND exchange_url=$2");
diff --git a/src/include/merchant-database/iterate_transfers.h b/src/include/merchant-database/iterate_transfers.h
@@ -33,6 +33,8 @@
* @param expected_credit_amount how we expect to see wired to the merchant (minus fees), NULL if unknown
* @param wtid wire transfer identifier
* @param payto_uri target account that received the wire transfer
+ * @param exchange_payto_uri account of the exchange that was debited;
+ * `full_payto` is NULL if the exchange did not tell us
* @param exchange_url base URL of the exchange that made the wire transfer
* @param transfer_serial_id serial number identifying the transfer in the backend
* @param expected_transfer_serial_id serial number identifying the expected transfer in the backend, 0 if not @a expected
@@ -46,6 +48,7 @@ typedef void
const struct TALER_Amount *expected_credit_amount,
const struct TALER_WireTransferIdentifierRawP *wtid,
struct TALER_FullPayto payto_uri,
+ struct TALER_FullPayto exchange_payto_uri,
const char *exchange_url,
uint64_t transfer_serial_id,
uint64_t expected_transfer_serial_id,
diff --git a/src/include/merchant-database/update_to_expected_transfer_finalized.h b/src/include/merchant-database/update_to_expected_transfer_finalized.h
@@ -38,20 +38,21 @@ struct TALER_MERCHANTDB_PostgresContext;
* @param wire_fee wire fee charged by the exchange
* @param exchange_pub key used to make @e exchange_sig
* @param exchange_sig signature of the exchange over reconciliation data
+ * @param exchange_payto_uri bank account of the exchange that was debited;
+ * `full_payto` is NULL if the exchange did not tell us
* @return database transaction status
*/
enum GNUNET_DB_QueryStatus
-TALER_MERCHANTDB_update_to_expected_transfer_finalized (struct TALER_MERCHANTDB_PostgresContext *pg,
- const char *exchange_url,
- const struct
- TALER_WireTransferIdentifierRawP *wtid,
- const struct GNUNET_HashCode *h_details,
- const struct TALER_Amount *total_amount,
- const struct TALER_Amount *wire_fee,
- const struct TALER_ExchangePublicKeyP *
- exchange_pub,
- const struct TALER_ExchangeSignatureP *
- exchange_sig);
+TALER_MERCHANTDB_update_to_expected_transfer_finalized (
+ struct TALER_MERCHANTDB_PostgresContext *pg,
+ const char *exchange_url,
+ const struct TALER_WireTransferIdentifierRawP *wtid,
+ const struct GNUNET_HashCode *h_details,
+ const struct TALER_Amount *total_amount,
+ const struct TALER_Amount *wire_fee,
+ const struct TALER_ExchangePublicKeyP *exchange_pub,
+ const struct TALER_ExchangeSignatureP *exchange_sig,
+ const struct TALER_FullPayto exchange_payto_uri);
#endif
diff --git a/src/include/taler/merchant/get-private-transfers.h b/src/include/taler/merchant/get-private-transfers.h
@@ -309,6 +309,14 @@ struct TALER_MERCHANT_GetPrivateTransfersTransferData
struct TALER_FullPayto payto_uri;
/**
+ * URI of the exchange's own bank account that was debited.
+ * `full_payto` is NULL if the backend does not know it: the
+ * exchange only started reporting it with its protocol v39,
+ * and even then not for older transfers.
+ */
+ struct TALER_FullPayto exchange_payto_uri;
+
+ /**
* URL of the exchange that made the transfer.
*/
const char *exchange_url;
diff --git a/src/lib/merchant_api_get-config.c b/src/lib/merchant_api_get-config.c
@@ -34,12 +34,12 @@
* Which version of the Taler protocol is implemented
* by this library? Used to determine compatibility.
*/
-#define MERCHANT_PROTOCOL_CURRENT 34
+#define MERCHANT_PROTOCOL_CURRENT 35
/**
* How many configs are we backwards-compatible with?
*/
-#define MERCHANT_PROTOCOL_AGE 10
+#define MERCHANT_PROTOCOL_AGE 11
/**
* How many exchanges do we allow at most per merchant?
diff --git a/src/lib/merchant_api_get-private-transfers.c b/src/lib/merchant_api_get-private-transfers.c
@@ -148,6 +148,12 @@ parse_transfers (
&td->wtid),
TALER_JSON_spec_full_payto_uri ("payto_uri",
&td->payto_uri),
+ /* Optional: unknown for transfers reported by an exchange
+ older than protocol v39. */
+ GNUNET_JSON_spec_mark_optional (
+ TALER_JSON_spec_full_payto_uri ("exchange_payto_uri",
+ &td->exchange_payto_uri),
+ NULL),
TALER_JSON_spec_web_url ("exchange_url",
&td->exchange_url),
GNUNET_JSON_spec_uint64 ("transfer_serial_id",