commit daf4176f5b2dea94728251b6138187b83a73e429
parent c6591e06b44a5b95df91e78f2b06f6a114ce0a69
Author: Antoine A <>
Date: Wed, 4 Nov 2026 10:54:36 +0100
wise: per balance transfer mapping
Diffstat:
5 files changed, 48 insertions(+), 28 deletions(-)
diff --git a/adapters/taler-wise/db/wise-0001.sql b/adapters/taler-wise/db/wise-0001.sql
@@ -55,6 +55,7 @@ CREATE TYPE incoming_type AS ENUM
COMMENT ON TYPE incoming_type IS 'Types of incoming talerable transactions';
CREATE TABLE taler_in (
+ balance_id INT8 NOT NULL,
taler_in_id INT8 GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
tx_in_id INT8 NOT NULL UNIQUE REFERENCES tx_in(tx_in_id) ON DELETE CASCADE,
type incoming_type NOT NULL,
@@ -64,25 +65,30 @@ CREATE TABLE taler_in (
);
COMMENT ON TABLE taler_in IS 'Incoming talerable transactions';
-CREATE UNIQUE INDEX taler_in_unique_reserve_pub ON taler_in (metadata) WHERE type = 'reserve';
+CREATE UNIQUE INDEX taler_in_unique_reserve_pub ON taler_in (balance_id, metadata) WHERE type = 'reserve';
CREATE TABLE prepared_in (
+ balance_id INT8 NOT NULL,
type incoming_type NOT NULL,
account_pub BYTEA NOT NULL CHECK (LENGTH(account_pub)=32),
- authorization_pub BYTEA UNIQUE NOT NULL CHECK (LENGTH(authorization_pub)=32),
+ authorization_pub BYTEA NOT NULL CHECK (LENGTH(authorization_pub)=32),
authorization_sig BYTEA NOT NULL CHECK (LENGTH(authorization_sig)=64),
recurrent BOOLEAN NOT NULL,
registered_at INT8 NOT NULL,
- tx_in_id INT8 UNIQUE REFERENCES tx_in(tx_in_id) ON DELETE CASCADE
+ tx_in_id INT8 UNIQUE REFERENCES tx_in(tx_in_id) ON DELETE CASCADE,
+ PRIMARY KEY (balance_id, authorization_pub)
);
COMMENT ON TABLE prepared_in IS 'Prepared incoming transaction';
CREATE UNIQUE INDEX prepared_in_unique_reserve_pub
- ON prepared_in (account_pub) WHERE type = 'reserve';
+ ON prepared_in (balance_id, account_pub) WHERE type = 'reserve';
CREATE TABLE pending_recurrent_in(
tx_in_id INT8 NOT NULL UNIQUE REFERENCES tx_in(tx_in_id) ON DELETE CASCADE,
- authorization_pub BYTEA NOT NULL REFERENCES prepared_in(authorization_pub)
+ balance_id INT8 NOT NULL,
+ authorization_pub BYTEA NOT NULL,
+ FOREIGN KEY (balance_id, authorization_pub)
+ REFERENCES prepared_in(balance_id, authorization_pub)
);
CREATE INDEX pending_recurrent_inc_auth_pub
- ON pending_recurrent_in (authorization_pub);
+ ON pending_recurrent_in (balance_id, authorization_pub);
COMMENT ON TABLE pending_recurrent_in IS 'Pending recurrent incoming transaction';
diff --git a/adapters/taler-wise/db/wise-procedures.sql b/adapters/taler-wise/db/wise-procedures.sql
@@ -84,7 +84,7 @@ IF in_type = 'map' THEN
tx_in_id IS NOT NULL AND recurrent
INTO in_type, in_metadata, local_authorization_pub, local_authorization_sig, out_mapping_reuse, out_pending
FROM prepared_in
- WHERE authorization_pub = in_metadata;
+ WHERE balance_id = in_balance_id AND authorization_pub = in_metadata;
out_unknown_mapping = NOT FOUND;
IF out_unknown_mapping OR out_mapping_reuse THEN
RETURN;
@@ -93,7 +93,7 @@ END IF;
-- Check conflict
-out_reserve_pub_reuse=NOT out_pending AND in_type = 'reserve' AND EXISTS(SELECT FROM taler_in WHERE metadata = in_metadata AND type = 'reserve');
+out_reserve_pub_reuse=NOT out_pending AND in_type = 'reserve' AND EXISTS(SELECT FROM taler_in WHERE balance_id = in_balance_id AND metadata = in_metadata AND type = 'reserve');
IF out_reserve_pub_reuse THEN
RETURN;
END IF;
@@ -127,24 +127,26 @@ PERFORM pg_notify('tx_in', in_balance_id || ' ' || out_tx_row_id);
IF out_pending THEN
-- Delay talerable registration until mapping again
- INSERT INTO pending_recurrent_in (tx_in_id, authorization_pub)
- VALUES (out_tx_row_id, local_authorization_pub);
+ INSERT INTO pending_recurrent_in (tx_in_id, balance_id, authorization_pub)
+ VALUES (out_tx_row_id, in_balance_id, local_authorization_pub);
ELSIF in_type IS NOT NULL AND in_debit_payto IS NOT NULL THEN
UPDATE prepared_in
SET tx_in_id = out_tx_row_id
- WHERE (
+ WHERE balance_id = in_balance_id AND ((
tx_in_id IS NULL AND account_pub = in_metadata AND in_type=type AND type='reserve'
- ) OR authorization_pub = local_authorization_pub;
+ ) OR authorization_pub = local_authorization_pub);
-- Lock for incoming history id commit ordering
LOCK TABLE taler_in IN SHARE ROW EXCLUSIVE MODE;
-- Insert new incoming talerable transaction
INSERT INTO taler_in (
+ balance_id,
tx_in_id,
type,
metadata,
authorization_pub,
authorization_sig
) VALUES (
+ in_balance_id,
out_tx_row_id,
in_type,
in_metadata,
@@ -159,6 +161,7 @@ COMMENT ON FUNCTION register_tx_in IS 'Register an incoming transaction idempote
CREATE FUNCTION register_prepared_transfers (
+ IN in_balance_id INT8,
IN in_type incoming_type,
IN in_account_pub BYTEA,
IN in_authorization_pub BYTEA,
@@ -182,20 +185,20 @@ SELECT type = in_type
AND recurrent = in_recurrent
INTO idempotent
FROM prepared_in
-WHERE authorization_pub = in_authorization_pub;
+WHERE balance_id = in_balance_id AND authorization_pub = in_authorization_pub;
-- Check idempotency and delay garbage collection
IF FOUND AND idempotent THEN
UPDATE prepared_in
SET registered_at=in_timestamp,authorization_sig=in_authorization_sig
- WHERE authorization_pub=in_authorization_pub;
+ WHERE balance_id = in_balance_id AND authorization_pub=in_authorization_pub;
RETURN;
END IF;
-- Check reserve pub reuse
out_reserve_pub_reuse=in_type = 'reserve' AND (
- EXISTS(SELECT FROM taler_in WHERE metadata = in_account_pub AND type = 'reserve')
- OR EXISTS(SELECT FROM prepared_in WHERE account_pub = in_account_pub AND type = 'reserve' AND authorization_pub != in_authorization_pub)
+ EXISTS(SELECT FROM taler_in WHERE balance_id = in_balance_id AND metadata = in_account_pub AND type = 'reserve')
+ OR EXISTS(SELECT FROM prepared_in WHERE balance_id = in_balance_id AND account_pub = in_account_pub AND type = 'reserve' AND authorization_pub != in_authorization_pub)
);
IF out_reserve_pub_reuse THEN
RETURN;
@@ -210,7 +213,9 @@ IF in_recurrent THEN
SELECT pending.tx_in_id
FROM pending_recurrent_in AS pending
JOIN tx_in USING (tx_in_id)
- WHERE authorization_pub = in_authorization_pub
+ WHERE pending.balance_id = in_balance_id
+ AND tx_in.balance_id = in_balance_id
+ AND authorization_pub = in_authorization_pub
ORDER BY valued_at ASC
LIMIT 1
)
@@ -219,8 +224,8 @@ IF in_recurrent THEN
IF talerable_tx IS NOT NULL THEN
-- Lock for incoming history id commit ordering
LOCK TABLE taler_in IN SHARE ROW EXCLUSIVE MODE;
- INSERT INTO taler_in (tx_in_id, type, metadata, authorization_pub, authorization_sig)
- VALUES (talerable_tx, in_type, in_account_pub, in_authorization_pub, in_authorization_sig)
+ INSERT INTO taler_in (balance_id, tx_in_id, type, metadata, authorization_pub, authorization_sig)
+ VALUES (in_balance_id, talerable_tx, in_type, in_account_pub, in_authorization_pub, in_authorization_sig)
RETURNING taler_in_id INTO local_taler_in_id;
PERFORM pg_notify('taler_in', local_balance_id || ' ' || local_taler_in_id);
END IF;
@@ -228,6 +233,7 @@ END IF;
-- Upsert registration
INSERT INTO prepared_in (
+ balance_id,
type,
account_pub,
authorization_pub,
@@ -236,6 +242,7 @@ INSERT INTO prepared_in (
registered_at,
tx_in_id
) VALUES (
+ in_balance_id,
in_type,
in_account_pub,
in_authorization_pub,
@@ -243,7 +250,7 @@ INSERT INTO prepared_in (
in_recurrent,
in_timestamp,
talerable_tx
-) ON CONFLICT (authorization_pub)
+) ON CONFLICT (balance_id, authorization_pub)
DO UPDATE SET
type = EXCLUDED.type,
account_pub = EXCLUDED.account_pub,
@@ -254,6 +261,7 @@ DO UPDATE SET
END $$;
CREATE FUNCTION delete_prepared_transfers (
+ IN in_balance_id INT8,
IN in_authorization_pub BYTEA,
IN in_timestamp INT8,
OUT out_found BOOLEAN
@@ -263,7 +271,7 @@ BEGIN
-- Delete registration
DELETE FROM prepared_in
-WHERE authorization_pub = in_authorization_pub;
+WHERE balance_id = in_balance_id AND authorization_pub = in_authorization_pub;
out_found = FOUND;
END $$;
diff --git a/adapters/taler-wise/src/api.rs b/adapters/taler-wise/src/api.rs
@@ -375,7 +375,7 @@ impl PreparedTransfer for WiseBalanceApi {
if creditor != self.payto {
return Err(failure_code(ErrorCode::BANK_UNKNOWN_CREDITOR));
}
- match db::transfer_register(&self.pool, &req).await? {
+ match db::transfer_register(&self.pool, self.id, &req).await? {
db::RegistrationResult::Success => {
let simple = TransferSubject::Simple {
credit_amount: req.credit_amount,
@@ -397,7 +397,7 @@ impl PreparedTransfer for WiseBalanceApi {
}
async fn unregistration(&self, req: Unregistration) -> ApiResult<bool> {
- Ok(db::transfer_unregister(&self.pool, &req).await?)
+ Ok(db::transfer_unregister(&self.pool, self.id, &req).await?)
}
}
diff --git a/adapters/taler-wise/src/db.rs b/adapters/taler-wise/src/db.rs
@@ -302,7 +302,7 @@ pub async fn incoming_history(
authorization_pub,
authorization_sig
FROM taler_in
- JOIN tx_in USING (tx_in_id)
+ JOIN tx_in USING (tx_in_id, balance_id)
WHERE balance_id =
",
);
@@ -394,13 +394,15 @@ pub enum RegistrationResult {
pub async fn transfer_register(
db: &PgPool,
+ balance_id: u32,
req: &RegistrationRequest,
) -> sqlx::Result<RegistrationResult> {
let ty: IncomingType = req.r#type.into();
serialized!(
sqlx::query(
- "SELECT out_reserve_pub_reuse FROM register_prepared_transfers($1,$2,$3,$4,$5,$6)"
+ "SELECT out_reserve_pub_reuse FROM register_prepared_transfers($1,$2,$3,$4,$5,$6,$7)"
)
+ .bind(i64::from(balance_id))
.bind(ty)
.bind(req.account_pub)
.bind(req.authorization_pub)
@@ -418,9 +420,14 @@ pub async fn transfer_register(
)
}
-pub async fn transfer_unregister(db: &PgPool, req: &Unregistration) -> sqlx::Result<bool> {
+pub async fn transfer_unregister(
+ db: &PgPool,
+ balance_id: u32,
+ req: &Unregistration,
+) -> sqlx::Result<bool> {
serialized!(
- sqlx::query("SELECT out_found FROM delete_prepared_transfers($1,$2)")
+ sqlx::query("SELECT out_found FROM delete_prepared_transfers($1,$2,$3)")
+ .bind(i64::from(balance_id))
.bind(req.authorization_pub)
.bind_timestamp(&Timestamp::now())
.try_map(|r: PgRow| r.try_get_flag("out_found"))
diff --git a/taler-apns-relay/src/api.rs b/taler-apns-relay/src/api.rs
@@ -29,7 +29,6 @@ use taler_api::{
error::{ApiResult, failure},
extract::{Path, Req},
};
-
use taler_common::error_code::ErrorCode;
use crate::db;