wallet-core: improve crypto worker code duplication
Also add new testCrypto call for later testing
This commit is contained in:
parent
4d232fd565
commit
dd14e67c70
@ -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).
|
||||
|
@ -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 {
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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 {
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
});
|
||||
}
|
||||
|
@ -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);
|
||||
});
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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 {};
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user