taler-rust

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

routine.rs (54657B)


      1 /*
      2   This file is part of TALER
      3   Copyright (C) 2024-2026 Taler Systems SA
      4 
      5   TALER is free software; you can redistribute it and/or modify it under the
      6   terms of the GNU Affero General Public License as published by the Free Software
      7   Foundation; either version 3, or (at your option) any later version.
      8 
      9   TALER is distributed in the hope that it will be useful, but WITHOUT ANY
     10   WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
     11   A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more details.
     12 
     13   You should have received a copy of the GNU Affero General Public License along with
     14   TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
     15 */
     16 
     17 use std::{
     18     fmt::Debug,
     19     future::Future,
     20     str::FromStr,
     21     sync::LazyLock,
     22     time::{Duration, Instant},
     23 };
     24 
     25 use aws_lc_rs::signature::{Ed25519KeyPair, KeyPair as _};
     26 use axum::{Router, http::StatusCode};
     27 use jiff::{SignedDuration, Timestamp};
     28 use serde::de::DeserializeOwned;
     29 use taler_api::subject::fmt_in_subject;
     30 use taler_common::{
     31     api::{
     32         EddsaPublicKey, EddsaSignature, HashCode, ShortHashCode,
     33         params::PageParams,
     34         prepared::{
     35             PublicKeyAlg, RegistrationRequest, RegistrationResponse, TransferSubject, TransferType,
     36             Unregistration,
     37         },
     38         revenue::RevenueIncomingHistory,
     39         wire::{
     40             IncomingBankTransaction, IncomingHistory, OutgoingHistory, TransferList,
     41             TransferRequest, TransferResponse, TransferState, TransferStatus,
     42         },
     43     },
     44     db::IncomingType,
     45     error_code::ErrorCode,
     46     signature::Signature as _,
     47     types::{
     48         amount::{Amount, Currency, amount},
     49         base32::Base32,
     50         payto::PaytoURI,
     51         time::TalerTimestamp,
     52         url,
     53     },
     54 };
     55 use tokio::time::sleep;
     56 
     57 use crate::{
     58     json,
     59     server::{TestResponse, TestServer as _},
     60 };
     61 
     62 static UNKNOWN: LazyLock<PaytoURI> = LazyLock::new(|| {
     63     PaytoURI::from_str("payto://malformed/unused?receiver-name=Malformed").unwrap()
     64 });
     65 
     66 pub trait Page: DeserializeOwned + Debug {
     67     fn ids(&self) -> Vec<i64>;
     68 }
     69 
     70 impl Page for IncomingHistory {
     71     fn ids(&self) -> Vec<i64> {
     72         self.incoming_transactions
     73             .iter()
     74             .map(|it| match it {
     75                 IncomingBankTransaction::Reserve { row_id, .. }
     76                 | IncomingBankTransaction::Wad { row_id, .. }
     77                 | IncomingBankTransaction::Kyc { row_id, .. } => *row_id as i64,
     78             })
     79             .collect()
     80     }
     81 }
     82 
     83 impl Page for OutgoingHistory {
     84     fn ids(&self) -> Vec<i64> {
     85         self.outgoing_transactions
     86             .iter()
     87             .map(|it| it.row_id as i64)
     88             .collect()
     89     }
     90 }
     91 
     92 impl Page for RevenueIncomingHistory {
     93     fn ids(&self) -> Vec<i64> {
     94         self.incoming_transactions
     95             .iter()
     96             .map(|it| it.row_id as i64)
     97             .collect()
     98     }
     99 }
    100 
    101 impl Page for TransferList {
    102     fn ids(&self) -> Vec<i64> {
    103         self.transfers.iter().map(|it| it.row_id as i64).collect()
    104     }
    105 }
    106 
    107 pub async fn latest_id<T: Page>(router: &Router) -> i64 {
    108     let res = router.get("?limit=-1").await;
    109     if res.status == StatusCode::NO_CONTENT {
    110         0
    111     } else {
    112         res.assert_ids::<T>(1)[0]
    113     }
    114 }
    115 
    116 pub async fn routine_pagination<T: Page>(
    117     server: &Router,
    118     mut register: Tasks<impl AsyncFnMut(usize)>,
    119 ) {
    120     // Check supported
    121     if !server.get("").await.is_implemented() {
    122         return;
    123     }
    124 
    125     // Check history is following specs
    126     let assert_history =
    127         async |args: &str, size: usize| server.get(format!("?{args}")).await.assert_ids::<T>(size);
    128     // Get latest registered id
    129     let latest_id = async || latest_id::<T>(server).await;
    130 
    131     for i in 0..20 {
    132         (register.lambda)(i).await;
    133     }
    134 
    135     let id = latest_id().await;
    136 
    137     // default
    138     assert_history("", 20).await;
    139 
    140     // forward range
    141     assert_history("limit=10", 10).await;
    142     assert_history("limit=10&offset=4", 10).await;
    143 
    144     // backward range
    145     assert_history("limit=-10", 10).await;
    146     assert_history(&format!("limit=-10&{}", id - 4), 10).await;
    147 }
    148 
    149 pub async fn assert_time<R: Debug>(range: std::ops::Range<u128>, task: impl Future<Output = R>) {
    150     let start = Instant::now();
    151     let limit = Duration::from_millis(range.end.try_into().expect("timing bound exceeds u64"));
    152     if tokio::time::timeout(limit, task).await.is_err() {
    153         panic!(
    154             "Expected to last {range:?} ms, timed out after {} ms",
    155             start.elapsed().as_millis()
    156         );
    157     }
    158     let elapsed = start.elapsed().as_millis();
    159     if !range.contains(&elapsed) {
    160         panic!("Expected to last {range:?} got {elapsed:?}")
    161     }
    162 }
    163 
    164 pub async fn routine_history<T: Page>(
    165     server: &Router,
    166     mut register: Tasks<impl AsyncFnMut(usize)>,
    167     mut ignore: Tasks<impl AsyncFnMut(usize)>,
    168 ) {
    169     // Check history is following specs
    170     macro_rules! assert_history {
    171         ($args:expr, $size:expr) => {
    172             async {
    173                 server
    174                     .get(&format!("?{}", $args))
    175                     .await
    176                     .assert_ids::<T>($size)
    177             }
    178         };
    179     }
    180     // Get latest registered id
    181     let latest_id = async || assert_history!("limit=-1", 1).await[0];
    182 
    183     // Check error when no transactions
    184     assert_history!("limit=7".to_owned(), 0).await;
    185 
    186     let mut register_iter = (0..register.len).peekable();
    187     let mut ignore_iter = (0..ignore.len).peekable();
    188     while register_iter.peek().is_some() || ignore_iter.peek().is_some() {
    189         if let Some(idx) = register_iter.next() {
    190             (register.lambda)(idx).await
    191         }
    192         if let Some(idx) = ignore_iter.next() {
    193             (ignore.lambda)(idx).await
    194         }
    195     }
    196     let nb_register = register.len;
    197     let nb_ignore = ignore.len;
    198     let nb_total = nb_register + nb_ignore;
    199 
    200     // Check ignored
    201     assert_history!(format_args!("limit={nb_total}"), nb_register).await;
    202     // Check skip ignored
    203     assert_history!(format_args!("limit={nb_register}"), nb_register).await;
    204 
    205     // Allow CI scheduling and database delays, but require completion before
    206     // the long-poll timeout so broken notification delivery still fails.
    207     // Check no polling when we cannot have more transactions
    208     assert_time(
    209         0..5000,
    210         assert_history!(
    211             format_args!("limit=-{}&timeout_ms=10000", nb_register + 1),
    212             nb_register
    213         ),
    214     )
    215     .await;
    216     // Check no polling when already find transactions even if less than delta
    217     assert_time(
    218         0..5000,
    219         assert_history!(
    220             format_args!("limit={}&timeout_ms=10000", nb_register + 1),
    221             nb_register
    222         ),
    223     )
    224     .await;
    225 
    226     // Check polling
    227     let id = latest_id().await;
    228     tokio::join!(
    229         // Check polling succeed
    230         assert_time(
    231             100..5000,
    232             assert_history!(format_args!("limit=2&offset={id}&timeout_ms=10000"), 1)
    233         ),
    234         assert_time(
    235             200..5000,
    236             assert_history!(
    237                 format_args!(
    238                     "limit=1&offset={}&timeout_ms=200",
    239                     id as usize + nb_total * 3
    240                 ),
    241                 0
    242             )
    243         ),
    244         async {
    245             sleep(Duration::from_millis(100)).await;
    246             (register.lambda)(0).await
    247         }
    248     );
    249 
    250     // Test triggers
    251     for i in 0..register.len {
    252         let id = latest_id().await;
    253         tokio::join!(
    254             // Check polling succeed
    255             assert_time(
    256                 100..5000,
    257                 assert_history!(format_args!("limit=7&offset={id}&timeout_ms=10000"), 1)
    258             ),
    259             async {
    260                 sleep(Duration::from_millis(100)).await;
    261                 (register.lambda)(i).await
    262             }
    263         );
    264     }
    265 
    266     // Test doesn't trigger
    267     let id = latest_id().await;
    268     tokio::join!(
    269         // Check polling succeed
    270         assert_time(
    271             200..5000,
    272             assert_history!(format_args!("limit=7&offset={id}&timeout_ms=200"), 0)
    273         ),
    274         async {
    275             sleep(Duration::from_millis(100)).await;
    276             for i in 0..ignore.len {
    277                 (ignore.lambda)(i).await
    278             }
    279         }
    280     );
    281 
    282     routine_pagination::<T>(server, register).await;
    283 }
    284 
    285 impl TestResponse {
    286     #[track_caller]
    287     fn assert_ids<T: Page>(&self, size: usize) -> Vec<i64> {
    288         if size == 0 {
    289             self.assert_no_content();
    290             return vec![];
    291         }
    292         let body = self.assert_ok_json::<T>();
    293         let page = body.ids();
    294         let params = self.query::<PageParams>().check().unwrap();
    295 
    296         // testing the size is like expected
    297         assert_eq!(size, page.len(), "bad page length: {page:?}\n{body:?}");
    298         if params.limit < 0 {
    299             // testing that the first id is at most the 'offset' query param.
    300             assert!(
    301                 params
    302                     .offset
    303                     .map(|offset| page[0] <= offset)
    304                     .unwrap_or(true),
    305                 "bad page offset: {params:?} {page:?}"
    306             );
    307             // testing that the id decreases.
    308             assert!(
    309                 page.as_slice().is_sorted_by(|a, b| a > b),
    310                 "bad page order: {page:?}"
    311             )
    312         } else {
    313             // testing that the first id is at least the 'offset' query param.
    314             assert!(
    315                 params
    316                     .offset
    317                     .map(|offset| page[0] >= offset)
    318                     .unwrap_or(true),
    319                 "bad page offset: {params:?} {page:?}"
    320             );
    321             // testing that the id increases.
    322             assert!(page.as_slice().is_sorted(), "bad page order: {page:?}")
    323         }
    324         page
    325     }
    326 }
    327 
    328 // Get currency from config
    329 async fn get_currency(server: &Router) -> Currency {
    330     let config = server
    331         .get("/config")
    332         .await
    333         .assert_ok_json::<serde_json::Value>();
    334     let currency = config["currency"].as_str().unwrap();
    335     Currency::from_str(currency).unwrap()
    336 }
    337 
    338 /// Test standard behavior of the transfer endpoints
    339 pub async fn transfer_routine(
    340     wire_gateway: &Router,
    341     default_status: TransferState,
    342     credit_account: &PaytoURI,
    343 ) {
    344     let currency = &get_currency(wire_gateway).await;
    345     let default_amount = amount(format!("{currency}:42"));
    346     let request_uid = HashCode::rand();
    347     let wtid = ShortHashCode::rand();
    348     let valid_req = json!({
    349         "request_uid": request_uid,
    350         "amount": default_amount,
    351         "exchange_base_url": "http://exchange.taler/",
    352         "wtid": wtid,
    353         "credit_account": credit_account,
    354     });
    355 
    356     // Check empty db
    357     {
    358         wire_gateway.get("/transfers").await.assert_no_content();
    359         wire_gateway
    360             .get(format!("/transfers?status={}", default_status.as_ref()))
    361             .await
    362             .assert_no_content();
    363     }
    364 
    365     // TODO check subject formatting
    366 
    367     let routine = async |req: &TransferRequest| {
    368         // Check OK
    369         let first = wire_gateway
    370             .post("/transfer")
    371             .json(req)
    372             .await
    373             .assert_ok_json::<TransferResponse>();
    374         // Check idempotent
    375         let second = wire_gateway
    376             .post("/transfer")
    377             .json(req)
    378             .await
    379             .assert_ok_json::<TransferResponse>();
    380         assert_eq!(first.row_id, second.row_id);
    381         assert_eq!(first.timestamp, second.timestamp);
    382 
    383         // Check by id
    384         let tx = wire_gateway
    385             .get(format!("/transfers/{}", first.row_id))
    386             .await
    387             .assert_ok_json::<TransferStatus>();
    388         assert_eq!(default_status, tx.status);
    389         assert_eq!(default_amount, tx.amount);
    390         assert_eq!("http://exchange.taler/", tx.exchange_base_url);
    391         assert_eq!(req.wtid, tx.wtid);
    392         assert_eq!(first.timestamp, tx.timestamp);
    393         assert_eq!(req.metadata, tx.metadata);
    394         assert_eq!(credit_account, &tx.credit_account);
    395 
    396         // Check page
    397         let list = wire_gateway
    398             .get("/transfers?limit=-1")
    399             .await
    400             .assert_ok_json::<TransferList>();
    401         let tx = &list.transfers[0];
    402         assert_eq!(first.row_id, tx.row_id);
    403         assert_eq!(default_status, tx.status);
    404         assert_eq!(default_amount, tx.amount);
    405         assert_eq!(first.timestamp, tx.timestamp);
    406         assert_eq!(credit_account, &tx.credit_account);
    407     };
    408 
    409     let req = TransferRequest {
    410         request_uid,
    411         amount: default_amount,
    412         exchange_base_url: url("http://exchange.taler/"),
    413         metadata: None,
    414         wtid,
    415         credit_account: credit_account.clone(),
    416     };
    417     // Simple
    418     routine(&req).await;
    419     // With metadata
    420     wire_gateway
    421         .post("/transfer")
    422         .json(&TransferRequest {
    423             metadata: Some("changed".into()),
    424             ..req.clone()
    425         })
    426         .await
    427         .assert_error(ErrorCode::BANK_TRANSFER_REQUEST_UID_REUSED);
    428 
    429     let metadata_req = TransferRequest {
    430         request_uid: HashCode::rand(),
    431         wtid: ShortHashCode::rand(),
    432         metadata: Some("test:metadata".into()),
    433         ..req.clone()
    434     };
    435     routine(&metadata_req).await;
    436     wire_gateway
    437         .post("/transfer")
    438         .json(&TransferRequest {
    439             metadata: None,
    440             ..metadata_req
    441         })
    442         .await
    443         .assert_error(ErrorCode::BANK_TRANSFER_REQUEST_UID_REUSED);
    444 
    445     // Check create transfer errors
    446     {
    447         // Check request uid reuse
    448         wire_gateway
    449             .post("/transfer")
    450             .json(json!(valid_req + {
    451                 "wtid": ShortHashCode::rand()
    452             }))
    453             .await
    454             .assert_error(ErrorCode::BANK_TRANSFER_REQUEST_UID_REUSED);
    455         // Check wtid reuse
    456         wire_gateway
    457             .post("/transfer")
    458             .json(json!(valid_req + {
    459                 "request_uid": HashCode::rand(),
    460             }))
    461             .await
    462             .assert_error(ErrorCode::BANK_TRANSFER_WTID_REUSED);
    463 
    464         // Check currency mismatch
    465         wire_gateway
    466             .post("/transfer")
    467             .json(json!(valid_req + {
    468                 "amount": "BAD:42"
    469             }))
    470             .await
    471             .assert_error(ErrorCode::GENERIC_CURRENCY_MISMATCH);
    472 
    473         // Base Base32
    474         wire_gateway
    475             .post("/transfer")
    476             .json(json!(valid_req + {
    477                 "wtid": "I love chocolate"
    478             }))
    479             .await
    480             .assert_error(ErrorCode::GENERIC_JSON_INVALID);
    481         wire_gateway
    482             .post("/transfer")
    483             .json(json!(valid_req + {
    484                 "wtid": Base32::<31>::rand()
    485             }))
    486             .await
    487             .assert_error(ErrorCode::GENERIC_JSON_INVALID);
    488         wire_gateway
    489             .post("/transfer")
    490             .json(json!(valid_req + {
    491                 "request_uid": "I love chocolate"
    492             }))
    493             .await
    494             .assert_error(ErrorCode::GENERIC_JSON_INVALID);
    495         wire_gateway
    496             .post("/transfer")
    497             .json(json!(valid_req + {
    498                 "request_uid": Base32::<65>::rand()
    499             }))
    500             .await
    501             .assert_error(ErrorCode::GENERIC_JSON_INVALID);
    502 
    503         // Missing receiver-name
    504         let res = wire_gateway
    505             .post("/transfer")
    506             .json(json!(valid_req + {
    507                 "credit_account": credit_account.as_ref().as_str().split('?').next().unwrap()
    508             }))
    509             .await;
    510         if !res.status.is_success() {
    511             res.assert_error(ErrorCode::GENERIC_PAYTO_URI_MALFORMED);
    512         }
    513 
    514         // Unsupported payto kind
    515         wire_gateway
    516             .post("/transfer")
    517             .json(json!(valid_req + { "credit_account": *UNKNOWN }))
    518             .await
    519             .assert_error(ErrorCode::GENERIC_PAYTO_URI_MALFORMED);
    520         // Malformed payto
    521         wire_gateway
    522             .post("/transfer")
    523             .json(json!(valid_req + { "credit_account": "http://email@test.com" }))
    524             .await
    525             .assert_error(ErrorCode::GENERIC_JSON_INVALID);
    526 
    527         // Bad base URL
    528         for base_url in [
    529             "not-a-url",
    530             "file://not.http.com/",
    531             "no.transport.com/",
    532             "https://not.a/base/url",
    533         ] {
    534             wire_gateway
    535                 .post("/transfer")
    536                 .json(&json!(valid_req + { "exchange_base_url": base_url }))
    537                 .await
    538                 .assert_error(ErrorCode::GENERIC_JSON_INVALID);
    539         }
    540 
    541         // Malformed metadata
    542         for metadata in ["bad_id", "bad id", "bad@id.com", &"A".repeat(41)] {
    543             wire_gateway
    544                 .post("/transfer")
    545                 .json(&json!(valid_req + { "metadata": metadata }))
    546                 .await
    547                 .assert_error(ErrorCode::GENERIC_JSON_INVALID);
    548         }
    549     }
    550 
    551     // Check transfer by id errors
    552     {
    553         // Check unknown transaction
    554         wire_gateway
    555             .get("/transfers/42")
    556             .await
    557             .assert_error(ErrorCode::BANK_TRANSACTION_NOT_FOUND);
    558     }
    559 
    560     // Check transfer page
    561     {
    562         for _ in 0..4 {
    563             wire_gateway
    564                 .post("/transfer")
    565                 .json(&json!(valid_req + {
    566                     "request_uid": HashCode::rand(),
    567                     "wtid": ShortHashCode::rand(),
    568                 }))
    569                 .await
    570                 .assert_ok_json::<TransferResponse>();
    571         }
    572         {
    573             let list = wire_gateway
    574                 .get("/transfers")
    575                 .await
    576                 .assert_ok_json::<TransferList>();
    577             assert_eq!(list.transfers.len(), 6);
    578             assert_eq!(
    579                 list,
    580                 wire_gateway
    581                     .get(format!("/transfers?status={}", default_status.as_ref()))
    582                     .await
    583                     .assert_ok_json::<TransferList>()
    584             )
    585         }
    586 
    587         // Pagination test
    588         routine_pagination::<TransferList>(
    589             &wire_gateway.suffix("/transfers"),
    590             crate::tasks!({
    591                 wire_gateway
    592                     .post("/transfer")
    593                     .json(json!({
    594                         "request_uid": HashCode::rand(),
    595                         "amount": amount(format!("{currency}:0.1")),
    596                         "exchange_base_url": url("http://exchange.taler"),
    597                         "wtid": ShortHashCode::rand(),
    598                         "credit_account": credit_account,
    599                     }))
    600                     .await
    601                     .assert_ok_json::<TransferResponse>();
    602             }),
    603         )
    604         .await;
    605     }
    606 }
    607 
    608 async fn add_incoming_routine(
    609     wire_gateway: &Router,
    610     prepared_transfer: &Router,
    611     currency: &Currency,
    612     kind: IncomingType,
    613     debit_acount: &PaytoURI,
    614     credit_account: &PaytoURI,
    615 ) {
    616     let (path, key) = match kind {
    617         IncomingType::reserve => ("/admin/add-incoming", "reserve_pub"),
    618         IncomingType::kyc => ("/admin/add-kycauth", "account_pub"),
    619         IncomingType::map => ("/admin/add-mapped", "authorization_pub"),
    620     };
    621     let key_pair = Ed25519KeyPair::generate().unwrap();
    622     let pub_key = EddsaPublicKey::try_from(key_pair.public_key().as_ref()).unwrap();
    623     // Valid
    624     let req = RegistrationRequest {
    625         credit_account: credit_account.clone(),
    626         r#type: TransferType::reserve,
    627         recurrent: false,
    628         credit_amount: Amount::new(currency, 44, 0),
    629         alg: PublicKeyAlg::EdDSA,
    630         account_pub: pub_key,
    631         authorization_pub: pub_key,
    632         authorization_sig: EddsaSignature::ZEROED,
    633     }
    634     .signed(&key_pair);
    635 
    636     prepared_transfer
    637         .post("/registration")
    638         .json(&req)
    639         .await
    640         .assert_ok_json::<RegistrationResponse>();
    641     let valid_req = json!({
    642         "amount": format!("{currency}:44"),
    643         key: pub_key,
    644         "debit_account": debit_acount,
    645     });
    646 
    647     // Check OK
    648     wire_gateway.post(path).json(&valid_req).await.assert_ok();
    649 
    650     match kind {
    651         IncomingType::reserve => {
    652             // Trigger conflict due to reused reserve_pub
    653             wire_gateway
    654                 .post(path)
    655                 .json(&json!(valid_req + {
    656                     "amount": format!("{currency}:44.1"),
    657                 }))
    658                 .await
    659                 .assert_error(ErrorCode::BANK_DUPLICATE_RESERVE_PUB_SUBJECT)
    660         }
    661         IncomingType::kyc => {
    662             // Non conflict on reuse
    663             wire_gateway.post(path).json(&valid_req).await.assert_ok();
    664         }
    665         IncomingType::map => {
    666             // Trigger conflict due to reused authorization_pub
    667             wire_gateway
    668                 .post(path)
    669                 .json(&valid_req)
    670                 .await
    671                 .assert_error(ErrorCode::BANK_TRANSFER_MAPPING_REUSED);
    672             // Trigger conflict due to unknown authorization_pub
    673             wire_gateway
    674                 .post(path)
    675                 .json(&json!(valid_req + {
    676                    key: EddsaPublicKey::rand()
    677                 }))
    678                 .await
    679                 .assert_error(ErrorCode::BANK_TRANSFER_MAPPING_UNKNOWN);
    680         }
    681     }
    682 
    683     // Currency mismatch
    684     wire_gateway
    685         .post(path)
    686         .json(&json!(valid_req + { "amount": "BAD:33" }))
    687         .await
    688         .assert_error(ErrorCode::GENERIC_CURRENCY_MISMATCH);
    689 
    690     // Bad BASE32 reserve_pub
    691     wire_gateway
    692         .post(path)
    693         .json(json!(valid_req + { key: "I love chocolate" }))
    694         .await
    695         .assert_error(ErrorCode::GENERIC_JSON_INVALID);
    696     wire_gateway
    697         .post(path)
    698         .json(json!(valid_req + { key: Base32::<31>::rand() }))
    699         .await
    700         .assert_error(ErrorCode::GENERIC_JSON_INVALID);
    701 
    702     // Unsupported payto kind
    703     wire_gateway
    704         .post(path)
    705         .json(json!(valid_req + { "debit_account": *UNKNOWN }))
    706         .await
    707         .assert_error(ErrorCode::GENERIC_PAYTO_URI_MALFORMED);
    708 
    709     // Malformed payto
    710     wire_gateway
    711         .post(path)
    712         .json(json!(valid_req + { "debit_account": "http://email@test.com" }))
    713         .await
    714         .assert_error(ErrorCode::GENERIC_JSON_INVALID);
    715 }
    716 
    717 pub struct Tasks<F: AsyncFnMut(usize)> {
    718     pub len: usize,
    719     pub lambda: F,
    720 }
    721 
    722 #[macro_export]
    723 macro_rules! tasks {
    724     // Create new
    725     ( $( $(if $cond:expr =>)? $body:block ),* $(,)? ) => {
    726         $crate::tasks!(@build 0usize;
    727             $( $(if $cond =>)? $body ),*
    728         )
    729     };
    730 
    731     // Append to existing
    732     ( $existing:expr ; $( $(if $cond:expr =>)? $body:block ),* $(,)? ) => {{
    733         let mut existing = $existing;
    734         let mut extra = $crate::tasks![
    735             $( $(if $cond =>)? $body ),*
    736         ];
    737 
    738         $crate::routine::Tasks {
    739             len: existing.len + extra.len,
    740             lambda: async move |i: usize| {
    741                 if existing.len == 0 && extra.len == 0 {
    742                     return;
    743                 }
    744 
    745                 let i = i % (existing.len + extra.len);
    746 
    747                 if i < existing.len {
    748                     (existing.lambda)(i).await;
    749                 } else {
    750                     (extra.lambda)(i - existing.len).await;
    751                 }
    752             }
    753         }
    754     }};
    755 
    756     // Internal builder
    757     (@build $idx:expr; $( $(if $cond:expr =>)? $body:block ),* ) => {{
    758         let conditions = [ $( true $( && $cond )? ),* ];
    759         let len = conditions.iter().filter(|&&x| x).count();
    760 
    761         $crate::routine::Tasks {
    762             len,
    763             lambda: async move |i: usize| {
    764                 if len == 0 {
    765                     return;
    766                 }
    767 
    768                 let i = i % len;
    769                 let mut current = 0usize;
    770 
    771                 $crate::tasks!(
    772                     @dispatch i, current, conditions, 0usize;
    773                     $( $(if $cond =>)? $body ),*
    774                 );
    775 
    776                 let _ = (i, &mut current); // suppress lints
    777 
    778                 unreachable!()
    779             }
    780         }
    781     }};
    782 
    783     // Recursive dispatcher
    784     (@dispatch $i:expr, $current:ident, $conditions:ident, $idx:expr;) => {};
    785 
    786     (@dispatch
    787         $i:expr,
    788         $current:ident,
    789         $conditions:ident,
    790         $idx:expr;
    791         $(if $cond:expr =>)? $body:block
    792         $(, $($rest:tt)*)?
    793     ) => {{
    794         if $conditions[$idx] {
    795             if $i == $current {
    796                 (async $body).await;
    797                 return;
    798             }
    799             $current += 1;
    800         }
    801 
    802         $crate::tasks!(
    803             @dispatch
    804             $i,
    805             $current,
    806             $conditions,
    807             $idx + 1usize;
    808             $($($rest)*)?
    809         );
    810     }};
    811 }
    812 
    813 /// Test standard behavior of the revenue endpoints
    814 pub async fn revenue_routine(
    815     wire_gateway: &Router,
    816     revenue_api: &Router,
    817     debit_acount: &PaytoURI,
    818     register: Tasks<impl AsyncFnMut(usize)>,
    819     ignore: Tasks<impl AsyncFnMut(usize)>,
    820 ) {
    821     let currency = &get_currency(revenue_api).await;
    822     routine_history::<RevenueIncomingHistory>(
    823         &revenue_api.suffix("/history"),
    824         tasks!(register;
    825             {
    826                 wire_gateway
    827                         .post("/admin/add-incoming")
    828                         .json(json!({
    829                             "amount": format!("{currency}:1"),
    830                             "reserve_pub": EddsaPublicKey::rand(),
    831                             "debit_account": debit_acount,
    832                         }))
    833                         .await
    834                         .assert_ok_json::<TransferResponse>();
    835             },
    836             {
    837                 wire_gateway
    838                         .post("/admin/add-kycauth")
    839                         .json(json!({
    840                             "amount": format!("{currency}:2"),
    841                             "account_pub": EddsaPublicKey::rand(),
    842                             "debit_account": debit_acount,
    843                         }))
    844                         .await
    845                         .assert_ok_json::<TransferResponse>();
    846             }
    847         ),
    848         ignore,
    849     )
    850     .await;
    851 }
    852 
    853 /// Test standard behavior of the outgoing history endpoint
    854 pub async fn out_history_routine(
    855     wire_gateway: &Router,
    856     register: Tasks<impl AsyncFnMut(usize)>,
    857     ignore: Tasks<impl AsyncFnMut(usize)>,
    858 ) {
    859     routine_history::<OutgoingHistory>(&wire_gateway.suffix("/history/outgoing"), register, ignore)
    860         .await;
    861 }
    862 
    863 /// Test standard behavior of the incoming history endpoint
    864 pub async fn in_history_routine(
    865     wire_gateway: &Router,
    866     prepared_transfer: &Router,
    867     debit_account: &PaytoURI,
    868     credit_account: &PaytoURI,
    869     register: Tasks<impl AsyncFnMut(usize)>,
    870     ignored: Tasks<impl AsyncFnMut(usize)>,
    871 ) {
    872     let currency = &get_currency(wire_gateway).await;
    873     let mut key = Ed25519KeyPair::generate().unwrap();
    874 
    875     routine_history::<IncomingHistory>(
    876         &wire_gateway.suffix("/history/incoming"),
    877         tasks!(register;
    878             {
    879                 wire_gateway
    880                     .post("/admin/add-incoming")
    881                     .json(json!({
    882                         "amount": format!("{currency}:1"),
    883                         "reserve_pub": EddsaPublicKey::rand(),
    884                         "debit_account": debit_account,
    885                     }))
    886                     .await
    887                     .assert_ok_json::<TransferResponse>();
    888             },
    889             {
    890                 key = Ed25519KeyPair::generate().unwrap();
    891                 let auth_pub = EddsaPublicKey::try_from(key.public_key().as_ref()).unwrap();
    892                 let reserve_pub = EddsaPublicKey::rand();
    893                 let amount = Amount::new(currency, 2, 0);
    894                 prepared_transfer
    895                     .post("/registration")
    896                     .json(
    897                         RegistrationRequest {
    898                             credit_account: credit_account.clone(),
    899                             r#type: TransferType::reserve,
    900                             recurrent: true,
    901                             credit_amount: amount,
    902                             alg: PublicKeyAlg::EdDSA,
    903                             account_pub: reserve_pub,
    904                             authorization_pub: auth_pub,
    905                             authorization_sig: EddsaSignature::ZEROED,
    906                         }
    907                         .signed(&key)
    908                     )
    909                     .await
    910                     .assert_ok_json::<RegistrationResponse>();
    911                 wire_gateway
    912                     .post("/admin/add-mapped")
    913                     .json(json!({
    914                         "amount": amount,
    915                         "authorization_pub": auth_pub,
    916                         "debit_account": debit_account,
    917                     }))
    918                     .await
    919                     .assert_ok_json::<TransferResponse>();
    920                 wire_gateway
    921                     .post("/admin/add-mapped")
    922                     .json(json!({
    923                         "amount": amount,
    924                         "authorization_pub": auth_pub,
    925                         "debit_account": debit_account,
    926                     }))
    927                     .await
    928                     .assert_ok_json::<TransferResponse>();
    929             },
    930             {
    931                 let auth_pub = EddsaPublicKey::try_from(key.public_key().as_ref()).unwrap();
    932                 let reserve_pub = EddsaPublicKey::rand();
    933                 prepared_transfer
    934                     .post("/registration")
    935                     .json(
    936                         RegistrationRequest {
    937                             credit_account: credit_account.clone(),
    938                             r#type: TransferType::reserve,
    939                             recurrent: true,
    940                             credit_amount: Amount::new(currency, 3, 0),
    941                             alg: PublicKeyAlg::EdDSA,
    942                             account_pub: reserve_pub,
    943                             authorization_pub: auth_pub,
    944                             authorization_sig: EddsaSignature::ZEROED,
    945                         }
    946                         .signed(&key)
    947                     )
    948                     .await
    949                     .assert_ok_json::<RegistrationResponse>();
    950             },
    951             {
    952                 wire_gateway
    953                     .post("/admin/add-kycauth")
    954                     .json(json!({
    955                         "amount": format!("{currency}:4"),
    956                         "account_pub": EddsaPublicKey::rand(),
    957                         "debit_account": debit_account,
    958                     }))
    959                     .await
    960                     .assert_ok_json::<TransferResponse>();
    961             },
    962             {
    963                 key = Ed25519KeyPair::generate().unwrap();
    964                 let auth_pub = EddsaPublicKey::try_from(key.public_key().as_ref()).unwrap();
    965                 let account_pub = EddsaPublicKey::rand();
    966                 let amount = Amount::new(currency, 5, 0);
    967                 prepared_transfer
    968                     .post("/registration")
    969                     .json(
    970                         RegistrationRequest {
    971                             credit_account: credit_account.clone(),
    972                             r#type: TransferType::kyc,
    973                             recurrent: true,
    974                             credit_amount: amount,
    975                             alg: PublicKeyAlg::EdDSA,
    976                             account_pub,
    977                             authorization_pub: auth_pub,
    978                             authorization_sig: EddsaSignature::ZEROED,
    979                         }
    980                         .signed(&key)
    981                     )
    982                     .await
    983                     .assert_ok_json::<RegistrationResponse>();
    984                 wire_gateway
    985                     .post("/admin/add-mapped")
    986                     .json(json!({
    987                         "amount": amount,
    988                         "authorization_pub": auth_pub,
    989                         "debit_account": debit_account,
    990                     }))
    991                     .await
    992                     .assert_ok_json::<TransferResponse>();
    993                 wire_gateway
    994                     .post("/admin/add-mapped")
    995                     .json(json!({
    996                         "amount": amount,
    997                         "authorization_pub": auth_pub,
    998                         "debit_account": debit_account,
    999                     }))
   1000                     .await
   1001                     .assert_ok_json::<TransferResponse>();
   1002             },
   1003             {
   1004                 let auth_pub = EddsaPublicKey::try_from(key.public_key().as_ref()).unwrap();
   1005                 let account_pub = EddsaPublicKey::rand();
   1006                 prepared_transfer
   1007                     .post("/registration")
   1008                     .json(
   1009                         RegistrationRequest {
   1010                             credit_account: credit_account.clone(),
   1011                             r#type: TransferType::kyc,
   1012                             recurrent: true,
   1013                             credit_amount: Amount::new(currency, 6, 0),
   1014                             alg: PublicKeyAlg::EdDSA,
   1015                             account_pub,
   1016                             authorization_pub: auth_pub,
   1017                             authorization_sig: EddsaSignature::ZEROED,
   1018                         }
   1019                         .signed(&key)
   1020                     )
   1021                     .await
   1022                     .assert_ok_json::<RegistrationResponse>();
   1023             }
   1024         ),
   1025         ignored,
   1026     )
   1027     .await;
   1028 }
   1029 
   1030 /// Test standard behavior of the admin add incoming endpoints
   1031 pub async fn admin_add_incoming_routine(
   1032     wire_gateway: &Router,
   1033     prepared_transfer: &Router,
   1034     debit_acount: &PaytoURI,
   1035     credit_account: &PaytoURI,
   1036 ) {
   1037     let currency = &get_currency(wire_gateway).await;
   1038     for kind in IncomingType::entries {
   1039         add_incoming_routine(
   1040             wire_gateway,
   1041             prepared_transfer,
   1042             currency,
   1043             *kind,
   1044             debit_acount,
   1045             credit_account,
   1046         )
   1047         .await;
   1048     }
   1049 }
   1050 
   1051 #[derive(Debug, PartialEq, Eq)]
   1052 pub enum Status {
   1053     Simple,
   1054     Pending,
   1055     Bounced,
   1056     Incomplete,
   1057     Reserve(EddsaPublicKey),
   1058     Kyc(EddsaPublicKey),
   1059 }
   1060 
   1061 /// Test standard registration behavior of the registration endpoints
   1062 pub async fn registration_routine<F1: Future<Output = Vec<Status>>>(
   1063     wire_gateway: &Router,
   1064     prepared_transfer: &Router,
   1065     debit_acount: &PaytoURI,
   1066     credit_account: &PaytoURI,
   1067     unknown_account: &PaytoURI,
   1068     mut in_status: impl FnMut() -> F1,
   1069 ) {
   1070     pub use Status::*;
   1071     let mut check_in = async |state: &[Status]| {
   1072         let current = in_status().await;
   1073         pretty_assertions::assert_eq!(state, current);
   1074     };
   1075 
   1076     let currency = &get_currency(wire_gateway).await;
   1077     let amount = amount(format!("{currency}:42"));
   1078     let key_pair1 = Ed25519KeyPair::generate().unwrap();
   1079     let auth_pub1 = EddsaPublicKey::try_from(key_pair1.public_key().as_ref()).unwrap();
   1080     let req = RegistrationRequest {
   1081         credit_account: credit_account.clone(),
   1082         r#type: TransferType::reserve,
   1083         recurrent: false,
   1084         credit_amount: amount,
   1085         alg: PublicKeyAlg::EdDSA,
   1086         account_pub: auth_pub1,
   1087         authorization_pub: auth_pub1,
   1088         authorization_sig: EddsaSignature::ZEROED,
   1089     };
   1090 
   1091     let register = async |auth_pub: &EddsaPublicKey| {
   1092         wire_gateway
   1093             .post("/admin/add-mapped")
   1094             .json(json!({
   1095                 "amount": format!("{currency}:42"),
   1096                 "authorization_pub": auth_pub,
   1097                 "debit_account": debit_acount,
   1098             }))
   1099             .await
   1100     };
   1101 
   1102     /* ----- Registration ----- */
   1103     let routine = async |ty: TransferType,
   1104                          account_pub: EddsaPublicKey,
   1105                          recurrent: bool,
   1106                          fmt: IncomingType| {
   1107         let req = RegistrationRequest {
   1108             r#type: ty,
   1109             account_pub,
   1110             recurrent,
   1111             ..req.clone()
   1112         }
   1113         .signed(&key_pair1);
   1114         // Valid
   1115         let res = prepared_transfer
   1116             .post("/registration")
   1117             .json(&req)
   1118             .await
   1119             .assert_ok_json::<RegistrationResponse>();
   1120 
   1121         // Idempotent
   1122         assert_eq!(
   1123             res,
   1124             prepared_transfer
   1125                 .post("/registration")
   1126                 .json(&req)
   1127                 .await
   1128                 .assert_ok_json::<RegistrationResponse>()
   1129         );
   1130 
   1131         assert!(!res.subjects.is_empty());
   1132 
   1133         for sub in res.subjects {
   1134             if let TransferSubject::Simple { subject, .. } = sub {
   1135                 assert_eq!(subject, fmt_in_subject(fmt, &auth_pub1).to_string());
   1136             };
   1137         }
   1138     };
   1139     for ty in [TransferType::reserve, TransferType::kyc] {
   1140         routine(ty, auth_pub1, false, ty.into()).await;
   1141         routine(ty, auth_pub1, true, IncomingType::map).await;
   1142     }
   1143 
   1144     let acc_pub1 = EddsaPublicKey::rand();
   1145     for ty in [TransferType::reserve, TransferType::kyc] {
   1146         routine(ty, acc_pub1, false, IncomingType::map).await;
   1147         routine(ty, acc_pub1, true, IncomingType::map).await;
   1148     }
   1149 
   1150     // Bad signature
   1151     prepared_transfer
   1152         .post("/registration")
   1153         .json(&req)
   1154         .await
   1155         .assert_error(ErrorCode::BANK_BAD_SIGNATURE);
   1156 
   1157     // Unknown account
   1158     prepared_transfer
   1159         .post("/registration")
   1160         .json(
   1161             RegistrationRequest {
   1162                 r#credit_account: unknown_account.clone(),
   1163                 ..req.clone()
   1164             }
   1165             .signed(&key_pair1),
   1166         )
   1167         .await
   1168         .assert_error(ErrorCode::BANK_UNKNOWN_CREDITOR);
   1169 
   1170     // Unsupported payto kind
   1171     prepared_transfer
   1172         .post("/registration")
   1173         .json(
   1174             RegistrationRequest {
   1175                 r#credit_account: UNKNOWN.clone(),
   1176                 ..req.clone()
   1177             }
   1178             .signed(&key_pair1),
   1179         )
   1180         .await
   1181         .assert_error(ErrorCode::GENERIC_PAYTO_URI_MALFORMED);
   1182 
   1183     // Malformed payto
   1184     prepared_transfer
   1185         .post("/registration")
   1186         .json(
   1187             RegistrationRequest {
   1188                 r#credit_account: unsafe { PaytoURI::from_raw("http://email@test.com") },
   1189                 ..req.clone()
   1190             }
   1191             .signed(&key_pair1),
   1192         )
   1193         .await
   1194         .assert_error(ErrorCode::GENERIC_JSON_INVALID);
   1195 
   1196     // Reserve pub reuse
   1197     prepared_transfer
   1198         .post("/registration")
   1199         .json(
   1200             RegistrationRequest {
   1201                 account_pub: acc_pub1,
   1202                 ..req.clone()
   1203             }
   1204             .signed(&key_pair1),
   1205         )
   1206         .await
   1207         .assert_ok_json::<RegistrationResponse>();
   1208     {
   1209         let key_pair = Ed25519KeyPair::generate().unwrap();
   1210         let auth_pub = EddsaPublicKey::try_from(key_pair.public_key().as_ref()).unwrap();
   1211         prepared_transfer
   1212             .post("/registration")
   1213             .json(
   1214                 RegistrationRequest {
   1215                     account_pub: acc_pub1,
   1216                     authorization_pub: auth_pub,
   1217                     ..req.clone()
   1218                 }
   1219                 .signed(&key_pair),
   1220             )
   1221             .await
   1222             .assert_error(ErrorCode::BANK_DUPLICATE_RESERVE_PUB_SUBJECT);
   1223     }
   1224 
   1225     // Non recurrent accept one then bounce
   1226     prepared_transfer
   1227         .post("/registration")
   1228         .json(
   1229             RegistrationRequest {
   1230                 account_pub: acc_pub1,
   1231                 ..req.clone()
   1232             }
   1233             .signed(&key_pair1),
   1234         )
   1235         .await
   1236         .assert_ok_json::<RegistrationResponse>();
   1237     register(&auth_pub1)
   1238         .await
   1239         .assert_ok_json::<TransferResponse>();
   1240     check_in(&[Reserve(acc_pub1)]).await;
   1241     register(&auth_pub1)
   1242         .await
   1243         .assert_error(ErrorCode::BANK_TRANSFER_MAPPING_REUSED);
   1244 
   1245     // Again without using mapping
   1246     let acc_pub2 = EddsaPublicKey::rand();
   1247     prepared_transfer
   1248         .post("/registration")
   1249         .json(
   1250             RegistrationRequest {
   1251                 account_pub: acc_pub2,
   1252                 ..req.clone()
   1253             }
   1254             .signed(&key_pair1),
   1255         )
   1256         .await
   1257         .assert_ok_json::<RegistrationResponse>();
   1258     wire_gateway
   1259         .post("/admin/add-incoming")
   1260         .json(json!({
   1261             "amount": amount,
   1262             "reserve_pub": acc_pub2,
   1263             "debit_account": debit_acount,
   1264         }))
   1265         .await
   1266         .assert_ok();
   1267     register(&auth_pub1)
   1268         .await
   1269         .assert_error(ErrorCode::BANK_TRANSFER_MAPPING_REUSED);
   1270     check_in(&[Reserve(acc_pub1), Reserve(acc_pub2)]).await;
   1271 
   1272     // Recurrent accept one and delay others
   1273     let acc_pub3 = EddsaPublicKey::rand();
   1274     prepared_transfer
   1275         .post("/registration")
   1276         .json(
   1277             RegistrationRequest {
   1278                 account_pub: acc_pub3,
   1279                 recurrent: true,
   1280                 ..req.clone()
   1281             }
   1282             .signed(&key_pair1),
   1283         )
   1284         .await
   1285         .assert_ok_json::<RegistrationResponse>();
   1286     for _ in 0..5 {
   1287         register(&auth_pub1)
   1288             .await
   1289             .assert_ok_json::<TransferResponse>();
   1290     }
   1291     check_in(&[
   1292         Reserve(acc_pub1),
   1293         Reserve(acc_pub2),
   1294         Reserve(acc_pub3),
   1295         Pending,
   1296         Pending,
   1297         Pending,
   1298         Pending,
   1299     ])
   1300     .await;
   1301 
   1302     // Complete pending on recurrent update
   1303     let acc_pub4 = EddsaPublicKey::rand();
   1304     prepared_transfer
   1305         .post("/registration")
   1306         .json(
   1307             RegistrationRequest {
   1308                 r#type: TransferType::kyc,
   1309                 account_pub: acc_pub4,
   1310                 recurrent: true,
   1311                 ..req.clone()
   1312             }
   1313             .signed(&key_pair1),
   1314         )
   1315         .await
   1316         .assert_ok_json::<RegistrationResponse>();
   1317     prepared_transfer
   1318         .post("/registration")
   1319         .json(
   1320             RegistrationRequest {
   1321                 account_pub: acc_pub4,
   1322                 recurrent: true,
   1323                 ..req.clone()
   1324             }
   1325             .signed(&key_pair1),
   1326         )
   1327         .await
   1328         .assert_ok_json::<RegistrationResponse>();
   1329     check_in(&[
   1330         Reserve(acc_pub1),
   1331         Reserve(acc_pub2),
   1332         Reserve(acc_pub3),
   1333         Kyc(acc_pub4),
   1334         Reserve(acc_pub4),
   1335         Pending,
   1336         Pending,
   1337     ])
   1338     .await;
   1339 
   1340     // Kyc key reuse keep pending ones
   1341     wire_gateway
   1342         .post("/admin/add-kycauth")
   1343         .json(json!({
   1344             "amount": amount,
   1345             "account_pub": acc_pub4,
   1346             "debit_account": debit_acount,
   1347         }))
   1348         .await
   1349         .assert_ok_json::<TransferResponse>();
   1350     check_in(&[
   1351         Reserve(acc_pub1),
   1352         Reserve(acc_pub2),
   1353         Reserve(acc_pub3),
   1354         Kyc(acc_pub4),
   1355         Reserve(acc_pub4),
   1356         Pending,
   1357         Pending,
   1358         Kyc(acc_pub4),
   1359     ])
   1360     .await;
   1361 
   1362     // Switching to non recurrent cancel pending
   1363     let auth_pair = Ed25519KeyPair::generate().unwrap();
   1364     let auth_pub2 = EddsaPublicKey::try_from(auth_pair.public_key().as_ref()).unwrap();
   1365     prepared_transfer
   1366         .post("/registration")
   1367         .json(
   1368             RegistrationRequest {
   1369                 account_pub: auth_pub2,
   1370                 authorization_pub: auth_pub2,
   1371                 recurrent: true,
   1372                 ..req.clone()
   1373             }
   1374             .signed(&auth_pair),
   1375         )
   1376         .await
   1377         .assert_ok_json::<RegistrationResponse>();
   1378     for _ in 0..3 {
   1379         register(&auth_pub2)
   1380             .await
   1381             .assert_ok_json::<TransferResponse>();
   1382     }
   1383     check_in(&[
   1384         Reserve(acc_pub1),
   1385         Reserve(acc_pub2),
   1386         Reserve(acc_pub3),
   1387         Kyc(acc_pub4),
   1388         Reserve(acc_pub4),
   1389         Pending,
   1390         Pending,
   1391         Kyc(acc_pub4),
   1392         Reserve(auth_pub2),
   1393         Pending,
   1394         Pending,
   1395     ])
   1396     .await;
   1397     prepared_transfer
   1398         .post("/registration")
   1399         .json(
   1400             RegistrationRequest {
   1401                 r#type: TransferType::kyc,
   1402                 account_pub: auth_pub2,
   1403                 authorization_pub: auth_pub2,
   1404                 recurrent: false,
   1405                 ..req.clone()
   1406             }
   1407             .signed(&auth_pair),
   1408         )
   1409         .await
   1410         .assert_ok_json::<RegistrationResponse>();
   1411     check_in(&[
   1412         Reserve(acc_pub1),
   1413         Reserve(acc_pub2),
   1414         Reserve(acc_pub3),
   1415         Kyc(acc_pub4),
   1416         Reserve(acc_pub4),
   1417         Pending,
   1418         Pending,
   1419         Kyc(acc_pub4),
   1420         Reserve(auth_pub2),
   1421         Bounced,
   1422         Bounced,
   1423     ])
   1424     .await;
   1425 
   1426     // Recurrent reserve simple subject
   1427     let acc_pub5 = EddsaPublicKey::rand();
   1428     prepared_transfer
   1429         .post("/registration")
   1430         .json(
   1431             RegistrationRequest {
   1432                 account_pub: acc_pub5,
   1433                 authorization_pub: auth_pub2,
   1434                 recurrent: true,
   1435                 ..req.clone()
   1436             }
   1437             .signed(&auth_pair),
   1438         )
   1439         .await
   1440         .assert_ok_json::<RegistrationResponse>();
   1441     wire_gateway
   1442         .post("/admin/add-incoming")
   1443         .json(json!({
   1444             "amount": amount,
   1445             "reserve_pub": acc_pub5,
   1446             "debit_account": debit_acount,
   1447         }))
   1448         .await
   1449         .assert_ok();
   1450     register(&auth_pub2)
   1451         .await
   1452         .assert_ok_json::<TransferResponse>();
   1453     check_in(&[
   1454         Reserve(acc_pub1),
   1455         Reserve(acc_pub2),
   1456         Reserve(acc_pub3),
   1457         Kyc(acc_pub4),
   1458         Reserve(acc_pub4),
   1459         Pending,
   1460         Pending,
   1461         Kyc(acc_pub4),
   1462         Reserve(auth_pub2),
   1463         Bounced,
   1464         Bounced,
   1465         Reserve(acc_pub5),
   1466         Pending,
   1467     ])
   1468     .await;
   1469 
   1470     // Recurrent kyc simple subject
   1471     prepared_transfer
   1472         .post("/registration")
   1473         .json(
   1474             RegistrationRequest {
   1475                 r#type: TransferType::kyc,
   1476                 account_pub: acc_pub5,
   1477                 authorization_pub: auth_pub2,
   1478                 ..req.clone()
   1479             }
   1480             .signed(&auth_pair),
   1481         )
   1482         .await
   1483         .assert_ok_json::<RegistrationResponse>();
   1484     prepared_transfer
   1485         .post("/registration")
   1486         .json(
   1487             RegistrationRequest {
   1488                 r#type: TransferType::kyc,
   1489                 account_pub: acc_pub5,
   1490                 authorization_pub: auth_pub2,
   1491                 recurrent: true,
   1492                 ..req.clone()
   1493             }
   1494             .signed(&auth_pair),
   1495         )
   1496         .await
   1497         .assert_ok_json::<RegistrationResponse>();
   1498     let pair = Ed25519KeyPair::generate().unwrap();
   1499     prepared_transfer
   1500         .post("/registration")
   1501         .json(
   1502             RegistrationRequest {
   1503                 r#type: TransferType::kyc,
   1504                 account_pub: acc_pub5,
   1505                 authorization_pub: EddsaPublicKey::try_from(pair.public_key().as_ref()).unwrap(),
   1506                 recurrent: true,
   1507                 ..req.clone()
   1508             }
   1509             .signed(&pair),
   1510         )
   1511         .await
   1512         .assert_ok_json::<RegistrationResponse>();
   1513     wire_gateway
   1514         .post("/admin/add-kycauth")
   1515         .json(json!({
   1516             "amount": amount,
   1517             "account_pub": acc_pub5,
   1518             "debit_account": debit_acount,
   1519         }))
   1520         .await
   1521         .assert_ok();
   1522     for _ in 0..2 {
   1523         register(&auth_pub2)
   1524             .await
   1525             .assert_ok_json::<TransferResponse>();
   1526     }
   1527     check_in(&[
   1528         Reserve(acc_pub1),
   1529         Reserve(acc_pub2),
   1530         Reserve(acc_pub3),
   1531         Kyc(acc_pub4),
   1532         Reserve(acc_pub4),
   1533         Pending,
   1534         Pending,
   1535         Kyc(acc_pub4),
   1536         Reserve(auth_pub2),
   1537         Bounced,
   1538         Bounced,
   1539         Reserve(acc_pub5),
   1540         Bounced,
   1541         Kyc(acc_pub5),
   1542         Kyc(acc_pub5),
   1543         Pending,
   1544     ])
   1545     .await;
   1546 
   1547     // Kyc without using mapping
   1548 
   1549     /* ----- Unregistration ----- */
   1550     let un_req = Unregistration {
   1551         timestamp: TalerTimestamp::Timestamp(Timestamp::now()),
   1552         authorization_pub: auth_pub2,
   1553         authorization_sig: EddsaSignature::ZEROED,
   1554     };
   1555     let signed = un_req.clone().signed(&auth_pair);
   1556 
   1557     // Delete
   1558     prepared_transfer
   1559         .post("/unregistration")
   1560         .json(&signed)
   1561         .await
   1562         .assert_no_content();
   1563 
   1564     // Check bounce pending on deletion
   1565     check_in(&[
   1566         Reserve(acc_pub1),
   1567         Reserve(acc_pub2),
   1568         Reserve(acc_pub3),
   1569         Kyc(acc_pub4),
   1570         Reserve(acc_pub4),
   1571         Pending,
   1572         Pending,
   1573         Kyc(acc_pub4),
   1574         Reserve(auth_pub2),
   1575         Bounced,
   1576         Bounced,
   1577         Reserve(acc_pub5),
   1578         Bounced,
   1579         Kyc(acc_pub5),
   1580         Kyc(acc_pub5),
   1581         Bounced,
   1582     ])
   1583     .await;
   1584 
   1585     // Idempotent
   1586     prepared_transfer
   1587         .post("/unregistration")
   1588         .json(&signed)
   1589         .await
   1590         .assert_error(ErrorCode::BANK_TRANSACTION_NOT_FOUND);
   1591 
   1592     // Bad signature
   1593     prepared_transfer
   1594         .post("/unregistration")
   1595         .json(&un_req)
   1596         .await
   1597         .assert_error(ErrorCode::BANK_BAD_SIGNATURE);
   1598 
   1599     // Old timestamp
   1600     prepared_transfer
   1601         .post("/unregistration")
   1602         .json(
   1603             Unregistration {
   1604                 timestamp: TalerTimestamp::Timestamp(
   1605                     Timestamp::now() - SignedDuration::from_mins(10),
   1606                 ),
   1607                 ..un_req.clone()
   1608             }
   1609             .signed(&auth_pair),
   1610         )
   1611         .await
   1612         .assert_error(ErrorCode::BANK_OLD_TIMESTAMP);
   1613 
   1614     // Never timestamp
   1615     prepared_transfer
   1616         .post("/unregistration")
   1617         .json(
   1618             Unregistration {
   1619                 timestamp: TalerTimestamp::Never,
   1620                 ..un_req.clone()
   1621             }
   1622             .signed(&auth_pair),
   1623         )
   1624         .await
   1625         .assert_error(ErrorCode::BANK_OLD_TIMESTAMP);
   1626 
   1627     // Future timestamp
   1628     prepared_transfer
   1629         .post("/unregistration")
   1630         .json(
   1631             Unregistration {
   1632                 timestamp: TalerTimestamp::Timestamp(
   1633                     Timestamp::now() + SignedDuration::from_mins(1),
   1634                 ),
   1635                 ..un_req.clone()
   1636             }
   1637             .signed(&auth_pair),
   1638         )
   1639         .await
   1640         .assert_error(ErrorCode::BANK_OLD_TIMESTAMP);
   1641 
   1642     // A KYC payment with a reserve's account key must not consume that
   1643     // reserve's prepared registration. The mapped reserve is still valid.
   1644     let reserve_pair = Ed25519KeyPair::generate().unwrap();
   1645     let authorization_pub = EddsaPublicKey::try_from(reserve_pair.public_key().as_ref()).unwrap();
   1646     let reserve_pub = EddsaPublicKey::rand();
   1647     prepared_transfer
   1648         .post("/registration")
   1649         .json(
   1650             RegistrationRequest {
   1651                 account_pub: reserve_pub,
   1652                 authorization_pub,
   1653                 ..req.clone()
   1654             }
   1655             .signed(&reserve_pair),
   1656         )
   1657         .await
   1658         .assert_ok_json::<RegistrationResponse>();
   1659     wire_gateway
   1660         .post("/admin/add-kycauth")
   1661         .json(json!({
   1662             "amount": amount,
   1663             "account_pub": reserve_pub,
   1664             "debit_account": debit_acount,
   1665         }))
   1666         .await
   1667         .assert_ok_json::<TransferResponse>();
   1668     register(&authorization_pub)
   1669         .await
   1670         .assert_ok_json::<TransferResponse>();
   1671 
   1672     // Re-registering the same key with changed signed information must
   1673     // replace the signature retained for the next mapped transfer.
   1674     let replacement_pair = Ed25519KeyPair::generate().unwrap();
   1675     let replacement_auth_pub =
   1676         EddsaPublicKey::try_from(replacement_pair.public_key().as_ref()).unwrap();
   1677     let replacement_reserve_pub = EddsaPublicKey::rand();
   1678     let original_registration = RegistrationRequest {
   1679         account_pub: replacement_reserve_pub,
   1680         authorization_pub: replacement_auth_pub,
   1681         ..req.clone()
   1682     }
   1683     .signed(&replacement_pair);
   1684     prepared_transfer
   1685         .post("/registration")
   1686         .json(&original_registration)
   1687         .await
   1688         .assert_ok_json::<RegistrationResponse>();
   1689     let replacement_registration = RegistrationRequest {
   1690         credit_amount: taler_common::types::amount::amount(format!("{currency}:43")),
   1691         ..original_registration
   1692     }
   1693     .signed(&replacement_pair);
   1694     prepared_transfer
   1695         .post("/registration")
   1696         .json(&replacement_registration)
   1697         .await
   1698         .assert_ok_json::<RegistrationResponse>();
   1699     let before_replacement_transfer =
   1700         latest_id::<IncomingHistory>(&wire_gateway.suffix("/history/incoming")).await;
   1701     register(&replacement_auth_pub)
   1702         .await
   1703         .assert_ok_json::<TransferResponse>();
   1704     let latest = wire_gateway
   1705         .get(format!(
   1706             "/history/incoming?limit=1&offset={before_replacement_transfer}"
   1707         ))
   1708         .await
   1709         .assert_ok_json::<IncomingHistory>();
   1710     assert!(matches!(
   1711         &latest.incoming_transactions[0],
   1712         IncomingBankTransaction::Reserve {
   1713             reserve_pub,
   1714             authorization_pub: Some(auth_pub),
   1715             authorization_sig: Some(auth_sig),
   1716             ..
   1717         } if *reserve_pub == replacement_reserve_pub
   1718             && *auth_pub == replacement_auth_pub
   1719             && replacement_registration.verify(auth_pub, auth_sig)
   1720     ));
   1721 
   1722     /* ----- API ----- */
   1723 
   1724     let history: Vec<_> = wire_gateway
   1725         .get("/history/incoming?limit=20")
   1726         .await
   1727         .assert_ok_json::<IncomingHistory>()
   1728         .incoming_transactions
   1729         .into_iter()
   1730         .map(|tx| {
   1731             let (acc_pub, auth_pub, auth_sig) = match tx {
   1732                 IncomingBankTransaction::Reserve {
   1733                     reserve_pub,
   1734                     authorization_pub,
   1735                     authorization_sig,
   1736                     ..
   1737                 } => (reserve_pub, authorization_pub, authorization_sig),
   1738                 IncomingBankTransaction::Wad { .. } => unreachable!(),
   1739                 IncomingBankTransaction::Kyc {
   1740                     account_pub,
   1741                     authorization_pub,
   1742                     authorization_sig,
   1743                     ..
   1744                 } => (account_pub, authorization_pub, authorization_sig),
   1745             };
   1746             assert_eq!(auth_pub.is_some(), auth_sig.is_some());
   1747             (acc_pub, auth_pub)
   1748         })
   1749         .collect();
   1750     pretty_assertions::assert_eq!(
   1751         history,
   1752         [
   1753             (acc_pub1, Some(auth_pub1)),
   1754             (acc_pub2, None),
   1755             (acc_pub3, Some(auth_pub1)),
   1756             (acc_pub4, Some(auth_pub1)),
   1757             (acc_pub4, Some(auth_pub1)),
   1758             (acc_pub4, None),
   1759             (auth_pub2, Some(auth_pub2)),
   1760             (acc_pub5, None),
   1761             (acc_pub5, None),
   1762             (acc_pub5, Some(auth_pub2)),
   1763             (reserve_pub, None),
   1764             (reserve_pub, Some(authorization_pub)),
   1765             (replacement_reserve_pub, Some(replacement_auth_pub)),
   1766         ]
   1767     )
   1768 }