commit 4ed98dc57ed48957f9a44fba1b4f40e480d43b05
parent 097cfe22de25541e07e988cb6317b4cba6f1b679
Author: Antoine A <>
Date: Fri, 21 Feb 2025 16:56:27 +0100
magnet-bank: bounce in worker
Diffstat:
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", ×tamp)
- .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",
+ ×tamp
+ )
+ .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", ×tamp)
- .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",
+ ×tamp
+ )
+ .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",
+ ×tamp
+ )
+ .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",
+ ×tamp
+ )
+ .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, ×tamp).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) {