taler-rust

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

commit e66401b06e243ae00d152ac6715efe81ec398d01
parent e43225e2e68bc9b5d32755e77165d1ec2502bbc8
Author: Antoine A <>
Date:   Sat, 22 Feb 2025 12:48:03 +0100

magnet-bank: differentiate tx registration and valuation

Diffstat:
MCargo.lock | 12++++++------
Mcommon/taler-common/src/types/timestamp.rs | 13++++++++++++-
Mdatabase-versioning/magnet-bank-0001.sql | 8+++++---
Mdatabase-versioning/magnet-bank-procedures.sql | 79+++++++++++++++++++++++++++++++++++++++++--------------------------------------
Mtaler-magnet-bank/src/adapter.rs | 16++++++++--------
Mtaler-magnet-bank/src/db.rs | 223+++++++++++++++++++++++++++++++++++++++----------------------------------------
Mtaler-magnet-bank/src/worker.rs | 17++++++++++++-----
Mtaler-magnet-bank/tests/api.rs | 4+++-
8 files changed, 196 insertions(+), 176 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock @@ -1508,9 +1508,9 @@ dependencies = [ [[package]] name = "miniz_oxide" -version = "0.8.4" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3b1c9bd4fe1f0f8b387f6eb9eb3b4a1aa26185e5750efb9140301703f62cd1b" +checksum = "8e3e04debbb59698c15bacbb6d93584a8c0ca9cc3213cb423d31f760d8843ce5" dependencies = [ "adler2", ] @@ -1895,9 +1895,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.8" +version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834" +checksum = "82b568323e98e49e2a0899dcee453dd679fae22d69adf9b11dd508d1549b7e2f" dependencies = [ "bitflags", ] @@ -1996,9 +1996,9 @@ dependencies = [ [[package]] name = "ring" -version = "0.17.9" +version = "0.17.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e75ec5e92c4d8aede845126adc388046234541629e76029599ed35a003c7ed24" +checksum = "d34b5020fcdea098ef7d95e9f89ec15952123a4a039badd09fabebe9e963e839" dependencies = [ "cc", "cfg-if", diff --git a/common/taler-common/src/types/timestamp.rs b/common/taler-common/src/types/timestamp.rs @@ -14,7 +14,7 @@ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ -use std::fmt::Display; +use std::{fmt::Display, ops::Add}; use jiff::{civil::Time, tz::TimeZone}; use serde::{Deserialize, Deserializer, Serialize, Serializer, de::Error, ser::SerializeStruct}; // codespell:ignore @@ -130,3 +130,14 @@ impl From<jiff::civil::Date> for Timestamp { .into() } } + +impl Add<jiff::Span> for Timestamp { + type Output = Self; + + fn add(self, rhs: jiff::Span) -> Self::Output { + match self { + Timestamp::Never => Self::never(), + Timestamp::Time(timestamp) => Self::Time(timestamp + rhs), + } + } +} diff --git a/database-versioning/magnet-bank-0001.sql b/database-versioning/magnet-bank-0001.sql @@ -28,7 +28,8 @@ CREATE TABLE tx_in( subject TEXT NOT NULL, debit_account TEXT NOT NULL, debit_name TEXT NOT NULL, - created INT8 NOT NULL + valued_at INT8 NOT NULL, + registered_at INT8 NOT NULL ); COMMENT ON TABLE tx_in IS 'Incoming transactions'; @@ -39,7 +40,8 @@ CREATE TABLE tx_out( subject TEXT NOT NULL, credit_account TEXT NOT NULL, credit_name TEXT NOT NULL, - created INT8 NOT NULL + valued_at INT8 NOT NULL, + registered_at INT8 NOT NULL ); COMMENT ON TABLE tx_in IS 'Outgoing transactions'; @@ -91,7 +93,7 @@ CREATE TABLE initiated( last_submitted INT8, submission_counter INT2 NOT NULL DEFAULT 0, tx_out_id INT8 UNIQUE REFERENCES tx_out(tx_out_id) ON DELETE CASCADE, - created INT8 NOT NULL + initiated_at INT8 NOT NULL ); COMMENT ON TABLE tx_in IS 'Initiated outgoing transactions'; diff --git a/database-versioning/magnet-bank-procedures.sql b/database-versioning/magnet-bank-procedures.sql @@ -44,21 +44,22 @@ CREATE FUNCTION register_tx_in( IN in_subject TEXT, IN in_debit_account TEXT, IN in_debit_name TEXT, - IN in_timestamp INT8, + IN in_valued_at INT8, IN in_type incoming_type, IN in_metadata BYTEA, + IN in_now INT8, -- Error status OUT out_reserve_pub_reuse BOOLEAN, -- Success return OUT out_tx_row_id INT8, - OUT out_timestamp INT8, + OUT out_valued_at INT8, OUT out_new BOOLEAN ) LANGUAGE plpgsql AS $$ BEGIN -- Check for idempotence -SELECT tx_in_id, created -INTO out_tx_row_id, out_timestamp +SELECT tx_in_id, valued_at +INTO out_tx_row_id, out_valued_at FROM tx_in WHERE (in_code IS NOT NULL AND magnet_code = in_code) -- Magnet transaction OR (in_code IS NULL AND amount = in_amount AND debit_account = in_debit_account AND subject = in_subject); -- Admin transaction @@ -76,23 +77,25 @@ IF out_reserve_pub_reuse THEN END IF; -- Insert new incoming transaction +out_valued_at = in_valued_at; INSERT INTO tx_in ( magnet_code, amount, subject, debit_account, debit_name, - created + valued_at, + registered_at ) VALUES ( in_code, in_amount, in_subject, in_debit_account, in_debit_name, - in_timestamp + in_valued_at, + in_now ) -RETURNING tx_in_id, created -INTO out_tx_row_id, out_timestamp; +RETURNING tx_in_id INTO out_tx_row_id; -- Notify new incoming transaction registration PERFORM pg_notify('tx_in', out_tx_row_id || ''); IF in_type IS NOT NULL THEN @@ -118,19 +121,18 @@ CREATE FUNCTION register_tx_out( IN in_subject TEXT, IN in_credit_account TEXT, IN in_credit_name TEXT, - IN in_timestamp INT8, + IN in_valued_at INT8, IN in_wtid BYTEA, IN in_origin_exchange_url TEXT, + IN in_now INT8, -- Success return OUT out_tx_row_id INT8, - OUT out_timestamp INT8, OUT out_new BOOLEAN ) LANGUAGE plpgsql AS $$ BEGIN -- Check for idempotence -SELECT tx_out_id, created -INTO out_tx_row_id, out_timestamp +SELECT tx_out_id INTO out_tx_row_id FROM tx_out WHERE magnet_code = in_code; out_new = NOT found; @@ -142,17 +144,18 @@ IF out_new THEN subject, credit_account, credit_name, - created + valued_at, + registered_at ) VALUES ( in_code, in_amount, in_subject, in_credit_account, in_credit_name, - in_timestamp + in_valued_at, + in_now ) - RETURNING tx_out_id, created - INTO out_tx_row_id, out_timestamp; + RETURNING tx_out_id INTO out_tx_row_id; -- Notify new outgoing transaction registration PERFORM pg_notify('tx_out', out_tx_row_id || ''); @@ -182,13 +185,13 @@ CREATE FUNCTION taler_transfer( IN in_exchange_base_url TEXT, IN in_credit_account TEXT, IN in_credit_name TEXT, - IN in_timestamp INT8, + IN in_now INT8, -- Error return OUT out_request_uid_reuse BOOLEAN, OUT out_wtid_reuse BOOLEAN, -- Success return - OUT out_tx_row_id INT8, - OUT out_timestamp INT8 + OUT out_initiated_row_id INT8, + OUT out_initiated_at INT8 ) LANGUAGE plpgsql AS $$ BEGIN @@ -197,8 +200,8 @@ SELECT (amount != in_amount OR credit_account != in_credit_account OR exchange_base_url != in_exchange_base_url OR wtid != in_wtid) - ,transfer.initiated_id, created -INTO out_request_uid_reuse, out_tx_row_id, out_timestamp + ,initiated_id, initiated_at +INTO out_request_uid_reuse, out_initiated_row_id, out_initiated_at FROM transfer JOIN initiated USING (initiated_id) WHERE request_uid = in_request_uid; IF FOUND THEN @@ -212,20 +215,21 @@ IF out_wtid_reuse THEN RETURN; END IF; -- Insert an initiated outgoing transaction +out_initiated_at = in_now; INSERT INTO initiated ( amount, subject, credit_account, credit_name, - created + initiated_at ) VALUES ( in_amount, in_subject, in_credit_account, in_credit_name, - in_timestamp -) RETURNING initiated_id, created -INTO out_tx_row_id, out_timestamp; + in_now +) RETURNING initiated_id +INTO out_initiated_row_id; -- Insert a transfer operation INSERT INTO transfer ( initiated_id, @@ -233,12 +237,12 @@ INSERT INTO transfer ( wtid, exchange_base_url ) VALUES ( - out_tx_row_id, + out_initiated_row_id, in_request_uid, in_wtid, in_exchange_base_url ); -PERFORM pg_notify('transfer', out_tx_row_id || ''); +PERFORM pg_notify('transfer', out_initiated_row_id || ''); END $$; CREATE FUNCTION initiated_status_update( @@ -274,27 +278,26 @@ CREATE FUNCTION register_bounce_tx_in( IN in_subject TEXT, IN in_debit_account TEXT, IN in_debit_name TEXT, + IN in_valued_at INT8, IN in_bounce_amount taler_amount, IN in_reason TEXT, - IN in_timestamp INT8, + IN in_now INT8, -- Success return OUT out_tx_row_id 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 $$ 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); +SELECT register_tx_in.out_tx_row_id, register_tx_in.out_new +INTO out_tx_row_id, out_tx_new +FROM register_tx_in(in_code, in_amount, in_subject, in_debit_account, in_debit_name, in_valued_at, NULL, NULL, in_now); -- Check if already bounce -SELECT initiated_id, created - INTO out_bounce_row_id, out_bounce_timestamp +SELECT initiated_id + INTO out_bounce_row_id FROM bounced JOIN initiated USING (initiated_id) WHERE tx_in_id = out_tx_row_id; out_bounce_new=NOT FOUND; @@ -306,15 +309,15 @@ IF out_bounce_new THEN subject, credit_account, credit_name, - created + initiated_at ) VALUES ( in_amount, 'bounce: ' || in_code, in_debit_account, in_debit_name, - in_timestamp + in_now ) - RETURNING initiated_id, created INTO out_bounce_row_id, out_bounce_timestamp; + RETURNING initiated_id INTO out_bounce_row_id; -- Register the bounce INSERT INTO bounced ( tx_in_id, diff --git a/taler-magnet-bank/src/adapter.rs b/taler-magnet-bank/src/adapter.rs @@ -88,8 +88,8 @@ impl WireGateway for MagnetApi { let creditor = FullHuPayto::try_from(&req.credit_account)?; let result = db::make_transfer(&self.pool, &req, &creditor, &Timestamp::now()).await?; match result { - db::TransferResult::Success { id, timestamp } => Ok(TransferResponse { - timestamp, + db::TransferResult::Success { id, initiated_at } => Ok(TransferResponse { + timestamp: initiated_at, row_id: SafeU64::try_from(id).unwrap(), }), db::TransferResult::RequestUidReuse => Err(failure( @@ -149,17 +149,17 @@ impl WireGateway for MagnetApi { amount: req.amount, subject: format!("Admin incoming {}", req.reserve_pub), debtor, - timestamp: Timestamp::now(), metadata: IncomingSubject::Reserve(req.reserve_pub), }, + &Timestamp::now(), ) .await?; match res { AddIncomingResult::Success { - row_id, timestamp, .. + row_id, valued_at, .. } => Ok(AddIncomingResponse { row_id: safe_u64(row_id), - timestamp, + timestamp: valued_at, }), AddIncomingResult::ReservePubReuse => Err(failure( ErrorCode::BANK_DUPLICATE_RESERVE_PUB_SUBJECT, @@ -176,17 +176,17 @@ impl WireGateway for MagnetApi { amount: req.amount, subject: format!("Admin incoming KYC:{}", req.account_pub), debtor, - timestamp: Timestamp::now(), metadata: IncomingSubject::Kyc(req.account_pub), }, + &Timestamp::now(), ) .await?; match res { AddIncomingResult::Success { - row_id, timestamp, .. + row_id, valued_at, .. } => Ok(AddKycauthResponse { row_id: safe_u64(row_id), - timestamp, + timestamp: valued_at, }), AddIncomingResult::ReservePubReuse => Err(failure( ErrorCode::BANK_DUPLICATE_RESERVE_PUB_SUBJECT, diff --git a/taler-magnet-bank/src/db.rs b/taler-magnet-bank/src/db.rs @@ -63,7 +63,7 @@ pub struct TxIn { pub amount: Amount, pub subject: String, pub debtor: FullHuPayto, - pub timestamp: Timestamp, + pub value_date: Timestamp, } impl Display for TxIn { @@ -73,9 +73,9 @@ impl Display for TxIn { amount, subject, debtor, - timestamp, + value_date, } = self; - write!(f, "{timestamp} {amount} {code} {debtor} '{subject}'") + write!(f, "{value_date} {amount} {code} {debtor} '{subject}'") } } @@ -85,7 +85,7 @@ pub struct TxOut { pub amount: Amount, pub subject: String, pub creditor: FullHuPayto, - pub timestamp: Timestamp, + pub value_date: Timestamp, } impl Display for TxOut { @@ -95,9 +95,9 @@ impl Display for TxOut { amount, subject, creditor, - timestamp, + value_date, } = self; - write!(f, "{timestamp} {amount} {code} {creditor} '{subject}'") + write!(f, "{value_date} {amount} {code} {creditor} '{subject}'") } } @@ -106,7 +106,6 @@ pub struct TxInAdmin { pub amount: Amount, pub subject: String, pub debtor: FullHuPayto, - pub timestamp: Timestamp, pub metadata: IncomingSubject, } @@ -114,7 +113,6 @@ pub struct TxInAdmin { pub struct AddOutgoingResult { pub new: bool, pub row_id: u64, - pub timestamp: Timestamp, } #[derive(Debug, PartialEq, Eq)] @@ -122,7 +120,7 @@ pub enum AddIncomingResult { Success { new: bool, row_id: u64, - timestamp: Timestamp, + valued_at: Timestamp, }, ReservePubReuse, } @@ -145,18 +143,22 @@ impl Display for Initiated { } } -pub async fn register_tx_in_admin(db: &PgPool, tx: &TxInAdmin) -> sqlx::Result<AddIncomingResult> { +pub async fn register_tx_in_admin( + db: &PgPool, + tx: &TxInAdmin, + now: &Timestamp, +) -> sqlx::Result<AddIncomingResult> { sqlx::query( " - SELECT out_reserve_pub_reuse, out_new, out_tx_row_id, out_timestamp - FROM register_tx_in(NULL, ($1, $2)::taler_amount, $3, $4, $5, $6, $7, $8) + SELECT out_reserve_pub_reuse, out_tx_row_id, out_valued_at, out_new + FROM register_tx_in(NULL, ($1, $2)::taler_amount, $3, $4, $5, $6, $7, $8, $6) ", ) .bind_amount(&tx.amount) .bind(&tx.subject) .bind(tx.debtor.iban()) .bind(&tx.debtor.name) - .bind_timestamp(&tx.timestamp) + .bind_timestamp(now) .bind(tx.metadata.ty()) .bind(tx.metadata.key()) .try_map(|r: PgRow| { @@ -164,9 +166,9 @@ pub async fn register_tx_in_admin(db: &PgPool, tx: &TxInAdmin) -> sqlx::Result<A AddIncomingResult::ReservePubReuse } else { AddIncomingResult::Success { - new: r.try_get(1)?, - row_id: r.try_get_u64(2)?, - timestamp: r.try_get_timestamp(3)?, + row_id: r.try_get_u64(1)?, + valued_at: r.try_get_timestamp(2)?, + new: r.try_get(3)?, } }) }) @@ -178,11 +180,12 @@ pub async fn register_tx_in( db: &mut PgConnection, tx: &TxIn, subject: &Option<IncomingSubject>, + now: &Timestamp, ) -> sqlx::Result<AddIncomingResult> { sqlx::query( " - SELECT out_reserve_pub_reuse, out_new, out_tx_row_id, out_timestamp - FROM register_tx_in($1, ($2, $3)::taler_amount, $4, $5, $6, $7, $8, $9) + SELECT out_reserve_pub_reuse, out_tx_row_id, out_valued_at, out_new + FROM register_tx_in($1, ($2, $3)::taler_amount, $4, $5, $6, $7, $8, $9, $10) ", ) .bind(tx.code as i64) @@ -190,17 +193,18 @@ pub async fn register_tx_in( .bind(&tx.subject) .bind(tx.debtor.iban()) .bind(&tx.debtor.name) - .bind_timestamp(&tx.timestamp) + .bind_timestamp(&tx.value_date) .bind(subject.as_ref().map(|it| it.ty())) .bind(subject.as_ref().map(|it| it.key())) + .bind_timestamp(now) .try_map(|r: PgRow| { Ok(if r.try_get(0)? { AddIncomingResult::ReservePubReuse } else { AddIncomingResult::Success { - new: r.try_get(1)?, - row_id: r.try_get_u64(2)?, - timestamp: r.try_get_timestamp(3)?, + row_id: r.try_get_u64(1)?, + valued_at: r.try_get_timestamp(2)?, + new: r.try_get(3)?, } }) }) @@ -212,11 +216,12 @@ pub async fn register_tx_out( db: &mut PgConnection, tx: &TxOut, subject: &Option<OutgoingSubject>, + now: &Timestamp, ) -> sqlx::Result<AddOutgoingResult> { sqlx::query( " - SELECT out_new, out_tx_row_id, out_timestamp - FROM register_tx_out($1, ($2, $3)::taler_amount, $4, $5, $6, $7, $8, $9) + SELECT out_new, out_tx_row_id + FROM register_tx_out($1, ($2, $3)::taler_amount, $4, $5, $6, $7, $8, $9, $10) ", ) .bind(tx.code as i64) @@ -224,14 +229,14 @@ pub async fn register_tx_out( .bind(&tx.subject) .bind(tx.creditor.iban()) .bind(&tx.creditor.name) - .bind_timestamp(&tx.timestamp) + .bind_timestamp(&tx.value_date) .bind(subject.as_ref().map(|it| it.0.as_ref())) .bind(subject.as_ref().map(|it| it.1.as_str())) + .bind_timestamp(now) .try_map(|r: PgRow| { Ok(AddOutgoingResult { new: r.try_get(0)?, row_id: r.try_get_u64(1)?, - timestamp: r.try_get_timestamp(2)?, }) }) .fetch_one(db) @@ -240,7 +245,7 @@ pub async fn register_tx_out( #[derive(Debug, PartialEq, Eq)] pub enum TransferResult { - Success { id: u64, timestamp: Timestamp }, + Success { id: u64, initiated_at: Timestamp }, RequestUidReuse, WtidReuse, } @@ -249,12 +254,12 @@ pub async fn make_transfer<'a>( db: impl PgExecutor<'a>, req: &TransferRequest, creditor: &FullHuPayto, - timestamp: &Timestamp, + now: &Timestamp, ) -> sqlx::Result<TransferResult> { let subject = format!("{} {}", req.wtid, req.exchange_base_url); sqlx::query( " - SELECT out_request_uid_reuse, out_wtid_reuse, out_tx_row_id, out_timestamp + SELECT out_request_uid_reuse, out_wtid_reuse, out_initiated_row_id, out_initiated_at FROM taler_transfer($1, $2, $3, ($4, $5)::taler_amount, $6, $7, $8, $9) ", ) @@ -265,7 +270,7 @@ pub async fn make_transfer<'a>( .bind(req.exchange_base_url.as_str()) .bind(creditor.iban()) .bind(&creditor.name) - .bind_timestamp(timestamp) + .bind_timestamp(now) .try_map(|r: PgRow| { Ok(if r.try_get(0)? { TransferResult::RequestUidReuse @@ -274,7 +279,7 @@ pub async fn make_transfer<'a>( } else { TransferResult::Success { id: r.try_get_u64(2)?, - timestamp: r.try_get_timestamp(3)?, + initiated_at: r.try_get_timestamp(3)?, } }) }) @@ -285,10 +290,8 @@ pub async fn make_transfer<'a>( #[derive(Debug, PartialEq, Eq)] pub struct BounceResult { pub tx_id: u64, - pub tx_timestamp: Timestamp, pub tx_new: bool, pub bounce_id: u64, - pub bounce_timestamp: Timestamp, pub bounce_new: bool, } @@ -297,12 +300,12 @@ pub async fn register_bounce_tx_in( tx: &TxIn, amount: &Amount, reason: &str, - timestamp: &Timestamp, + now: &Timestamp, ) -> sqlx::Result<BounceResult> { sqlx::query( " - 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) + SELECT out_tx_row_id, out_tx_new, out_bounce_row_id, out_bounce_new + FROM register_bounce_tx_in($1, ($2, $3)::taler_amount, $4, $5, $6, $7, ($8, $9)::taler_amount, $10, $11) ", ) .bind(tx.code as i64) @@ -310,17 +313,16 @@ pub async fn register_bounce_tx_in( .bind(&tx.subject) .bind(tx.debtor.iban()) .bind(&tx.debtor.name) + .bind_timestamp(&tx.value_date) .bind_amount(amount) .bind(reason) - .bind_timestamp(timestamp) + .bind_timestamp(now) .try_map(|r: PgRow| { Ok(BounceResult { 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)?, + tx_new: r.try_get(1)?, + bounce_id: r.try_get_u64(2)?, + bounce_new: r.try_get(3)?, }) }) .fetch_one(db) @@ -346,7 +348,7 @@ pub async fn transfer_page<'a>( (amount).frac as amount_frac, credit_account, credit_name, - created + initiated_at FROM transfer JOIN initiated USING (initiated_id) WHERE @@ -389,7 +391,7 @@ pub async fn outgoing_history( (amount).frac as amount_frac, credit_account, credit_name, - created, + valued_at, exchange_base_url, wtid FROM taler_out @@ -432,7 +434,7 @@ pub async fn incoming_history( (amount).frac as amount_frac, debit_account, debit_name, - created, + valued_at, metadata FROM taler_in JOIN tx_in USING (tx_in_id) @@ -480,7 +482,7 @@ pub async fn revenue_history( " SELECT tx_in_id, - created, + valued_at, (amount).val as amount_val, (amount).frac as amount_frac, debit_account, @@ -520,7 +522,7 @@ pub async fn transfer_by_id<'a>( wtid, credit_account, credit_name, - created + initiated_at FROM transfer JOIN initiated USING (initiated_id) WHERE initiated_id = $1 @@ -612,6 +614,7 @@ pub async fn initiated_submit_failure<'a>( #[cfg(test)] mod test { + use jiff::Span; use sqlx::{PgConnection, PgPool, postgres::PgRow}; use taler_api::{ db::TypeHelper, @@ -662,6 +665,8 @@ mod test { .fetch_one(&mut *db) .await .unwrap(); + let now = Timestamp::now_stable(); + let later = now + Span::new().hours(4); let tx = TxIn { code: code, amount: amount("EUR:10"), @@ -669,17 +674,17 @@ mod test { debtor: magnet_payto( "payto://iban/HU30162000031000163100000000?receiver-name=name", ), - timestamp: Timestamp::now_stable(), + value_date: now, }; // Insert assert_eq!( - register_tx_in(db, &tx, &first) + register_tx_in(db, &tx, &first, &now) .await .expect("register tx in"), AddIncomingResult::Success { new: true, row_id: id, - timestamp: tx.timestamp + valued_at: now } ); // Idempotent @@ -687,17 +692,18 @@ mod test { register_tx_in( db, &TxIn { - timestamp: Timestamp::now(), + value_date: later, ..tx.clone() }, - &first + &first, + &now ) .await .expect("register tx in"), AddIncomingResult::Success { new: false, row_id: id, - timestamp: tx.timestamp + valued_at: now } ); // Many @@ -706,16 +712,18 @@ mod test { db, &TxIn { code: code + 1, + value_date: later, ..tx }, - &second + &second, + &now ) .await .expect("register tx in"), AddIncomingResult::Success { new: true, row_id: id + 1, - timestamp: tx.timestamp + valued_at: later } ); } @@ -782,39 +790,34 @@ mod test { Vec::new() ); + let now = Timestamp::now_stable(); + let later = now + Span::new().hours(3); let tx = TxInAdmin { amount: amount("EUR:10"), subject: "subject".to_owned(), debtor: magnet_payto("payto://iban/HU30162000031000163100000000?receiver-name=name"), - timestamp: Timestamp::now_stable(), metadata: IncomingSubject::Reserve(EddsaPublicKey::rand()), }; // Insert assert_eq!( - register_tx_in_admin(&pool, &tx) + register_tx_in_admin(&pool, &tx, &now) .await .expect("register tx in"), AddIncomingResult::Success { new: true, row_id: 1, - timestamp: tx.timestamp + valued_at: now } ); // Idempotent assert_eq!( - register_tx_in_admin( - &pool, - &TxInAdmin { - timestamp: Timestamp::now(), - ..tx.clone() - } - ) - .await - .expect("register tx in"), + register_tx_in_admin(&pool, &tx, &later) + .await + .expect("register tx in"), AddIncomingResult::Success { new: false, row_id: 1, - timestamp: tx.timestamp + valued_at: now } ); // Many @@ -825,14 +828,15 @@ mod test { subject: "Other".to_owned(), metadata: IncomingSubject::Reserve(EddsaPublicKey::rand()), ..tx.clone() - } + }, + &later ) .await .expect("register tx in"), AddIncomingResult::Success { new: true, row_id: 2, - timestamp: tx.timestamp + valued_at: later } ); @@ -861,6 +865,8 @@ mod test { .fetch_one(&mut *db) .await .unwrap(); + let now = Timestamp::now_stable(); + let later = now + Span::new().hours(4); let tx = TxOut { code, amount: amount("EUR:10"), @@ -868,17 +874,16 @@ mod test { creditor: magnet_payto( "payto://iban/HU30162000031000163100000000?receiver-name=name", ), - timestamp: Timestamp::now_stable(), + value_date: now, }; // Insert assert_eq!( - register_tx_out(db, &tx, &first) + register_tx_out(db, &tx, &first, &now) .await .expect("register tx out"), AddOutgoingResult { new: true, row_id: id, - timestamp: tx.timestamp } ); // Idempotent @@ -886,17 +891,17 @@ mod test { register_tx_out( db, &TxOut { - timestamp: Timestamp::now(), + value_date: later, ..tx.clone() }, - &first + &first, + &now ) .await .expect("register tx out"), AddOutgoingResult { new: false, row_id: id, - timestamp: tx.timestamp } ); // Many @@ -905,16 +910,17 @@ mod test { db, &TxOut { code: code + 1, + value_date: later, ..tx.clone() }, - &second + &second, + &now ) .await .expect("register tx out"), AddOutgoingResult { new: true, row_id: id + 1, - timestamp: tx.timestamp } ); } @@ -975,25 +981,26 @@ mod test { credit_account: payto("payto://iban/HU02162000031000164800000000?receiver-name=name"), }; let payto = magnet_payto("payto://iban/HU30162000031000163100000000?receiver-name=name"); - let timestamp = Timestamp::now_stable(); + let now = Timestamp::now_stable(); + let later = now + Span::new().hours(2); // Insert assert_eq!( - make_transfer(&mut db, &req, &payto, &timestamp) + make_transfer(&mut db, &req, &payto, &now) .await .expect("transfer"), TransferResult::Success { id: 1, - timestamp: timestamp + initiated_at: now } ); // Idempotent assert_eq!( - make_transfer(&mut db, &req, &payto, &Timestamp::now()) + make_transfer(&mut db, &req, &payto, &later) .await .expect("transfer"), TransferResult::Success { id: 1, - timestamp: timestamp + initiated_at: now } ); // Request UID reuse @@ -1005,7 +1012,7 @@ mod test { ..req.clone() }, &payto, - &Timestamp::now() + &now ) .await .expect("transfer"), @@ -1020,7 +1027,7 @@ mod test { ..req.clone() }, &payto, - &Timestamp::now() + &now ) .await .expect("transfer"), @@ -1036,13 +1043,13 @@ mod test { ..req }, &payto, - &timestamp + &later ) .await .expect("transfer"), TransferResult::Success { id: 2, - timestamp: timestamp + initiated_at: later } ); @@ -1065,15 +1072,10 @@ mod test { let amount = amount("HUF:10"); let payto = magnet_payto("payto://iban/HU30162000031000163100000000?receiver-name=name"); - let timestamp = Timestamp::now_stable(); + let now = Timestamp::now_stable(); // Empty db - assert!( - db::pending_batch(&mut db, &timestamp) - .await - .unwrap() - .is_empty() - ); + assert!(db::pending_batch(&mut db, &now).await.unwrap().is_empty()); // Insert assert_eq!( @@ -1084,16 +1086,17 @@ mod test { amount: amount.clone(), subject: "subject".to_owned(), debtor: payto.clone(), - timestamp + value_date: now }, - &None + &None, + &now ) .await .expect("register tx in"), AddIncomingResult::Success { new: true, row_id: 1, - timestamp: timestamp + valued_at: now } ); @@ -1106,20 +1109,18 @@ mod test { amount: amount.clone(), subject: "subject".to_owned(), debtor: payto.clone(), - timestamp + value_date: now }, &amount, "good reason", - &timestamp + &now ) .await .expect("bounce"), BounceResult { tx_id: 2, - tx_timestamp: timestamp, tx_new: true, bounce_id: 1, - bounce_timestamp: timestamp, bounce_new: true } ); @@ -1132,20 +1133,18 @@ mod test { amount: amount.clone(), subject: "subject".to_owned(), debtor: payto.clone(), - timestamp + value_date: now }, &amount, "good reason", - &timestamp + &now ) .await .expect("bounce"), BounceResult { tx_id: 2, - tx_timestamp: timestamp, tx_new: false, bounce_id: 1, - bounce_timestamp: timestamp, bounce_new: false } ); @@ -1159,20 +1158,18 @@ mod test { amount: amount.clone(), subject: "subject".to_owned(), debtor: payto.clone(), - timestamp + value_date: now }, &amount, "good reason", - &timestamp + &now ) .await .expect("bounce"), BounceResult { tx_id: 1, - tx_timestamp: timestamp, tx_new: false, bounce_id: 2, - bounce_timestamp: timestamp, bounce_new: true } ); @@ -1185,27 +1182,25 @@ mod test { amount: amount.clone(), subject: "subject".to_owned(), debtor: payto.clone(), - timestamp + value_date: now }, &amount, "good reason", - &timestamp + &now ) .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(), + db::pending_batch(&mut db, &now).await.unwrap(), &[ Initiated { id: 1, diff --git a/taler-magnet-bank/src/worker.rs b/taler-magnet-bank/src/worker.rs @@ -90,8 +90,13 @@ impl Worker<'_> { match parse_incoming_unstructured(&tx_in.subject) { Ok(None) => bounce(self.db, "missing public key").await?, Ok(Some(subject)) => { - let res = - db::register_tx_in(self.db, &tx_in, &Some(subject)).await?; + let res = db::register_tx_in( + self.db, + &tx_in, + &Some(subject), + &Timestamp::now(), + ) + .await?; match res { AddIncomingResult::Success { new, .. } => { if new { @@ -110,7 +115,9 @@ impl Worker<'_> { } Tx::Out(tx_out) => { let subject = subject::parse_outgoing(&tx_out.subject); - let res = db::register_tx_out(self.db, &tx_out, &subject.ok()).await?; + let res = + db::register_tx_out(self.db, &tx_out, &subject.ok(), &Timestamp::now()) + .await?; // TODO log recovered & log malformed if res.new { info!("outgoing {tx_out}"); @@ -232,7 +239,7 @@ pub fn extract_tx_info(tx: Transaction) -> Tx { amount, subject: tx.subject, debtor: counter_account, - timestamp: Timestamp::from(tx.value_date), + value_date: Timestamp::from(tx.value_date), }) } else { Tx::Out(TxOut { @@ -240,7 +247,7 @@ pub fn extract_tx_info(tx: Transaction) -> Tx { amount, subject: tx.subject, creditor: counter_account, - timestamp: Timestamp::from(tx.value_date), + value_date: Timestamp::from(tx.value_date), }) } } diff --git a/taler-magnet-bank/tests/api.rs b/taler-magnet-bank/tests/api.rs @@ -75,6 +75,7 @@ async fn outgoing_history() { let acquire = pool.acquire(); async move { let mut conn = acquire.await.unwrap(); + let now = Timestamp::now(); db::register_tx_out( &mut *conn, &db::TxOut { @@ -84,12 +85,13 @@ async fn outgoing_history() { creditor: magnet_payto( "payto://iban/HU30162000031000163100000000?receiver-name=name", ), - timestamp: Timestamp::now_stable(), + value_date: now, }, &Some(OutgoingSubject( ShortHashCode::rand(), url("https://exchange.test"), )), + &now, ) .await .unwrap();