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