routine.rs (49041B)
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 kyc: bool, 780 register: Tasks<impl AsyncFnMut(usize)>, 781 ignore: Tasks<impl AsyncFnMut(usize)>, 782 ) { 783 let currency = &get_currency(revenue_api).await; 784 routine_history::<RevenueIncomingHistory>( 785 &revenue_api.suffix("/history"), 786 tasks!(register; 787 { 788 wire_gateway 789 .post("/admin/add-incoming") 790 .json(json!({ 791 "amount": format!("{currency}:1"), 792 "reserve_pub": EddsaPublicKey::rand(), 793 "debit_account": debit_acount, 794 })) 795 .await 796 .assert_ok_json::<TransferResponse>(); 797 }, 798 if kyc => { 799 wire_gateway 800 .post("/admin/add-kycauth") 801 .json(json!({ 802 "amount": format!("{currency}:2"), 803 "account_pub": EddsaPublicKey::rand(), 804 "debit_account": debit_acount, 805 })) 806 .await 807 .assert_ok_json::<TransferResponse>(); 808 } 809 ), 810 ignore, 811 ) 812 .await; 813 } 814 815 /// Test standard behavior of the outgoing history endpoint 816 pub async fn out_history_routine( 817 wire_gateway: &Router, 818 register: Tasks<impl AsyncFnMut(usize)>, 819 ignore: Tasks<impl AsyncFnMut(usize)>, 820 ) { 821 routine_history::<OutgoingHistory>(&wire_gateway.suffix("/history/outgoing"), register, ignore) 822 .await; 823 } 824 825 /// Test standard behavior of the incoming history endpoint 826 pub async fn in_history_routine( 827 wire_gateway: &Router, 828 prepared_transfer: &Router, 829 debit_account: &PaytoURI, 830 credit_account: &PaytoURI, 831 kyc: bool, 832 register: Tasks<impl AsyncFnMut(usize)>, 833 ignored: Tasks<impl AsyncFnMut(usize)>, 834 ) { 835 let currency = &get_currency(wire_gateway).await; 836 let mut key = Ed25519KeyPair::generate().unwrap(); 837 838 routine_history::<IncomingHistory>( 839 &wire_gateway.suffix("/history/incoming"), 840 tasks!(register; 841 { 842 wire_gateway 843 .post("/admin/add-incoming") 844 .json(json!({ 845 "amount": format!("{currency}:1"), 846 "reserve_pub": EddsaPublicKey::rand(), 847 "debit_account": debit_account, 848 })) 849 .await 850 .assert_ok_json::<TransferResponse>(); 851 }, 852 { 853 key = Ed25519KeyPair::generate().unwrap(); 854 let auth_pub = EddsaPublicKey::try_from(key.public_key().as_ref()).unwrap(); 855 let reserve_pub = EddsaPublicKey::rand(); 856 let amount = format!("{currency}:2"); 857 prepared_transfer 858 .post("/registration") 859 .json(json!({ 860 "credit_account": credit_account, 861 "credit_amount": amount, 862 "type": "reserve", 863 "alg": "EdDSA", 864 "account_pub": reserve_pub, 865 "authorization_pub": auth_pub, 866 "authorization_sig": eddsa_sign(&key, reserve_pub.as_ref()), 867 "recurrent": true 868 })) 869 .await 870 .assert_ok_json::<RegistrationResponse>(); 871 wire_gateway 872 .post("/admin/add-mapped") 873 .json(json!({ 874 "amount": amount, 875 "authorization_pub": auth_pub, 876 "debit_account": debit_account, 877 })) 878 .await 879 .assert_ok_json::<TransferResponse>(); 880 wire_gateway 881 .post("/admin/add-mapped") 882 .json(json!({ 883 "amount": amount, 884 "authorization_pub": auth_pub, 885 "debit_account": debit_account, 886 })) 887 .await 888 .assert_ok_json::<TransferResponse>(); 889 }, 890 { 891 let auth_pub = EddsaPublicKey::try_from(key.public_key().as_ref()).unwrap(); 892 let reserve_pub = EddsaPublicKey::rand(); 893 prepared_transfer 894 .post("/registration") 895 .json(json!({ 896 "credit_account": credit_account, 897 "credit_amount": format!("{currency}:3"), 898 "type": "reserve", 899 "alg": "EdDSA", 900 "account_pub": reserve_pub, 901 "authorization_pub": auth_pub, 902 "authorization_sig": eddsa_sign(&key, reserve_pub.as_ref()), 903 "recurrent": true 904 })) 905 .await 906 .assert_ok_json::<RegistrationResponse>(); 907 }, 908 if kyc => { 909 wire_gateway 910 .post("/admin/add-kycauth") 911 .json(json!({ 912 "amount": format!("{currency}:4"), 913 "account_pub": EddsaPublicKey::rand(), 914 "debit_account": debit_account, 915 })) 916 .await 917 .assert_ok_json::<TransferResponse>(); 918 }, 919 if kyc => { 920 key = Ed25519KeyPair::generate().unwrap(); 921 let auth_pub = EddsaPublicKey::try_from(key.public_key().as_ref()).unwrap(); 922 let account_pub = EddsaPublicKey::rand(); 923 let amount = format!("{currency}:5"); 924 prepared_transfer 925 .post("/registration") 926 .json(json!({ 927 "credit_account": credit_account, 928 "credit_amount": amount, 929 "type": "kyc", 930 "alg": "EdDSA", 931 "account_pub": account_pub, 932 "authorization_pub": auth_pub, 933 "authorization_sig": eddsa_sign(&key, account_pub.as_ref()), 934 "recurrent": true 935 })) 936 .await 937 .assert_ok_json::<RegistrationResponse>(); 938 wire_gateway 939 .post("/admin/add-mapped") 940 .json(json!({ 941 "amount": amount, 942 "authorization_pub": auth_pub, 943 "debit_account": debit_account, 944 })) 945 .await 946 .assert_ok_json::<TransferResponse>(); 947 wire_gateway 948 .post("/admin/add-mapped") 949 .json(json!({ 950 "amount": amount, 951 "authorization_pub": auth_pub, 952 "debit_account": debit_account, 953 })) 954 .await 955 .assert_ok_json::<TransferResponse>(); 956 }, 957 if kyc => { 958 let auth_pub = EddsaPublicKey::try_from(key.public_key().as_ref()).unwrap(); 959 let account_pub = EddsaPublicKey::rand(); 960 prepared_transfer 961 .post("/registration") 962 .json(json!({ 963 "credit_account": credit_account, 964 "credit_amount": format!("{currency}:6"), 965 "type": "kyc", 966 "alg": "EdDSA", 967 "account_pub": account_pub, 968 "authorization_pub": auth_pub, 969 "authorization_sig": eddsa_sign(&key, account_pub.as_ref()), 970 "recurrent": true 971 })) 972 .await 973 .assert_ok_json::<RegistrationResponse>(); 974 } 975 ), 976 ignored, 977 ) 978 .await; 979 } 980 981 /// Test standard behavior of the admin add incoming endpoints 982 pub async fn admin_add_incoming_routine( 983 wire_gateway: &Router, 984 prepared_transfer: &Router, 985 debit_acount: &PaytoURI, 986 credit_account: &PaytoURI, 987 kyc: bool, 988 ) { 989 let currency = &get_currency(wire_gateway).await; 990 add_incoming_routine( 991 wire_gateway, 992 prepared_transfer, 993 currency, 994 IncomingType::reserve, 995 debit_acount, 996 credit_account, 997 ) 998 .await; 999 add_incoming_routine( 1000 wire_gateway, 1001 prepared_transfer, 1002 currency, 1003 IncomingType::map, 1004 debit_acount, 1005 credit_account, 1006 ) 1007 .await; 1008 if kyc { 1009 add_incoming_routine( 1010 wire_gateway, 1011 prepared_transfer, 1012 currency, 1013 IncomingType::kyc, 1014 debit_acount, 1015 credit_account, 1016 ) 1017 .await; 1018 } 1019 } 1020 1021 #[derive(Debug, PartialEq, Eq)] 1022 pub enum Status { 1023 Simple, 1024 Pending, 1025 Bounced, 1026 Incomplete, 1027 Reserve(EddsaPublicKey), 1028 Kyc(EddsaPublicKey), 1029 } 1030 1031 /// Test standard registration behavior of the registration endpoints 1032 pub async fn registration_routine<F1: Future<Output = Vec<Status>>>( 1033 wire_gateway: &Router, 1034 prepared_transfer: &Router, 1035 debit_acount: &PaytoURI, 1036 credit_account: &PaytoURI, 1037 unknown_account: &PaytoURI, 1038 mut in_status: impl FnMut() -> F1, 1039 ) { 1040 pub use Status::*; 1041 let mut check_in = async |state: &[Status]| { 1042 let current = in_status().await; 1043 assert_eq!(state, current); 1044 }; 1045 1046 let currency = &get_currency(wire_gateway).await; 1047 let amount = amount(format!("{currency}:42")); 1048 let key_pair1 = Ed25519KeyPair::generate().unwrap(); 1049 let auth_pub1 = EddsaPublicKey::try_from(key_pair1.public_key().as_ref()).unwrap(); 1050 let req = json!({ 1051 "credit_account": credit_account, 1052 "type": "reserve", 1053 "recurrent": false, 1054 "credit_amount": amount, 1055 "alg": "EdDSA", 1056 "account_pub": auth_pub1, 1057 "authorization_pub": auth_pub1, 1058 "authorization_sig": eddsa_sign(&key_pair1, auth_pub1.as_ref()), 1059 }); 1060 1061 let register = async |auth_pub: &EddsaPublicKey| { 1062 wire_gateway 1063 .post("/admin/add-mapped") 1064 .json(json!({ 1065 "amount": format!("{currency}:42"), 1066 "authorization_pub": auth_pub, 1067 "debit_account": debit_acount, 1068 })) 1069 .await 1070 }; 1071 1072 /* ----- Registration ----- */ 1073 let routine = async |ty: TransferType, 1074 account_pub: &EddsaPublicKey, 1075 recurrent: bool, 1076 fmt: IncomingType| { 1077 let req = json!(req + { 1078 "type": ty, 1079 "account_pub": account_pub, 1080 "authorization_pub": auth_pub1, 1081 "authorization_sig": eddsa_sign(&key_pair1, account_pub.as_ref()), 1082 "recurrent": recurrent 1083 }); 1084 // Valid 1085 let res = prepared_transfer 1086 .post("/registration") 1087 .json(&req) 1088 .await 1089 .assert_ok_json::<RegistrationResponse>(); 1090 1091 // Idempotent 1092 assert_eq!( 1093 res, 1094 prepared_transfer 1095 .post("/registration") 1096 .json(&req) 1097 .await 1098 .assert_ok_json::<RegistrationResponse>() 1099 ); 1100 1101 assert!(!res.subjects.is_empty()); 1102 1103 for sub in res.subjects { 1104 if let TransferSubject::Simple { subject, .. } = sub { 1105 assert_eq!(subject, fmt_in_subject(fmt, &auth_pub1).to_string()); 1106 }; 1107 } 1108 }; 1109 for ty in [TransferType::reserve, TransferType::kyc] { 1110 routine(ty, &auth_pub1, false, ty.into()).await; 1111 routine(ty, &auth_pub1, true, IncomingType::map).await; 1112 } 1113 1114 let acc_pub1 = EddsaPublicKey::rand(); 1115 for ty in [TransferType::reserve, TransferType::kyc] { 1116 routine(ty, &acc_pub1, false, IncomingType::map).await; 1117 routine(ty, &acc_pub1, true, IncomingType::map).await; 1118 } 1119 1120 // Bad signature 1121 prepared_transfer 1122 .post("/registration") 1123 .json(&json!(req + { "authorization_sig": eddsa_sign(&key_pair1, b"lol")})) 1124 .await 1125 .assert_error(ErrorCode::BANK_BAD_SIGNATURE); 1126 1127 // Unknown account 1128 prepared_transfer 1129 .post("/registration") 1130 .json(&json!(req + { "credit_account": unknown_account })) 1131 .await 1132 .assert_error(ErrorCode::BANK_UNKNOWN_CREDITOR); 1133 1134 // Unsupported payto kind 1135 prepared_transfer 1136 .post("/registration") 1137 .json(&json!(req + { "credit_account": UNKNOWN })) 1138 .await 1139 .assert_error(ErrorCode::GENERIC_PAYTO_URI_MALFORMED); 1140 1141 // Malformed payto 1142 prepared_transfer 1143 .post("/registration") 1144 .json(&json!(req + {"credit_account": "http://email@test.com" })) 1145 .await 1146 .assert_error(ErrorCode::GENERIC_JSON_INVALID); 1147 1148 // Reserve pub reuse 1149 prepared_transfer 1150 .post("/registration") 1151 .json(&json!(req + { 1152 "account_pub": acc_pub1, 1153 "authorization_sig": eddsa_sign(&key_pair1, acc_pub1.as_ref()), 1154 })) 1155 .await 1156 .assert_ok_json::<RegistrationResponse>(); 1157 { 1158 let key_pair = Ed25519KeyPair::generate().unwrap(); 1159 let auth_pub = EddsaPublicKey::try_from(key_pair.public_key().as_ref()).unwrap(); 1160 prepared_transfer 1161 .post("/registration") 1162 .json(&json!(req + { 1163 "account_pub": acc_pub1, 1164 "authorization_pub": auth_pub, 1165 "authorization_sig": eddsa_sign(&key_pair, acc_pub1.as_ref()), 1166 })) 1167 .await 1168 .assert_error(ErrorCode::BANK_DUPLICATE_RESERVE_PUB_SUBJECT); 1169 } 1170 1171 // Non recurrent accept one then bounce 1172 prepared_transfer 1173 .post("/registration") 1174 .json(&json!(req + { 1175 "account_pub": acc_pub1, 1176 "authorization_sig": eddsa_sign(&key_pair1, acc_pub1.as_ref()), 1177 })) 1178 .await 1179 .assert_ok_json::<RegistrationResponse>(); 1180 register(&auth_pub1) 1181 .await 1182 .assert_ok_json::<TransferResponse>(); 1183 check_in(&[Reserve(acc_pub1.clone())]).await; 1184 register(&auth_pub1) 1185 .await 1186 .assert_error(ErrorCode::BANK_TRANSFER_MAPPING_REUSED); 1187 1188 // Again without using mapping 1189 let acc_pub2 = EddsaPublicKey::rand(); 1190 prepared_transfer 1191 .post("/registration") 1192 .json(&json!(req + { 1193 "account_pub": acc_pub2, 1194 "authorization_sig": eddsa_sign(&key_pair1, acc_pub2.as_ref()), 1195 })) 1196 .await 1197 .assert_ok_json::<RegistrationResponse>(); 1198 wire_gateway 1199 .post("/admin/add-incoming") 1200 .json(json!({ 1201 "amount": amount, 1202 "reserve_pub": acc_pub2, 1203 "debit_account": debit_acount, 1204 })) 1205 .await 1206 .assert_ok(); 1207 register(&auth_pub1) 1208 .await 1209 .assert_error(ErrorCode::BANK_TRANSFER_MAPPING_REUSED); 1210 check_in(&[Reserve(acc_pub1.clone()), Reserve(acc_pub2.clone())]).await; 1211 1212 // Recurrent accept one and delay others 1213 let acc_pub3 = EddsaPublicKey::rand(); 1214 prepared_transfer 1215 .post("/registration") 1216 .json(&json!(req + { 1217 "account_pub": acc_pub3, 1218 "authorization_sig": eddsa_sign(&key_pair1, acc_pub3.as_ref()), 1219 "recurrent": true 1220 })) 1221 .await 1222 .assert_ok_json::<RegistrationResponse>(); 1223 for _ in 0..5 { 1224 register(&auth_pub1) 1225 .await 1226 .assert_ok_json::<TransferResponse>(); 1227 } 1228 check_in(&[ 1229 Reserve(acc_pub1.clone()), 1230 Reserve(acc_pub2.clone()), 1231 Reserve(acc_pub3.clone()), 1232 Pending, 1233 Pending, 1234 Pending, 1235 Pending, 1236 ]) 1237 .await; 1238 1239 // Complete pending on recurrent update 1240 let acc_pub4 = EddsaPublicKey::rand(); 1241 prepared_transfer 1242 .post("/registration") 1243 .json(&json!(req + { 1244 "type": "kyc", 1245 "account_pub": acc_pub4, 1246 "authorization_sig": eddsa_sign(&key_pair1, acc_pub4.as_ref()), 1247 "recurrent": true 1248 })) 1249 .await 1250 .assert_ok_json::<RegistrationResponse>(); 1251 prepared_transfer 1252 .post("/registration") 1253 .json(&json!(req + { 1254 "account_pub": acc_pub4, 1255 "authorization_sig": eddsa_sign(&key_pair1, acc_pub4.as_ref()), 1256 "recurrent": true 1257 })) 1258 .await 1259 .assert_ok_json::<RegistrationResponse>(); 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 ]) 1269 .await; 1270 1271 // Kyc key reuse keep pending ones 1272 wire_gateway 1273 .post("/admin/add-kycauth") 1274 .json(json!({ 1275 "amount": amount, 1276 "account_pub": acc_pub4, 1277 "debit_account": debit_acount, 1278 })) 1279 .await 1280 .assert_ok_json::<TransferResponse>(); 1281 check_in(&[ 1282 Reserve(acc_pub1.clone()), 1283 Reserve(acc_pub2.clone()), 1284 Reserve(acc_pub3.clone()), 1285 Kyc(acc_pub4.clone()), 1286 Reserve(acc_pub4.clone()), 1287 Pending, 1288 Pending, 1289 Kyc(acc_pub4.clone()), 1290 ]) 1291 .await; 1292 1293 // Switching to non recurrent cancel pending 1294 let auth_pair = Ed25519KeyPair::generate().unwrap(); 1295 let auth_pub2 = EddsaPublicKey::try_from(auth_pair.public_key().as_ref()).unwrap(); 1296 prepared_transfer 1297 .post("/registration") 1298 .json(&json!(req + { 1299 "account_pub": auth_pub2, 1300 "authorization_pub": auth_pub2, 1301 "authorization_sig": eddsa_sign(&auth_pair, auth_pub2.as_ref()), 1302 "recurrent": true 1303 })) 1304 .await 1305 .assert_ok_json::<RegistrationResponse>(); 1306 for _ in 0..3 { 1307 register(&auth_pub2) 1308 .await 1309 .assert_ok_json::<TransferResponse>(); 1310 } 1311 check_in(&[ 1312 Reserve(acc_pub1.clone()), 1313 Reserve(acc_pub2.clone()), 1314 Reserve(acc_pub3.clone()), 1315 Kyc(acc_pub4.clone()), 1316 Reserve(acc_pub4.clone()), 1317 Pending, 1318 Pending, 1319 Kyc(acc_pub4.clone()), 1320 Reserve(auth_pub2.clone()), 1321 Pending, 1322 Pending, 1323 ]) 1324 .await; 1325 prepared_transfer 1326 .post("/registration") 1327 .json(&json!(req + { 1328 "type": "kyc", 1329 "account_pub": auth_pub2, 1330 "authorization_pub": auth_pub2, 1331 "authorization_sig": eddsa_sign(&auth_pair, auth_pub2.as_ref()), 1332 "recurrent": false 1333 })) 1334 .await 1335 .assert_ok_json::<RegistrationResponse>(); 1336 check_in(&[ 1337 Reserve(acc_pub1.clone()), 1338 Reserve(acc_pub2.clone()), 1339 Reserve(acc_pub3.clone()), 1340 Kyc(acc_pub4.clone()), 1341 Reserve(acc_pub4.clone()), 1342 Pending, 1343 Pending, 1344 Kyc(acc_pub4.clone()), 1345 Reserve(auth_pub2.clone()), 1346 Bounced, 1347 Bounced, 1348 ]) 1349 .await; 1350 1351 // Recurrent reserve simple subject 1352 let acc_pub5 = EddsaPublicKey::rand(); 1353 prepared_transfer 1354 .post("/registration") 1355 .json(&json!(req + { 1356 "type": "reserve", 1357 "account_pub": acc_pub5, 1358 "authorization_pub": auth_pub2, 1359 "authorization_sig": eddsa_sign(&auth_pair, acc_pub5.as_ref()), 1360 "recurrent": true 1361 })) 1362 .await 1363 .assert_ok_json::<RegistrationResponse>(); 1364 wire_gateway 1365 .post("/admin/add-incoming") 1366 .json(json!({ 1367 "amount": amount, 1368 "reserve_pub": acc_pub5, 1369 "debit_account": debit_acount, 1370 })) 1371 .await 1372 .assert_ok(); 1373 register(&auth_pub2) 1374 .await 1375 .assert_ok_json::<TransferResponse>(); 1376 check_in(&[ 1377 Reserve(acc_pub1.clone()), 1378 Reserve(acc_pub2.clone()), 1379 Reserve(acc_pub3.clone()), 1380 Kyc(acc_pub4.clone()), 1381 Reserve(acc_pub4.clone()), 1382 Pending, 1383 Pending, 1384 Kyc(acc_pub4.clone()), 1385 Reserve(auth_pub2.clone()), 1386 Bounced, 1387 Bounced, 1388 Reserve(acc_pub5.clone()), 1389 Pending, 1390 ]) 1391 .await; 1392 1393 // Recurrent kyc simple subject 1394 prepared_transfer 1395 .post("/registration") 1396 .json(&json!(req + { 1397 "type": "kyc", 1398 "account_pub": acc_pub5, 1399 "authorization_pub": auth_pub2, 1400 "authorization_sig": eddsa_sign(&auth_pair, acc_pub5.as_ref()), 1401 "recurrent": false 1402 })) 1403 .await 1404 .assert_ok_json::<RegistrationResponse>(); 1405 prepared_transfer 1406 .post("/registration") 1407 .json(&json!(req + { 1408 "type": "kyc", 1409 "account_pub": acc_pub5, 1410 "authorization_pub": auth_pub2, 1411 "authorization_sig": eddsa_sign(&auth_pair, acc_pub5.as_ref()), 1412 "recurrent": true 1413 })) 1414 .await 1415 .assert_ok_json::<RegistrationResponse>(); 1416 wire_gateway 1417 .post("/admin/add-kycauth") 1418 .json(json!({ 1419 "amount": amount, 1420 "account_pub": acc_pub5, 1421 "debit_account": debit_acount, 1422 })) 1423 .await 1424 .assert_ok(); 1425 for _ in 0..2 { 1426 register(&auth_pub2) 1427 .await 1428 .assert_ok_json::<TransferResponse>(); 1429 } 1430 check_in(&[ 1431 Reserve(acc_pub1.clone()), 1432 Reserve(acc_pub2.clone()), 1433 Reserve(acc_pub3.clone()), 1434 Kyc(acc_pub4.clone()), 1435 Reserve(acc_pub4.clone()), 1436 Pending, 1437 Pending, 1438 Kyc(acc_pub4.clone()), 1439 Reserve(auth_pub2.clone()), 1440 Bounced, 1441 Bounced, 1442 Reserve(acc_pub5.clone()), 1443 Bounced, 1444 Kyc(acc_pub5.clone()), 1445 Pending, 1446 Pending, 1447 ]) 1448 .await; 1449 1450 /* ----- Unregistration ----- */ 1451 let now = Timestamp::now().to_string(); 1452 let un_req = json!({ 1453 "timestamp": now, 1454 "authorization_pub": auth_pub2, 1455 "authorization_sig": eddsa_sign(&auth_pair, now.as_bytes()), 1456 }); 1457 1458 // Delete 1459 prepared_transfer 1460 .post("/unregistration") 1461 .json(&un_req) 1462 .await 1463 .assert_no_content(); 1464 1465 // Check bounce pending on deletion 1466 check_in(&[ 1467 Reserve(acc_pub1.clone()), 1468 Reserve(acc_pub2.clone()), 1469 Reserve(acc_pub3.clone()), 1470 Kyc(acc_pub4.clone()), 1471 Reserve(acc_pub4.clone()), 1472 Pending, 1473 Pending, 1474 Kyc(acc_pub4.clone()), 1475 Reserve(auth_pub2.clone()), 1476 Bounced, 1477 Bounced, 1478 Reserve(acc_pub5.clone()), 1479 Bounced, 1480 Kyc(acc_pub5.clone()), 1481 Bounced, 1482 Bounced, 1483 ]) 1484 .await; 1485 1486 // Idempotent 1487 prepared_transfer 1488 .post("/unregistration") 1489 .json(&un_req) 1490 .await 1491 .assert_error(ErrorCode::BANK_TRANSACTION_NOT_FOUND); 1492 1493 // Bad signature 1494 prepared_transfer 1495 .post("/unregistration") 1496 .json(&json!(un_req + { 1497 "authorization_sig": eddsa_sign(&auth_pair, b"lol"), 1498 })) 1499 .await 1500 .assert_error(ErrorCode::BANK_BAD_SIGNATURE); 1501 1502 // Old timestamp 1503 let now = (Timestamp::now() - SignedDuration::from_mins(10)).to_string(); 1504 prepared_transfer 1505 .post("/unregistration") 1506 .json(json!({ 1507 "timestamp": now, 1508 "authorization_pub": auth_pub2, 1509 "authorization_sig": eddsa_sign(&auth_pair, now.as_bytes()), 1510 })) 1511 .await 1512 .assert_error(ErrorCode::BANK_OLD_TIMESTAMP); 1513 1514 /* ----- API ----- */ 1515 1516 let history: Vec<_> = wire_gateway 1517 .get("/history/incoming?limit=20") 1518 .await 1519 .assert_ok_json::<IncomingHistory>() 1520 .incoming_transactions 1521 .into_iter() 1522 .map(|tx| { 1523 let (acc_pub, auth_pub, auth_sig) = match tx { 1524 IncomingBankTransaction::Reserve { 1525 reserve_pub, 1526 authorization_pub, 1527 authorization_sig, 1528 .. 1529 } => (reserve_pub, authorization_pub, authorization_sig), 1530 IncomingBankTransaction::Wad { .. } => unreachable!(), 1531 IncomingBankTransaction::Kyc { 1532 account_pub, 1533 authorization_pub, 1534 authorization_sig, 1535 .. 1536 } => (account_pub, authorization_pub, authorization_sig), 1537 }; 1538 if let Some(auth_pub) = &auth_pub { 1539 assert!(check_eddsa_signature( 1540 auth_pub, 1541 acc_pub.as_ref(), 1542 &auth_sig.unwrap() 1543 )); 1544 } else { 1545 assert!(auth_sig.is_none()) 1546 } 1547 (acc_pub, auth_pub) 1548 }) 1549 .collect(); 1550 assert_eq!( 1551 history, 1552 [ 1553 (acc_pub1.clone(), Some(auth_pub1.clone())), 1554 (acc_pub2.clone(), None), 1555 (acc_pub3.clone(), Some(auth_pub1.clone())), 1556 (acc_pub4.clone(), Some(auth_pub1.clone())), 1557 (acc_pub4.clone(), Some(auth_pub1.clone())), 1558 (acc_pub4.clone(), None), 1559 (auth_pub2.clone(), Some(auth_pub2.clone())), 1560 (acc_pub5.clone(), None), 1561 (acc_pub5.clone(), None), 1562 ] 1563 ) 1564 }