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