gnunet-svn
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[taler-typescript-core] 03/03: harness: implement simple exchange linter


From: gnunet
Subject: [taler-typescript-core] 03/03: harness: implement simple exchange linter
Date: Mon, 27 Jan 2025 12:26:29 +0100

This is an automated email from the git hooks/post-receive script.

dold pushed a commit to branch master
in repository taler-typescript-core.

commit 122d7370e81f0df3f906de37187ade7e4ddb91c1
Author: Florian Dold <florian@dold.me>
AuthorDate: Mon Jan 27 12:26:21 2025 +0100

    harness: implement simple exchange linter
---
 packages/taler-harness/src/index.ts                |  19 ++-
 .../integrationtests/test-exchange-timetravel.ts   |  10 +-
 packages/taler-harness/src/lint.ts                 | 157 ++++++++++++++++++++-
 3 files changed, 169 insertions(+), 17 deletions(-)

diff --git a/packages/taler-harness/src/index.ts 
b/packages/taler-harness/src/index.ts
index 4ae4e4562..31ff52336 100644
--- a/packages/taler-harness/src/index.ts
+++ b/packages/taler-harness/src/index.ts
@@ -88,7 +88,7 @@ import {
 } from "./harness/harness.js";
 import { AML_PROGRAM_TEST_KYC_NEW_MEASURES_PROG } from 
"./integrationtests/test-kyc-new-measures-prog.js";
 import { getTestInfo, runTests } from "./integrationtests/testrunner.js";
-import { lintExchangeDeployment } from "./lint.js";
+import { lintExchangeDeployment, lintExchangeUrl } from "./lint.js";
 
 const logger = new Logger("taler-harness:index.ts");
 
@@ -604,8 +604,8 @@ deploymentCli
   });
 
 deploymentCli
-  .subcommand("lintExchange", "lint-exchange", {
-    help: "Run checks on the exchange deployment.",
+  .subcommand("lintExchangeConfig", "lint-exchange-config", {
+    help: "Run checks on the exchange deployment running on the current 
machine.",
   })
   .flag("cont", ["--continue"], {
     help: "Continue after errors if possible",
@@ -615,11 +615,20 @@ deploymentCli
   })
   .action(async (args) => {
     await lintExchangeDeployment(
-      args.lintExchange.debug,
-      args.lintExchange.cont,
+      args.lintExchangeConfig.debug,
+      args.lintExchangeConfig.cont,
     );
   });
 
+deploymentCli
+  .subcommand("lintExchangeUrl", "lint-exchange-url", {
+    help: "Run checks on a remote exchange deployment.",
+  })
+  .requiredArgument("baseUrl", clk.STRING)
+  .action(async (args) => {
+    await lintExchangeUrl(args.lintExchangeUrl.baseUrl);
+  });
+
 deploymentCli
   .subcommand("waitService", "wait-taler-service", {
     help: "Wait for the config endpoint of a Taler-style service to be 
available",
diff --git 
a/packages/taler-harness/src/integrationtests/test-exchange-timetravel.ts 
b/packages/taler-harness/src/integrationtests/test-exchange-timetravel.ts
index c501baf8d..764b1ae55 100644
--- a/packages/taler-harness/src/integrationtests/test-exchange-timetravel.ts
+++ b/packages/taler-harness/src/integrationtests/test-exchange-timetravel.ts
@@ -19,11 +19,11 @@
  */
 import {
   AbsoluteTime,
-  codecForExchangeKeysJson,
+  codecForExchangeKeysResponse,
   DenominationPubKey,
   DenomKeyType,
   Duration,
-  ExchangeKeysJson,
+  ExchangeKeysResponse,
   Logger,
   TalerCorebankApiClient,
 } from "@gnu-taler/taler-util";
@@ -53,7 +53,7 @@ interface DenomInfo {
   expireDeposit: string;
 }
 
-function getDenomInfoFromKeys(ek: ExchangeKeysJson): DenomInfo[] {
+function getDenomInfoFromKeys(ek: ExchangeKeysResponse): DenomInfo[] {
   const denomInfos: DenomInfo[] = [];
   for (const denomGroup of ek.denominations) {
     switch (denomGroup.cipher) {
@@ -201,7 +201,7 @@ export async function runExchangeTimetravelTest(t: 
GlobalTestState) {
   const keysResp1 = await http.fetch(exchange.baseUrl + "keys");
   const keys1 = await readSuccessResponseJsonOrThrow(
     keysResp1,
-    codecForExchangeKeysJson(),
+    codecForExchangeKeysResponse(),
   );
   console.log(
     "keys 1 (before time travel):",
@@ -223,7 +223,7 @@ export async function runExchangeTimetravelTest(t: 
GlobalTestState) {
   const keysResp2 = await http.fetch(exchange.baseUrl + "keys");
   const keys2 = await readSuccessResponseJsonOrThrow(
     keysResp2,
-    codecForExchangeKeysJson(),
+    codecForExchangeKeysResponse(),
   );
   console.log(
     "keys 2 (after time travel):",
diff --git a/packages/taler-harness/src/lint.ts 
b/packages/taler-harness/src/lint.ts
index bb6d70ed4..ade31c088 100644
--- a/packages/taler-harness/src/lint.ts
+++ b/packages/taler-harness/src/lint.ts
@@ -32,18 +32,24 @@
  * Imports.
  */
 import {
-  codecForExchangeKeysJson,
+  AbsoluteTime,
+  canonicalizeBaseUrl,
+  codecForExchangeKeysResponse,
   codecForKeysManagementResponse,
   Configuration,
   decodeCrock,
+  narrowOpSuccessOrThrow,
+  parsePaytoUri,
+  TalerExchangeHttpClient,
+  TalerProtocolTimestamp,
 } from "@gnu-taler/taler-util";
-import { URL } from "node:url";
-import { spawn } from "child_process";
-import { delayMs } from "./harness/harness.js";
 import {
   createPlatformHttpLib,
   readSuccessResponseJsonOrThrow,
 } from "@gnu-taler/taler-util/http";
+import { spawn } from "child_process";
+import { URL } from "node:url";
+import { delayMs } from "./harness/harness.js";
 
 interface BasicConf {
   mainCurrency: string;
@@ -430,7 +436,10 @@ export async function checkExchangeHttpd(
   {
     const keysUrl = new URL("keys", baseUrl);
 
-    const resp = await Promise.race([httpLib.fetch(keysUrl.href), 
delayMs(2000)]);
+    const resp = await Promise.race([
+      httpLib.fetch(keysUrl.href),
+      delayMs(2000),
+    ]);
 
     if (!resp) {
       context.numErr++;
@@ -446,7 +455,7 @@ export async function checkExchangeHttpd(
     } else {
       const keys = await readSuccessResponseJsonOrThrow(
         resp,
-        codecForExchangeKeysJson(),
+        codecForExchangeKeysResponse(),
       );
 
       if (keys.master_public_key !== pubConf.masterPublicKey) {
@@ -466,7 +475,10 @@ export async function checkExchangeHttpd(
   {
     const keysUrl = new URL("wire", baseUrl);
 
-    const resp = await Promise.race([httpLib.fetch(keysUrl.href), 
delayMs(2000)]);
+    const resp = await Promise.race([
+      httpLib.fetch(keysUrl.href),
+      delayMs(2000),
+    ]);
 
     if (!resp) {
       context.numErr++;
@@ -534,3 +546,134 @@ export async function lintExchangeDeployment(
     process.exit(1);
   }
 }
+
+function isCurrentlyValid(
+  start: TalerProtocolTimestamp,
+  end: TalerProtocolTimestamp,
+): boolean {
+  if (!AbsoluteTime.isExpired(AbsoluteTime.fromProtocolTimestamp(start))) {
+    return false;
+  }
+  if (AbsoluteTime.isExpired(AbsoluteTime.fromProtocolTimestamp(end))) {
+    return false;
+  }
+  return true;
+}
+
+class LintReporter {
+  numErrors = 0;
+  reportError(s: string): void {
+    this.numErrors++;
+    console.log(`ERROR: ${s}`);
+  }
+  reportWarning(s: string): void {
+    console.log(`WARNING: ${s}`);
+  }
+  reportInfo(s: string): void {
+    console.log(`INFO: ${s}`);
+  }
+}
+
+export async function lintExchangeUrl(exchangeBaseUrl: string): Promise<void> {
+  const canonUrl = canonicalizeBaseUrl(exchangeBaseUrl);
+  if (exchangeBaseUrl != canonUrl) {
+    console.error(`invalid or non-canonical base url: ${exchangeBaseUrl}`);
+    process.exit(1);
+  }
+  const exchangeClient = new TalerExchangeHttpClient(canonUrl);
+  const configResp = await exchangeClient.getConfig();
+  narrowOpSuccessOrThrow("", configResp);
+  const currency = configResp.body.currency;
+  const keysResp = await exchangeClient.getKeys();
+  narrowOpSuccessOrThrow("", configResp);
+
+  const reporter = new LintReporter();
+
+  if (keysResp.body.currency != currency) {
+    reporter.reportError("currency mismatch between /config and /keys");
+  }
+
+  if (keysResp.body.signkeys.length == 0) {
+    reporter.reportError("exchange has no signing keys");
+  }
+  // Check for valid signing key
+  {
+    let haveValidSk = false;
+    for (const sk of keysResp.body.signkeys) {
+      if (isCurrentlyValid(sk.stamp_start, sk.stamp_end)) {
+        haveValidSk = true;
+        break;
+      }
+    }
+    if (!haveValidSk) {
+      reporter.reportError("no signing key is valid");
+    }
+  }
+  let numDenomWithdraw = 0;
+  let numDenomDeposit = 0;
+  let numDenomLost = 0;
+  // Check denominations
+  {
+    for (const denomGroup of keysResp.body.denominations) {
+      for (const denom of denomGroup.denoms) {
+        if (denom.lost) {
+          numDenomLost++;
+        }
+        if (
+          !denom.lost &&
+          isCurrentlyValid(denom.stamp_start, denom.stamp_expire_withdraw)
+        ) {
+          numDenomWithdraw++;
+        }
+        if (isCurrentlyValid(denom.stamp_start, denom.stamp_expire_deposit)) {
+          numDenomDeposit++;
+        }
+      }
+    }
+    if (numDenomWithdraw == 0) {
+      reporter.reportWarning("no valid denomination available for withdrawal");
+    }
+    if (numDenomDeposit == 0) {
+      reporter.reportWarning("no valid denomination available for deposit");
+    }
+    if (numDenomLost > 0) {
+      reporter.reportWarning(`have lost denominations (${numDenomLost})`);
+    }
+  }
+  {
+    let haveGlobalFee = false;
+    for (const gf of keysResp.body.global_fees) {
+      if (isCurrentlyValid(gf.start_date, gf.end_date)) {
+        haveGlobalFee = true;
+        break;
+      }
+    }
+    if (!haveGlobalFee) {
+      reporter.reportError("no valid global fees for current time");
+    }
+  }
+  if (keysResp.body.accounts.length === 0) {
+    reporter.reportError("no wire accounts configured");
+  }
+  let wireTypesSet = new Set<string>();
+  for (const acc of keysResp.body.accounts) {
+    const payto = parsePaytoUri(acc.payto_uri);
+    if (!payto) {
+      reporter.reportError(`invalid account payto URI: ${acc.payto_uri}`);
+      continue;
+    }
+    wireTypesSet.add(payto?.targetType);
+  }
+  for (const wireType of wireTypesSet) {
+    let haveAccountFees = false;
+    for (const wf of keysResp.body.wire_fees[wireType]) {
+      if (isCurrentlyValid(wf.start_date, wf.end_date)) {
+        haveAccountFees = true;
+        break;
+      }
+    }
+    if (!haveAccountFees) {
+      reporter.reportError(`missing wire account fees for ${wireType}`);
+    }
+  }
+}

-- 
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]