libeufin

Integration and sandbox testing for FinTech APIs and data formats
Log | Files | Refs | Submodules | README | LICENSE

commit 248a7e70dade362cff8b12140696baf91acdbeca
parent b3043819e7e2af8f18b2c434b7266c25dcace578
Author: Antoine A <>
Date:   Tue,  8 Sep 2026 13:03:59 +0200

ebisync: many fixes

Diffstat:
Mdebian/etc/libeufin-ebisync/secrets/ebisync-fetch.secret.conf | 2+-
Mlibeufin-bank/src/api.rs | 5+----
Mlibeufin-bank/src/api/token.rs | 8++++++--
Mlibeufin-ebics/src/ebics.rs | 5++---
Mlibeufin-ebisync/ebisync.conf | 2+-
Mlibeufin-ebisync/src/api.rs | 3+--
Mlibeufin-ebisync/src/lib.rs | 22+++++++++++-----------
Mlibeufin-nexus/src/fetch.rs | 14++++++--------
Mlibeufin-nexus/src/lib.rs | 17+++++++++--------
Mtestbench/src/main.rs | 174++++++++++++++++++++++++++++++++++++++-----------------------------------------
10 files changed, 121 insertions(+), 131 deletions(-)

diff --git a/debian/etc/libeufin-ebisync/secrets/ebisync-fetch.secret.conf b/debian/etc/libeufin-ebisync/secrets/ebisync-fetch.secret.conf @@ -10,4 +10,4 @@ # AZURE_ACCOUNT_KEY = Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw== # Which Azure Blob Storage container to use for azure-blob-storage -# AZURE_COUNTAINER = mycontainer +# AZURE_CONTAINER = mycontainer diff --git a/libeufin-bank/src/api.rs b/libeufin-bank/src/api.rs @@ -27,9 +27,7 @@ use axum::{ }; use serde::{Deserialize, Serialize}; use sqlx::PgPool; -use taler_api::{ - api::TalerRouter as _, error::ApiResult, extract::Query, notification::NotificationChannel, -}; +use taler_api::{error::ApiResult, extract::Query, notification::NotificationChannel}; use taler_common::{ api::LibtoolVersion, types::amount::{Amount, Currency}, @@ -249,7 +247,6 @@ pub fn bank_api(state: Arc<BankState>) -> Router { .merge(prepared_api()) .merge(revenue_api()) .with_state(state) - .finalize() } #[cfg(any(test, feature = "test-utils"))] diff --git a/libeufin-bank/src/api/token.rs b/libeufin-bank/src/api/token.rs @@ -216,7 +216,7 @@ pub mod test { use std::{str::FromStr, time::Duration}; use axum::http::{Method, header::AUTHORIZATION}; - use jiff::Timestamp; + use jiff::{SignedDuration, Timestamp}; use taler_api::db::TypeHelper; use taler_common::{ api::ErrorDetail, @@ -288,7 +288,11 @@ pub mod test { match token.expiration { TalerTimestamp::Never => unreachable!(), TalerTimestamp::Timestamp(expiration) => { - let lifetime = token.creation.duration_until(expiration).unsigned_abs(); + let lifetime = token + .creation + .duration_until(expiration) + .max(SignedDuration::ZERO) + .unsigned_abs(); assert_eq!(lifetime, TOKEN_DEFAULT_DURATION); } } diff --git a/libeufin-ebics/src/ebics.rs b/libeufin-ebics/src/ebics.rs @@ -24,7 +24,6 @@ use aws_lc_rs::{ encoding::AsDer, error::KeyRejected, rsa::{self, PrivateDecryptingKey, PublicKey}, - signature::KeyPair, }; use compact_str::CompactString; use flate2::write::ZlibDecoder; @@ -45,8 +44,8 @@ use crate::{ cli::EbicsLogs, config::EbicsHostCfg, crypto::{ - decrypt_ebics_e002, decrypt_ebics_e002_key, digest_ebics_order_a006, ebics_pub_key_hash, - encrypt_ebics_e002, gen_ebics_e002_key, sign_ebics_a006, + decrypt_ebics_e002, decrypt_ebics_e002_key, digest_ebics_order_a006, encrypt_ebics_e002, + gen_ebics_e002_key, sign_ebics_a006, }, db::{ebics_first, ebics_register, ebics_remove}, ebics::{ diff --git a/libeufin-ebisync/ebisync.conf b/libeufin-ebisync/ebisync.conf @@ -50,7 +50,7 @@ DESTINATION = none # AZURE_ACCOUNT_KEY = Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw== # Which Azure Blob Storage container to use for azure-blob-storage -# AZURE_COUNTAINER = mycontainer +# AZURE_CONTAINER = mycontainer [ebisync-submit] # Where does the ebics file come from? This his can either can be ebisync-api or none diff --git a/libeufin-ebisync/src/api.rs b/libeufin-ebisync/src/api.rs @@ -36,7 +36,7 @@ use reqwest::StatusCode; use serde::{Deserialize, Serialize}; use sqlx::PgPool; use taler_api::{ - api::{RouterUtils, TalerRouter as _}, + api::RouterUtils, auth::AuthMethod, error::{ApiResult, bad_request, failure, failure_status}, }; @@ -216,5 +216,4 @@ pub fn sync_api(state: Arc<EbisyncState>, spa: &str, auth: AuthMethod) -> Router ) .with_state(state) .auth(auth, "realm") - .finalize() } diff --git a/libeufin-ebisync/src/lib.rs b/libeufin-ebisync/src/lib.rs @@ -23,14 +23,13 @@ use std::{ fmt::Write as _, io::{Cursor, Read as _}, sync::Arc, - time::Duration, }; use anyhow::{anyhow, bail}; use axum::{body::Bytes, http::HeaderValue}; use compact_str::CompactStringExt; use http_client::Client; -use jiff::{Timestamp, Zoned, civil::Date, tz::TimeZone}; +use jiff::{SignedDuration, Timestamp, Zoned, civil::Date, tz::TimeZone}; use libeufin_ebics::{ cli::{EbicsArgs, EbicsLogs}, db::{get_task_status, update_task_status}, @@ -105,7 +104,7 @@ pub enum Cmd { ebics: EbicsArgs, /// Only supported in --transient mode, this option lets specify the earliest timestamp of the downloaded documents - #[arg(long, value_name = "YYYY-MM-DD")] + #[arg(long, value_name = "YYYY-MM-DD", requires = "transient")] pinned_start: Option<Date>, /// Only supported in --transient mode, do not consume fetched documents @@ -353,7 +352,7 @@ pub async fn ebics_fetch( } } else { // We never ran, we must checkpoint now - now + Timestamp::UNIX_EPOCH } }; @@ -430,15 +429,16 @@ pub async fn ebics_fetch( } } - let delay = now.duration_until(next_fetch.min(next_checkpoint)); - let tx = timeout( - Duration::from_millis(delay.abs().as_millis() as u64), - receiver.recv(), - ) - .await; + let delay = now + .duration_until(next_fetch.min(next_checkpoint)) + .max(SignedDuration::ZERO) + .unsigned_abs(); + let tx = timeout(delay, receiver.recv()).await; if let Ok(Some(notification)) = tx { info!(target: "fetch", "Running at real-time notifications reception"); - upload(&notification, None).await?; + if let Err(e) = upload(&notification, None).await { + error!(target: "fetch", "{e}"); + } } } }; diff --git a/libeufin-nexus/src/fetch.rs b/libeufin-nexus/src/fetch.rs @@ -20,12 +20,11 @@ use std::{ collections::BTreeMap, io::{Cursor, Read as _}, - time::Duration, }; use anyhow::{anyhow, bail}; use compact_str::CompactStringExt; -use jiff::{Timestamp, Zoned, tz::TimeZone}; +use jiff::{SignedDuration, Timestamp, Zoned, tz::TimeZone}; use libeufin_ebics::{ db::{get_task_status, update_task_status}, ebics::{ @@ -272,12 +271,11 @@ pub async fn ebics_fetch( } } - let delay = now.duration_until(next_fetch.min(next_checkpoint)); - let tx = timeout( - Duration::from_millis(delay.abs().as_millis() as u64), - receiver.recv(), - ) - .await; + let delay = now + .duration_until(next_fetch.min(next_checkpoint)) + .max(SignedDuration::ZERO) + .unsigned_abs(); + let tx = timeout(delay, receiver.recv()).await; if let Ok(Some(mut notification)) = tx { notification.retain(|order| orders.iter().find(|it| order.eq(it)).is_some()); if !notification.is_empty() { diff --git a/libeufin-nexus/src/lib.rs b/libeufin-nexus/src/lib.rs @@ -23,7 +23,7 @@ use std::{fmt::Write, sync::Arc}; use anyhow::{anyhow, bail}; use compact_str::{CompactString, CompactStringExt, ToCompactString}; -use jiff::{Timestamp, civil::Date}; +use jiff::{SignedDuration, Timestamp, civil::Date}; use libeufin_ebics::{ cli::{EbicsArgs, EbicsLogs}, db::update_task_status, @@ -302,12 +302,13 @@ pub async fn ebics_submit( if let Err(e) = update_task_status(db, SUBMIT_TASK_KEY, &now, success).await { warn!(target: "submit", "{e}"); } - let wait = Timestamp::now().duration_until(now + submit_cfg.frequency); - if wait.is_positive() { - tokio::time::sleep(wait.unsigned_abs()).await; - } else { - tokio::task::yield_now().await; - } + tokio::time::sleep( + Timestamp::now() + .duration_until(now + submit_cfg.frequency) + .max(SignedDuration::ZERO) + .unsigned_abs(), + ) + .await; } } } @@ -584,7 +585,7 @@ pub async fn run(cfg: &Config, cmd: Cmd) -> anyhow::Result<()> { if let Some(it) = cfg.revenue_cfg { server = server.revenue(api.clone(), it.auth.method()) } - server.finalize().serve(&cfg.serve_cfg, None).await?; + server.serve(&cfg.serve_cfg, None).await?; } } Cmd::Manual(cmd) => { diff --git a/testbench/src/main.rs b/testbench/src/main.rs @@ -307,113 +307,105 @@ async fn main() -> anyhow::Result<()> { Component::Nexus => { let mut repl = Repl::new(".nexus_history"); auto_setup().await; - loop { - if let Some(cmd) = repl.read_line(&prompt, "") { - match cmd { - NexusCmd::Setup => { - run(&format!("ebics-setup {ebics_log}")).await; - } - NexusCmd::ResetDb => { - run("dbinit -r").await; - } - NexusCmd::Compact => { - compact(format!("testbench/test/{platform}")); - } - NexusCmd::Fetch { raw_args } => { - run(&format!("ebics-fetch {ebics_log} {}", raw_args.join(" "))).await; - } - NexusCmd::Submit { raw_args } => { - run(&format!("ebics-submit {ebics_log} {}", raw_args.join(" "))).await; - } - NexusCmd::Tx => { - run(&format!( - "initiate-payment --amount={}:0.1 --subject=\"single {}\" {payto}", - cfg.currency, - Timestamp::now() - )) - .await; - } - NexusCmd::List { raw_args } => { - run(&format!("list {}", raw_args.join(" "))).await; - } - NexusCmd::ResetKeys => { - if test && std::fs::exists(&ebics.client).unwrap() { - std::fs::remove_file(&ebics.client).unwrap(); - } - if std::fs::exists(&ebics.bank).unwrap() { - std::fs::remove_file(&ebics.bank).unwrap(); - } + while let Some(cmd) = repl.read_line(&prompt, "") { + match cmd { + NexusCmd::Setup => { + run(&format!("ebics-setup {ebics_log}")).await; + } + NexusCmd::ResetDb => { + run("dbinit -r").await; + } + NexusCmd::Compact => { + compact(format!("testbench/test/{platform}")); + } + NexusCmd::Fetch { raw_args } => { + run(&format!("ebics-fetch {ebics_log} {}", raw_args.join(" "))).await; + } + NexusCmd::Submit { raw_args } => { + run(&format!("ebics-submit {ebics_log} {}", raw_args.join(" "))).await; + } + NexusCmd::Tx => { + run(&format!( + "initiate-payment --amount={}:0.1 --subject=\"single {}\" {payto}", + cfg.currency, + Timestamp::now() + )) + .await; + } + NexusCmd::List { raw_args } => { + run(&format!("list {}", raw_args.join(" "))).await; + } + NexusCmd::ResetKeys => { + if test && std::fs::exists(&ebics.client).unwrap() { + std::fs::remove_file(&ebics.client).unwrap(); } - NexusCmd::TxCheck => { - run(&format!("testing tx-check {ebics_log}")).await; + if std::fs::exists(&ebics.bank).unwrap() { + std::fs::remove_file(&ebics.bank).unwrap(); } - NexusCmd::Wss => { - run(&format!("testing wss {ebics_log}")).await; + } + NexusCmd::TxCheck => { + run(&format!("testing tx-check {ebics_log}")).await; + } + NexusCmd::Wss => { + run(&format!("testing wss {ebics_log}")).await; + } + NexusCmd::Import { path, glob } => { + let mut b = GlobSetBuilder::new(); + for glob in glob { + b.add(glob); } - NexusCmd::Import { path, glob } => { - let mut b = GlobSetBuilder::new(); - for glob in glob { - b.add(glob); - } - let matcher = b.build().unwrap(); - let mut paths = Vec::new(); - for entry in WalkDir::new(&path).into_iter().filter_map(Result::ok) { - let entry = entry.path(); + let matcher = b.build().unwrap(); + let mut paths = Vec::new(); + for entry in WalkDir::new(&path).into_iter().filter_map(Result::ok) { + let entry = entry.path(); - // usually match against relative path - let rel = entry.strip_prefix(&path).unwrap_or(entry); + // usually match against relative path + let rel = entry.strip_prefix(&path).unwrap_or(entry); - if matcher.is_match(rel) { - paths.push(entry.to_string_lossy().to_string()); - } + if matcher.is_match(rel) { + paths.push(entry.to_string_lossy().to_string()); } - - run(&format!("manual import {}", paths.join(" "))).await; - } - NexusCmd::Export { path } => { - run(&format!("manual export {}", path.to_string_lossy())).await; } - NexusCmd::Exit => break, + + run(&format!("manual import {}", paths.join(" "))).await; } - } else { - break; + NexusCmd::Export { path } => { + run(&format!("manual export {}", path.to_string_lossy())).await; + } + NexusCmd::Exit => break, } } } Component::Ebisync => { let mut repl = Repl::new(".ebisync_history"); auto_setup().await; - loop { - if let Some(cmd) = repl.read_line(&prompt, "") { - match cmd { - SyncCmd::Compact => { - compact(format!("testbench/test/{platform}")); - } - SyncCmd::Setup => { - run(&format!("setup {ebics_log}")).await; - } - SyncCmd::ResetDb => { - run("dbinit -r").await; - } - SyncCmd::Fetch { raw_args } => { - run(&format!("fetch {ebics_log} {}", raw_args.join(" "))).await; - } - SyncCmd::Testing { raw_args } => { - run(&format!("testing {}", raw_args.join(" "))).await; - } - SyncCmd::Serve => { - run("serve").await; - } - SyncCmd::ResetKeys => { - if test { - std::fs::remove_file(&ebics.client).unwrap(); - } - std::fs::remove_file(&ebics.bank).unwrap(); + while let Some(cmd) = repl.read_line(&prompt, "") { + match cmd { + SyncCmd::Compact => { + compact(format!("testbench/test/{platform}")); + } + SyncCmd::Setup => { + run(&format!("setup {ebics_log}")).await; + } + SyncCmd::ResetDb => { + run("dbinit -r").await; + } + SyncCmd::Fetch { raw_args } => { + run(&format!("fetch {ebics_log} {}", raw_args.join(" "))).await; + } + SyncCmd::Testing { raw_args } => { + run(&format!("testing {}", raw_args.join(" "))).await; + } + SyncCmd::Serve => { + run("serve").await; + } + SyncCmd::ResetKeys => { + if test { + std::fs::remove_file(&ebics.client).unwrap(); } - SyncCmd::Exit => break, + std::fs::remove_file(&ebics.bank).unwrap(); } - } else { - break; + SyncCmd::Exit => break, } } }