taler-rust

GNU Taler code in Rust. Largely core banking integrations.
Log | Files | Refs | Submodules | README | LICENSE

commit 4ed98dc57ed48957f9a44fba1b4f40e480d43b05
parent 097cfe22de25541e07e988cb6317b4cba6f1b679
Author: Antoine A <>
Date:   Fri, 21 Feb 2025 16:56:27 +0100

magnet-bank: bounce in worker

Diffstat:
MCargo.lock | 12++++++------
Mdatabase-versioning/magnet-bank-procedures.sql | 52+++++++++++++++++++++++++++++-----------------------
Mtaler-magnet-bank/src/db.rs | 165+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------
Mtaler-magnet-bank/src/worker.rs | 32++++++++++++++++----------------
4 files changed, 187 insertions(+), 74 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock @@ -304,9 +304,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.2.14" +version = "1.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c3d1b2e905a3a7b00a6141adb0e4c0bb941d11caf55349d863942a1cc44e3c9" +checksum = "c736e259eea577f443d5c86c304f9f4ae0295c43f3ba05c21f1d66b5f06001af" dependencies = [ "shlex", ] @@ -1464,9 +1464,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.25" +version = "0.4.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f" +checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e" [[package]] name = "matchit" @@ -2930,9 +2930,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.13.2" +version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c1f41ffb7cf259f1ecc2876861a17e7142e63ead296f671f81f6ae85903e0d6" +checksum = "93d59ca99a559661b96bf898d8fce28ed87935fd2bea9f05983c1464dd6c71b1" [[package]] name = "valuable" diff --git a/database-versioning/magnet-bank-procedures.sql b/database-versioning/magnet-bank-procedures.sql @@ -268,33 +268,38 @@ BEGIN END IF; END $$; -CREATE FUNCTION bounce( - IN in_tx_in_id INT8, +CREATE FUNCTION register_bounce_tx_in( + IN in_code INT8, IN in_amount taler_amount, + IN in_subject TEXT, + IN in_debit_account TEXT, + IN in_debit_name TEXT, + IN in_bounce_amount taler_amount, IN in_reason TEXT, IN in_timestamp INT8, + -- Success return OUT out_tx_row_id INT8, - OUT out_timestamp INT8 + OUT out_tx_timestamp INT8, + OUT out_tx_new BOOLEAN, + OUT out_bounce_row_id INT8, + OUT out_bounce_timestamp INT8, + OUT out_bounce_new BOOLEAN ) LANGUAGE plpgsql AS $$ -DECLARE -local_debit_account TEXT; -local_debit_name TEXT; -local_magnet_code INT8; BEGIN +-- Register incoming transaction idempotently +SELECT register_tx_in.out_tx_row_id, register_tx_in.out_timestamp, register_tx_in.out_new +INTO out_tx_row_id, out_tx_timestamp, out_tx_new +FROM register_tx_in(in_code, in_amount, in_subject, in_debit_account, in_debit_name, in_timestamp, NULL, NULL); + -- Check if already bounce SELECT initiated_id, created - INTO out_tx_row_id, out_timestamp + INTO out_bounce_row_id, out_bounce_timestamp FROM bounced JOIN initiated USING (initiated_id) - WHERE tx_in_id = in_tx_in_id; - + WHERE tx_in_id = out_tx_row_id; +out_bounce_new=NOT FOUND; -- Else initiate the bounce transaction -IF NOT FOUND THEN - -- Get incoming transaction bank ID and creditor - SELECT debit_account, debit_name, magnet_code - INTO local_debit_account, local_debit_name, local_magnet_code - FROM tx_in - WHERE tx_in_id = in_tx_in_id; +IF out_bounce_new THEN -- Initiate the bounce transaction INSERT INTO initiated ( amount, @@ -304,21 +309,22 @@ IF NOT FOUND THEN created ) VALUES ( in_amount, - 'bounce: ' || local_magnet_code, - local_debit_account, - local_debit_name, + 'bounce: ' || in_code, + in_debit_account, + in_debit_name, in_timestamp ) - RETURNING initiated_id, created INTO out_tx_row_id, out_timestamp; + RETURNING initiated_id, created INTO out_bounce_row_id, out_bounce_timestamp; -- Register the bounce INSERT INTO bounced ( tx_in_id, initiated_id, reason ) VALUES ( - in_tx_in_id, out_tx_row_id, + out_bounce_row_id, in_reason ); END IF; -END$$; -\ No newline at end of file +END $$; +COMMENT ON FUNCTION register_bounce_tx_in IS 'Register an incoming transaction and bounce it idempotently'; +\ No newline at end of file diff --git a/taler-magnet-bank/src/db.rs b/taler-magnet-bank/src/db.rs @@ -284,31 +284,43 @@ pub async fn make_transfer<'a>( #[derive(Debug, PartialEq, Eq)] pub struct BounceResult { - id: u64, - timestamp: Timestamp, + pub tx_id: u64, + pub tx_timestamp: Timestamp, + pub tx_new: bool, + pub bounce_id: u64, + pub bounce_timestamp: Timestamp, + pub bounce_new: bool, } -pub async fn bounce<'a>( - db: impl PgExecutor<'a>, - tx_in: u64, +pub async fn register_bounce_tx_in( + db: &mut PgConnection, + tx: &TxIn, amount: &Amount, reason: &str, timestamp: &Timestamp, ) -> sqlx::Result<BounceResult> { sqlx::query( " - SELECT out_tx_row_id, out_timestamp - FROM bounce($1, ($2, $3)::taler_amount, $4, $5) + SELECT out_tx_row_id, out_tx_timestamp, out_tx_new, out_bounce_row_id, out_bounce_timestamp, out_bounce_new + FROM register_bounce_tx_in($1, ($2, $3)::taler_amount, $4, $5, $6, ($7, $8)::taler_amount, $9, $10) ", ) - .bind(tx_in as i64) + .bind(tx.code as i64) + .bind_amount(&tx.amount) + .bind(&tx.subject) + .bind(tx.debtor.iban()) + .bind(&tx.debtor.name) .bind_amount(amount) .bind(reason) .bind_timestamp(timestamp) .try_map(|r: PgRow| { Ok(BounceResult { - id: r.try_get_u64(0)?, - timestamp: r.try_get_timestamp(1)?, + tx_id: r.try_get_u64(0)?, + tx_timestamp: r.try_get_timestamp(1)?, + tx_new: r.try_get(2)?, + bounce_id: r.try_get_u64(3)?, + bounce_timestamp:r.try_get_timestamp(4)?, + bounce_new: r.try_get(5)?, }) }) .fetch_one(db) @@ -617,7 +629,8 @@ mod test { constant::CURRENCY, db::{ self, AddIncomingResult, AddOutgoingResult, BounceResult, Initiated, TransferResult, - TxIn, TxOut, make_transfer, register_tx_in, register_tx_in_admin, register_tx_out, + TxIn, TxOut, make_transfer, register_bounce_tx_in, register_tx_in, + register_tx_in_admin, register_tx_out, }, magnet_payto, }; @@ -1067,7 +1080,7 @@ mod test { register_tx_in( &mut db, &TxIn { - code: 12, + code: 13, amount: amount.clone(), subject: "subject".to_owned(), debtor: payto.clone(), @@ -1080,39 +1093,133 @@ mod test { AddIncomingResult::Success { new: true, row_id: 1, - timestamp + timestamp: timestamp } ); + // Bounce assert_eq!( - db::bounce(&mut db, 1, &amount, "good reason", &timestamp) - .await - .expect("bounce"), + register_bounce_tx_in( + &mut db, + &TxIn { + code: 12, + amount: amount.clone(), + subject: "subject".to_owned(), + debtor: payto.clone(), + timestamp + }, + &amount, + "good reason", + &timestamp + ) + .await + .expect("bounce"), BounceResult { - id: 1, - timestamp: timestamp + tx_id: 2, + tx_timestamp: timestamp, + tx_new: true, + bounce_id: 1, + bounce_timestamp: timestamp, + bounce_new: true } ); // Idempotent assert_eq!( - db::bounce(&mut db, 1, &amount, "good reason", &timestamp) - .await - .expect("bounce"), + register_bounce_tx_in( + &mut db, + &TxIn { + code: 12, + amount: amount.clone(), + subject: "subject".to_owned(), + debtor: payto.clone(), + timestamp + }, + &amount, + "good reason", + &timestamp + ) + .await + .expect("bounce"), BounceResult { - id: 1, - timestamp: timestamp + tx_id: 2, + tx_timestamp: timestamp, + tx_new: false, + bounce_id: 1, + bounce_timestamp: timestamp, + bounce_new: false + } + ); + + // Bounce registered + assert_eq!( + register_bounce_tx_in( + &mut db, + &TxIn { + code: 13, + amount: amount.clone(), + subject: "subject".to_owned(), + debtor: payto.clone(), + timestamp + }, + &amount, + "good reason", + &timestamp + ) + .await + .expect("bounce"), + BounceResult { + tx_id: 1, + tx_timestamp: timestamp, + tx_new: false, + bounce_id: 2, + bounce_timestamp: timestamp, + bounce_new: true + } + ); + // Idempotent registered + assert_eq!( + register_bounce_tx_in( + &mut db, + &TxIn { + code: 13, + amount: amount.clone(), + subject: "subject".to_owned(), + debtor: payto.clone(), + timestamp + }, + &amount, + "good reason", + &timestamp + ) + .await + .expect("bounce"), + BounceResult { + tx_id: 1, + tx_timestamp: timestamp, + tx_new: false, + bounce_id: 2, + bounce_timestamp: timestamp, + bounce_new: false } ); // Batch assert_eq!( db::pending_batch(&mut db, &timestamp).await.unwrap(), - &[Initiated { - id: 1, - amount, - subject: "bounce: 12".to_owned(), - creditor: payto - }] + &[ + Initiated { + id: 1, + amount: amount.clone(), + subject: "bounce: 12".to_owned(), + creditor: payto.clone() + }, + Initiated { + id: 2, + amount, + subject: "bounce: 13".to_owned(), + creditor: payto + } + ] ); } diff --git a/taler-magnet-bank/src/worker.rs b/taler-magnet-bank/src/worker.rs @@ -68,23 +68,23 @@ impl Worker<'_> { let bounce = async |db: &mut PgConnection, reason: &str| -> Result<(), WorkerError> { - // TODO bounce in db - let res = db::register_tx_in(db, &tx_in, &None).await?; - match res { - AddIncomingResult::Success { new, .. } => { - if new { - info!("incoming {tx_in} bounced in TODO: {reason}"); - } else { - debug!( - "incoming {tx_in} already seen and bounced in TODO: {reason}" - ); - } - } - AddIncomingResult::ReservePubReuse => { - unreachable!("no reserve pub key") - } + let res = db::register_bounce_tx_in( + db, + &tx_in, + &tx_in.amount, + reason, + &Timestamp::now(), + ) + .await?; + + if res.tx_new { + info!("incoming {tx_in} bounced in {}: {reason}", res.bounce_id); + } else { + debug!( + "incoming {tx_in} already seen and bounced in {}: {reason}", + res.bounce_id + ); } - Ok(()) }; match parse_incoming_unstructured(&tx_in.subject) {