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 }