libeufin

Integration and sandbox testing for FinTech APIs and data formats
Log | Files | Refs | Submodules | README | LICENSE

commit 435ef8d5a318614508439de3432cc1f709c3e116
parent 3827cb37eb3518647aa4164dafdf8d612fea9166
Author: Antoine A <>
Date:   Fri,  2 Oct 2026 14:06:53 +0200

common: sync taler-rust and incoming history ordering fix

Diffstat:
Adatabase-versioning/libeufin-bank-0016.sql | 34++++++++++++++++++++++++++++++++++
Mdatabase-versioning/libeufin-bank-procedures.sql | 5+++--
Adatabase-versioning/libeufin-nexus-0015.sql | 34++++++++++++++++++++++++++++++++++
Mdatabase-versioning/libeufin-nexus-procedures.sql | 10++++++----
Mlibeufin-bank/src/db/exchange.rs | 8++++----
Mlibeufin-nexus/src/api.rs | 18++++++++++++++++--
Mlibeufin-nexus/src/db/exchange.rs | 8++++----
7 files changed, 101 insertions(+), 16 deletions(-)

diff --git a/database-versioning/libeufin-bank-0016.sql b/database-versioning/libeufin-bank-0016.sql @@ -0,0 +1,34 @@ +-- +-- This file is part of TALER +-- Copyright (C) 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 +-- 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/> + +BEGIN; + +SELECT _v.register_patch('libeufin-bank-0016', NULL, NULL); + +SET search_path TO libeufin_bank; + +-- Preserve existing incoming-history IDs and persisted consumer cursors. +ALTER TABLE taler_exchange_incoming + DROP CONSTRAINT taler_exchange_incoming_exchange_incoming_id_key; +UPDATE taler_exchange_incoming SET exchange_incoming_id = bank_transaction; +ALTER TABLE taler_exchange_incoming + ALTER COLUMN exchange_incoming_id SET GENERATED ALWAYS, + ADD PRIMARY KEY (exchange_incoming_id); +COMMENT ON COLUMN taler_exchange_incoming.exchange_incoming_id IS 'Incoming history publication ID'; + +SELECT setval(pg_get_serial_sequence('taler_exchange_incoming', 'exchange_incoming_id'), + (SELECT COALESCE(MAX(bank_transaction_id), 0) + 1 FROM bank_account_transactions), false); + +COMMIT; diff --git a/database-versioning/libeufin-bank-procedures.sql b/database-versioning/libeufin-bank-procedures.sql @@ -640,6 +640,7 @@ RETURNS void LANGUAGE plpgsql AS $$ DECLARE local_amount taler_amount; +local_taler_in_id INT8; BEGIN -- Register incoming transaction INSERT INTO taler_exchange_incoming ( @@ -654,7 +655,7 @@ INSERT INTO taler_exchange_incoming ( in_type, in_authorization_pub, in_authorization_sig -); +) RETURNING exchange_incoming_id INTO local_taler_in_id; -- Update stats IF in_type = 'reserve' THEN SELECT (amount).val, (amount).frac @@ -663,7 +664,7 @@ IF in_type = 'reserve' THEN CALL stats_register_payment('taler_in', NULL, local_amount, null); END IF; -- Notify new incoming transaction -PERFORM pg_notify('bank_incoming_tx', in_account_id || ' ' || in_tx_row_id); +PERFORM pg_notify('bank_incoming_tx', in_account_id || ' ' || local_taler_in_id); END $$; COMMENT ON FUNCTION register_incoming IS 'Register a bank transaction as a taler incoming transaction and announce it'; diff --git a/database-versioning/libeufin-nexus-0015.sql b/database-versioning/libeufin-nexus-0015.sql @@ -0,0 +1,34 @@ +-- +-- This file is part of TALER +-- Copyright (C) 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 +-- 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/> + +BEGIN; + +SELECT _v.register_patch('libeufin-nexus-0015', NULL, NULL); + +SET search_path TO libeufin_nexus; + +-- Preserve existing incoming-history IDs and persisted consumer cursors. +-- Allocate subsequent IDs when a transaction becomes eligible for history. +ALTER TABLE talerable_incoming_transactions ADD COLUMN taler_in_id INT8 GENERATED BY DEFAULT AS IDENTITY; +UPDATE talerable_incoming_transactions SET taler_in_id = incoming_transaction_id; +ALTER TABLE talerable_incoming_transactions + ALTER COLUMN taler_in_id SET GENERATED ALWAYS, + ADD PRIMARY KEY (taler_in_id); +COMMENT ON COLUMN talerable_incoming_transactions.taler_in_id IS 'Incoming history publication ID'; + +SELECT setval(pg_get_serial_sequence('talerable_incoming_transactions', 'taler_in_id'), + (SELECT COALESCE(MAX(incoming_transaction_id), 0) + 1 FROM incoming_transactions), false); + +COMMIT; diff --git a/database-versioning/libeufin-nexus-procedures.sql b/database-versioning/libeufin-nexus-procedures.sql @@ -272,6 +272,7 @@ local_subject TEXT; local_debit_payto TEXT; local_authorization_pub BYTEA; local_authorization_sig BYTEA; +local_taler_in_id INT8; BEGIN IF in_credit_fee = (0, 0)::taler_amount THEN in_credit_fee = NULL; @@ -385,8 +386,8 @@ IF in_type IS NOT NULL AND NOT out_talerable AND out_bounce_id IS NULL THEN ,in_metadata ,local_authorization_pub ,local_authorization_sig - ); - PERFORM pg_notify('nexus_incoming_tx', out_tx_id::text); + ) RETURNING taler_in_id INTO local_taler_in_id; + PERFORM pg_notify('nexus_incoming_tx', local_taler_in_id::text); out_talerable=TRUE; END IF; END IF; @@ -673,6 +674,7 @@ CREATE FUNCTION register_prepared_transfers ( LANGUAGE plpgsql AS $$ DECLARE talerable_tx INT8; + local_taler_in_id INT8; idempotent BOOLEAN; BEGIN @@ -720,9 +722,9 @@ IF in_recurrent THEN INSERT INTO talerable_incoming_transactions (incoming_transaction_id, type, metadata, authorization_pub, authorization_sig) SELECT moved_tx.incoming_transaction_id, in_type, in_account_pub, in_authorization_pub, in_authorization_sig FROM moved_tx - RETURNING incoming_transaction_id INTO talerable_tx; + RETURNING incoming_transaction_id, taler_in_id INTO talerable_tx, local_taler_in_id; IF talerable_tx IS NOT NULL THEN - PERFORM pg_notify('nexus_incoming_tx', talerable_tx::text); + PERFORM pg_notify('nexus_incoming_tx', local_taler_in_id::text); END IF; ELSE -- Bounce all pending diff --git a/libeufin-bank/src/db/exchange.rs b/libeufin-bank/src/db/exchange.rs @@ -297,13 +297,13 @@ pub async fn incoming_history( ) -> sqlx::Result<Vec<IncomingBankTransaction>> { history( db, - "bank_transaction_id", + "exchange_incoming_id", params, || channel.subscribe(exchange_id), || { let mut query = QueryBuilder::new( "SELECT - bank_transaction_id + exchange_incoming_id ,transaction_date ,amount ,debtor_payto @@ -323,7 +323,7 @@ pub async fn incoming_history( |r| { Ok(match r.try_get_parse("type")? { IncomingType::reserve => IncomingBankTransaction::Reserve { - row_id: r.try_get_u64("bank_transaction_id")?, + row_id: r.try_get_u64("exchange_incoming_id")?, date: r.try_get_timestamp("transaction_date")?.into(), amount: r.try_get_amount("amount", currency)?, credit_fee: None, @@ -333,7 +333,7 @@ pub async fn incoming_history( authorization_sig: r.try_get("authorization_sig")?, }, IncomingType::kyc => IncomingBankTransaction::Kyc { - row_id: r.try_get_u64("bank_transaction_id")?, + row_id: r.try_get_u64("exchange_incoming_id")?, date: r.try_get_timestamp("transaction_date")?.into(), amount: r.try_get_amount("amount", currency)?, credit_fee: None, diff --git a/libeufin-nexus/src/api.rs b/libeufin-nexus/src/api.rs @@ -432,8 +432,8 @@ pub mod test { use taler_test_utils::{ Router, routine::{ - admin_add_incoming_routine, out_history_routine, registration_routine, revenue_routine, - transfer_routine, + admin_add_incoming_routine, in_history_routine, out_history_routine, + registration_routine, revenue_routine, transfer_routine, }, server::TestServer as _, tasks, @@ -508,6 +508,20 @@ pub mod test { } #[db_test] + async fn incoming_history(db: PgPool) { + let server = api_setup(&db).await; + in_history_routine( + &server.prefix("/taler-wire-gateway"), + &server.prefix("/taler-prepared-transfer"), + &CLIENT.as_uri(), + &EXCHANGE.as_uri(), + tasks!(), + tasks!(), + ) + .await; + } + + #[db_test] async fn admin_add_incoming(db: PgPool) { let server = api_setup(&db).await; admin_add_incoming_routine( diff --git a/libeufin-nexus/src/db/exchange.rs b/libeufin-nexus/src/db/exchange.rs @@ -92,7 +92,7 @@ pub async fn incoming_history( ) -> sqlx::Result<Vec<IncomingBankTransaction>> { history( db, - "incoming_transaction_id", + "taler_in_id", params, listen, || { @@ -100,7 +100,7 @@ pub async fn incoming_history( QueryBuilder::new( " SELECT - incoming_transaction_id + taler_in_id ,execution_time ,amount ,credit_fee @@ -121,7 +121,7 @@ pub async fn incoming_history( .filter(|it| !it.is_zero()); Ok(match r.try_get_parse("type")? { IncomingType::reserve => IncomingBankTransaction::Reserve { - row_id: r.try_get_u64("incoming_transaction_id")?, + row_id: r.try_get_u64("taler_in_id")?, amount: r.try_get_amount("amount", currency)?, credit_fee, debit_account: r.try_get_payto("debit_payto")?, @@ -131,7 +131,7 @@ pub async fn incoming_history( authorization_sig: r.try_get("authorization_sig")?, }, IncomingType::kyc => IncomingBankTransaction::Kyc { - row_id: r.try_get_u64("incoming_transaction_id")?, + row_id: r.try_get_u64("taler_in_id")?, amount: r.try_get_amount("amount", currency)?, credit_fee, debit_account: r.try_get_payto("debit_payto")?,