wallet-core: improve crypto worker code duplication

Also add new testCrypto call for later testing
This commit is contained in:
Florian Dold 2022-10-05 12:52:49 +02:00
parent 4d232fd565
commit dd14e67c70
No known key found for this signature in database
GPG Key ID: D2E4F00F29D02A4B
12 changed files with 182 additions and 180 deletions

View File

@ -152,6 +152,14 @@ export enum TalerErrorCode {
GENERIC_PARAMETER_MALFORMED = 26,
/**
* The reserve public key given as part of a /reserves/ endpoint was malformed.
* Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
* (A value of 0 indicates that the error is generated client-side).
*/
GENERIC_RESERVE_PUB_MALFORMED = 27,
/**
* The currencies involved in the operation do not match.
* Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
@ -456,14 +464,6 @@ export enum TalerErrorCode {
EXCHANGE_GENERIC_NEW_DENOMS_ARRAY_SIZE_EXCESSIVE = 1018,
/**
* The reserve public key was malformed.
* Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
* (A value of 0 indicates that the error is generated client-side).
*/
EXCHANGE_GENERIC_RESERVE_PUB_MALFORMED = 1019,
/**
* The time at the server is too far off from the time specified in the request. Most likely the client system time is wrong.
* Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
@ -1288,6 +1288,38 @@ export enum TalerErrorCode {
EXCHANGE_RESERVES_RESERVE_MERGE_SIGNATURE_INVALID = 1778,
/**
* The signature by the reserve affirming the open operation is invalid.
* Returned with an HTTP status code of #MHD_HTTP_FORBIDDEN (403).
* (A value of 0 indicates that the error is generated client-side).
*/
EXCHANGE_RESERVES_OPEN_BAD_SIGNATURE = 1785,
/**
* The signature by the reserve affirming the close operation is invalid.
* Returned with an HTTP status code of #MHD_HTTP_FORBIDDEN (403).
* (A value of 0 indicates that the error is generated client-side).
*/
EXCHANGE_RESERVES_CLOSE_BAD_SIGNATURE = 1786,
/**
* The signature by the reserve affirming the attestion request is invalid.
* Returned with an HTTP status code of #MHD_HTTP_FORBIDDEN (403).
* (A value of 0 indicates that the error is generated client-side).
*/
EXCHANGE_RESERVES_ATTEST_BAD_SIGNATURE = 1787,
/**
* The exchange does not know an origin account to which the remaining reserve balance could be wired to, and the wallet failed to provide one.
* Returned with an HTTP status code of #MHD_HTTP_CONFLICT (409).
* (A value of 0 indicates that the error is generated client-side).
*/
EXCHANGE_RESERVES_CLOSE_NO_TARGET_ACCOUNT = 1788,
/**
* The auditor that was supposed to be disabled is unknown to this exchange.
* Returned with an HTTP status code of #MHD_HTTP_NOT_FOUND (404).
@ -1768,14 +1800,6 @@ export enum TalerErrorCode {
MERCHANT_GENERIC_HOLE_IN_WIRE_FEE_STRUCTURE = 2001,
/**
* The reserve key of given to a /reserves/ handler was malformed.
* Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
* (A value of 0 indicates that the error is generated client-side).
*/
MERCHANT_GENERIC_RESERVE_PUB_MALFORMED = 2002,
/**
* The proposal is not known to the backend.
* Returned with an HTTP status code of #MHD_HTTP_NOT_FOUND (404).
@ -3064,6 +3088,14 @@ export enum TalerErrorCode {
WALLET_CRYPTO_WORKER_ERROR = 7023,
/**
* The crypto worker received a bad request.
* Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
* (A value of 0 indicates that the error is generated client-side).
*/
WALLET_CRYPTO_WORKER_BAD_REQUEST = 7024,
/**
* We encountered a timeout with our payment backend.
* Returned with an HTTP status code of #MHD_HTTP_GATEWAY_TIMEOUT (504).

View File

@ -29,7 +29,7 @@ import { timer, performanceNow, TimerHandle } from "../../util/timer.js";
import { nullCrypto, TalerCryptoInterface } from "../cryptoImplementation.js";
import { CryptoWorker } from "./cryptoWorkerInterface.js";
const logger = new Logger("cryptoApi.ts");
const logger = new Logger("cryptoDispatcher.ts");
/**
* State of a crypto worker.
@ -238,7 +238,7 @@ export class CryptoDispatcher {
}
handleWorkerMessage(ws: WorkerInfo, msg: any): void {
const id = msg.data.id;
const id = msg.id;
if (typeof id !== "number") {
logger.error("rpc id must be number");
return;
@ -256,12 +256,12 @@ export class CryptoDispatcher {
if (currentWorkItem.state === WorkItemState.Running) {
this.numBusy--;
currentWorkItem.state = WorkItemState.Finished;
if (msg.data.type === "success") {
currentWorkItem.resolve(msg.data.result);
} else if (msg.data.type === "error") {
if (msg.type === "success") {
currentWorkItem.resolve(msg.result);
} else if (msg.type === "error") {
currentWorkItem.reject(
TalerError.fromDetail(TalerErrorCode.WALLET_CRYPTO_WORKER_ERROR, {
innerError: msg.data.error,
innerError: msg.error,
}),
);
} else {

View File

@ -1,8 +1,65 @@
/*
This file is part of GNU Taler
(C) 2022 Taler Systems S.A.
GNU Taler is free software; you can redistribute it and/or modify it under the
terms of the GNU General Public License as published by the Free Software
Foundation; either version 3, or (at your option) any later version.
GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
/**
* Imports.
*/
import { TalerErrorDetail } from "@gnu-taler/taler-util";
/**
* Common interface for all crypto workers.
*/
export interface CryptoWorker {
postMessage(message: any): void;
terminate(): void;
onmessage: ((m: any) => void) | undefined;
onerror: ((m: any) => void) | undefined;
}
/**
* Type of requests sent to the crypto worker.
*/
export type CryptoWorkerRequestMessage = {
/**
* Operation ID to correlate request with the response.
*/
id: number;
/**
* Operation to execute.
*/
operation: string;
/**
* Operation-specific request payload.
*/
req: any;
};
/**
* Type of messages sent back by the crypto worker.
*/
export type CryptoWorkerResponseMessage =
| {
type: "success";
id: number;
result: any;
}
| {
type: "error";
id?: number;
error: TalerErrorDetail;
};

View File

@ -17,12 +17,12 @@
/**
* Imports
*/
import { Logger } from "@gnu-taler/taler-util";
import os from "os";
import { nativeCryptoR } from "../cryptoImplementation.js";
import { CryptoWorkerFactory } from "./cryptoDispatcher.js";
import { CryptoWorker } from "./cryptoWorkerInterface.js";
import os from "os";
import { Logger } from "@gnu-taler/taler-util";
import { nativeCryptoR } from "../cryptoImplementation.js";
import { getErrorDetailFromException } from "../../errors.js";
import { processRequestWithImpl } from "./worker-common.js";
const logger = new Logger("nodeThreadWorker.ts");
@ -71,44 +71,7 @@ const workerCode = `
*/
export function handleWorkerMessage(msg: any): void {
const handleRequest = async (): Promise<void> => {
const req = msg.req;
if (typeof req !== "object") {
logger.error("request must be an object");
return;
}
const id = msg.id;
if (typeof id !== "number") {
logger.error("RPC id must be number");
return;
}
const operation = msg.operation;
if (typeof operation !== "string") {
logger.error("RPC operation must be string");
return;
}
const impl = nativeCryptoR;
if (!(operation in impl)) {
logger.error(`crypto operation '${operation}' not found`);
return;
}
let responseMsg: any;
try {
const result = await (impl as any)[operation](impl, req);
responseMsg = { data: { type: "success", result, id } };
} catch (e: any) {
logger.error(`error during operation: ${e.stack ?? e.toString()}`);
responseMsg = {
data: {
type: "error",
error: getErrorDetailFromException(e),
id,
},
};
}
const responseMsg = await processRequestWithImpl(msg, nativeCryptoR);
try {
// eslint-disable-next-line @typescript-eslint/no-var-requires
const _r = "require";
@ -122,16 +85,12 @@ export function handleWorkerMessage(msg: any): void {
logger.error("parent port not available (not running in thread?");
}
} catch (e: any) {
logger.error(
`error sending back operation result: ${e.stack ?? e.toString()}`,
);
logger.error(`error in node worker: ${e.stack ?? e.toString()}`);
return;
}
};
handleRequest().catch((e) => {
logger.error("error in node worker", e);
});
handleRequest();
}
export function handleWorkerError(e: Error): void {

View File

@ -24,6 +24,9 @@ import { OpenedPromise, openPromise } from "../../util/promiseUtils.js";
const logger = new Logger("synchronousWorkerFactory.ts");
/**
* Client for the crypto helper process (taler-crypto-worker from exchange.git).
*/
export class CryptoRpcClient {
proc: child_process.ChildProcessByStdio<
internal.Writable,

View File

@ -14,20 +14,24 @@
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
import { Logger } from "@gnu-taler/taler-util";
import { getErrorDetailFromException } from "../../errors.js";
import { j2s, Logger } from "@gnu-taler/taler-util";
import {
nativeCryptoR,
TalerCryptoInterfaceR,
} from "../cryptoImplementation.js";
import { CryptoWorker } from "./cryptoWorkerInterface.js";
import { CryptoRpcClient } from "./rpcClient.js";
import { processRequestWithImpl } from "./worker-common.js";
const logger = new Logger("synchronousWorker.ts");
/**
* Worker implementation that uses node subprocesses.
*
* The node cryto worker can also use IPC to offload cryptographic
* operations to a helper process (ususally written in C / part of taler-exchange).
*/
export class SynchronousCryptoWorker {
export class SynchronousCryptoWorker implements CryptoWorker {
/**
* Function to be called when we receive a message from the worker thread.
*/
@ -144,61 +148,19 @@ export class SynchronousCryptoWorker {
}
}
private async handleRequest(
operation: string,
id: number,
req: unknown,
): Promise<void> {
const impl = this.cryptoImplR;
if (!(operation in impl)) {
logger.error(`crypto operation '${operation}' not found`);
return;
}
let responseMsg: any;
try {
const result = await (impl as any)[operation](impl, req);
responseMsg = { data: { type: "success", result, id } };
} catch (e: any) {
logger.error(`error during operation: ${e.stack ?? e.toString()}`);
responseMsg = {
data: {
type: "error",
id,
error: getErrorDetailFromException(e),
},
};
}
/**
* Send a message to the worker thread.
*/
postMessage(msg: any): void {
const handleRequest = async () => {
const responseMsg = await processRequestWithImpl(msg, this.cryptoImplR);
try {
setTimeout(() => this.dispatchMessage(responseMsg), 0);
} catch (e) {
logger.error("got error during dispatch", e);
}
}
/**
* Send a message to the worker thread.
*/
postMessage(msg: any): void {
const req = msg.req;
if (typeof req !== "object") {
logger.error("request must be an object");
return;
}
const id = msg.id;
if (typeof id !== "number") {
logger.error("RPC id must be number");
return;
}
const operation = msg.operation;
if (typeof operation !== "string") {
logger.error("RPC operation must be string");
return;
}
this.handleRequest(operation, id, req).catch((e) => {
};
handleRequest().catch((e) => {
logger.error("Error while handling crypto request:", e);
});
}

View File

@ -14,19 +14,26 @@
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
/**
* Imports.
*/
import { Logger } from "@gnu-taler/taler-util";
import { getErrorDetailFromException } from "../../errors.js";
import {
nativeCryptoR,
TalerCryptoInterfaceR,
} from "../cryptoImplementation.js";
import { CryptoWorker } from "./cryptoWorkerInterface.js";
import {
processRequestWithImpl,
} from "./worker-common.js";
const logger = new Logger("synchronousWorker.ts");
/**
* Worker implementation that uses node subprocesses.
* Worker implementation that synchronously executes cryptographic
* operations.
*/
export class SynchronousCryptoWorker {
export class SynchronousCryptoWorker implements CryptoWorker {
/**
* Function to be called when we receive a message from the worker thread.
*/
@ -65,61 +72,22 @@ export class SynchronousCryptoWorker {
}
}
private async handleRequest(
operation: string,
id: number,
req: unknown,
): Promise<void> {
const impl = this.cryptoImplR;
if (!(operation in impl)) {
logger.error(`crypto operation '${operation}' not found`);
return;
}
let responseMsg: any;
try {
const result = await (impl as any)[operation](impl, req);
responseMsg = { data: { type: "success", result, id } };
} catch (e: any) {
logger.error(`error during operation: ${e.stack ?? e.toString()}`);
responseMsg = {
data: {
type: "error",
id,
error: getErrorDetailFromException(e),
},
};
}
/**
* Send a message to the worker thread.
*/
postMessage(msg: any): void {
const handleRequest = async () => {
const responseMsg = await processRequestWithImpl(
msg,
this.cryptoImplR,
);
try {
setTimeout(() => this.dispatchMessage(responseMsg), 0);
} catch (e) {
logger.error("got error during dispatch", e);
}
}
/**
* Send a message to the worker thread.
*/
postMessage(msg: any): void {
const req = msg.req;
if (typeof req !== "object") {
logger.error("request must be an object");
return;
}
const id = msg.id;
if (typeof id !== "number") {
logger.error("RPC id must be number");
return;
}
const operation = msg.operation;
if (typeof operation !== "string") {
logger.error("RPC operation must be string");
return;
}
this.handleRequest(operation, id, req).catch((e) => {
};
handleRequest().catch((e) => {
logger.error("Error while handling crypto request:", e);
});
}

View File

@ -76,6 +76,9 @@ export interface DetailsMap {
[TalerErrorCode.WALLET_CRYPTO_WORKER_ERROR]: {
innerError: TalerErrorDetail;
};
[TalerErrorCode.WALLET_CRYPTO_WORKER_BAD_REQUEST]: {
detail: string;
};
}
type ErrBody<Y> = Y extends keyof DetailsMap ? DetailsMap[Y] : never;

View File

@ -92,6 +92,7 @@ export enum WalletApiOperation {
WithdrawTestBalance = "withdrawTestBalance",
PreparePayForUri = "preparePayForUri",
RunIntegrationTest = "runIntegrationTest",
CryptoTest = "cryptoTest",
TestPay = "testPay",
AddExchange = "addExchange",
GetTransactions = "getTransactions",
@ -524,6 +525,15 @@ export type RunIntegrationTestOp = {
response: {};
};
/**
* Test crypto worker.
*/
export type CryptoTestOp = {
op: WalletApiOperation.CryptoTest;
request: {};
response: any;
};
/**
* Make withdrawal on a test deployment of the exchange
* and merchant.
@ -639,6 +649,7 @@ export type WalletOperations = {
[WalletApiOperation.AddBackupProvider]: AddBackupProviderOp;
[WalletApiOperation.GetBackupInfo]: GetBackupInfoOp;
[WalletApiOperation.RunIntegrationTest]: RunIntegrationTestOp;
[WalletApiOperation.CryptoTest]: CryptoTestOp;
[WalletApiOperation.WithdrawTestBalance]: WithdrawTestBalanceOp;
[WalletApiOperation.TestPay]: TestPayOp;
[WalletApiOperation.ExportDb]: ExportDbOp;

View File

@ -1445,6 +1445,9 @@ async function dispatchRequestInternal(
logger.info(`started fakebank withdrawal: ${j2s(fbResp)}`);
return {};
}
case "testCrypto": {
return await ws.cryptoApi.hashString({ str: "hello world" });
}
case "clearDb":
await clearDatabase(ws.db.idbHandle());
return {};

View File

@ -28,6 +28,8 @@ export class BrowserCryptoWorkerFactory implements CryptoWorkerFactory {
startWorker(): CryptoWorker {
const workerCtor = Worker;
const workerPath = "/dist/browserWorkerEntry.js";
// FIXME: This is not really the same interface as the crypto worker!
// We need to wrap it.
return new workerCtor(workerPath) as CryptoWorker;
}

View File

@ -217,6 +217,8 @@ async function reinitWallet(): Promise<void> {
timer = new SetTimeoutTimerAPI();
} else {
httpLib = new BrowserHttpLib();
// We could (should?) use the BrowserCryptoWorkerFactory here,
// but right now we don't, to have less platform differences.
cryptoWorker = new SynchronousCryptoWorkerFactory();
timer = new SetTimeoutTimerAPI();
}